Skip to content

pkgutil.walk_packages uses mutable object as a default parameter in internal seen function #127318

@asai95

Description

@asai95

Bug report

Bug description:

As the title says, pkgutil.walk_packages is using mutable default (a dictionary) to remember modules it already seen.
Code link.

This may lead to hard-to-track bugs, when you try to import a package with the same name, but in different folder.

Example code where this issue could break stuff (I'm actually using something similar in my tests):

import importlib
import pkgutil
import sys
import tempfile
from pathlib import Path


DUMMY_MODULE = """
def hello_world():
    return "Hello, World!"
"""


class MyLoader:
    def __init__(self, path):
        self.path = path
        self.available_modules = {}

    def load_module(self, fullname):
        if fullname in self.available_modules:
            return self.available_modules[fullname]

    def discover_modules(self):
        sys.path.append(str(self.path))
        for _, module_name, is_pkg in pkgutil.walk_packages([self.path]):
            if not is_pkg:
                module = importlib.import_module(module_name)
                self.available_modules[module_name] = module
        sys.path.remove(str(self.path))


def do_test():
    print("Running test")
    with tempfile.TemporaryDirectory() as tmp_dir:
        tmp_path = Path(tmp_dir)
        package_name = "my_package"
        package_path = tmp_path / package_name
        package_path.mkdir()
        module_path = package_path / "my_module.py"
        init_path = package_path / "__init__.py"
        init_path.touch()
        with module_path.open("w") as f:
            f.write(DUMMY_MODULE)

        loader = MyLoader(path=tmp_path)
        loader.discover_modules()
        module = loader.load_module("my_package.my_module")
        assert module.hello_world() == "Hello, World!"
    print("Test passed")


if __name__ == "__main__":
    do_test()
    do_test()  # This will fail

CPython versions tested on:

3.13

Operating systems tested on:

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directory

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions