satwikkansal / wtfpython

What the f*ck Python? 😱
Do What The F*ck You Want To Public License
35.7k stars 2.65k forks source link

A f**king problem with sys.path #323

Open R4v3nl0 opened 9 months ago

R4v3nl0 commented 9 months ago

Problem

If you import some file or object with different path, it will have different id.. Look like this

The directory tree:

.
├── hole
│   ├── __init__.py
│   ├── base.py
│   └── caller.py
└── main.py

hole/init.py

import sys
from pathlib import Path

# allow imported as third-party package
__PATH = str(Path(__file__).parent)
if __PATH not in sys.path:
    sys.path.append(__PATH)

hole/base.py

class Base:
    shared = []

    @staticmethod
    def add(x):
        Base.shared.append(x)

hole/caller.py

from base import Base

def caller():
    Base.add(1)
    print(Base.shared)

main.py

from hole import caller
from hole.base import Base

caller.caller()
print(Base.shared)

After run command python3 main.py, u will get the output:

[1]
[]

Why????

Because sys.path causes them to have different modules, different objects are used.

We can clearly observe this distinction with Base.__dict__

Add print(Base.__dict__) after print(Base.shared) in main.py and hole/caller.py

The output:

[1]
{'__module__': 'base', 'shared': [1], 'add': <staticmethod(<function Base.add at 0x1007fd900>)>, '__dict__': <attribute '__dict__' of 'Base' objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>, '__doc__': None}
[]
{'__module__': 'hole.base', 'shared': [], 'add': <staticmethod(<function Base.add at 0x1007fda20>)>, '__dict__': <attribute '__dict__' of 'Base' objects>, '__weakref__': <attribute '__weakref__' of 'Base' objects>, '__doc__': None}

Okay, We can see that the call in hole/caller.py outputs '__module__': 'base' and the call in main.py outputs '__module__': 'hole.base'

How to fix it?

If we need to use the class attribute in different files and want they use the same object, you may have these two options

Attention: If there is no interference from sys.path, you don't need to care

Option 1

Add a function to return the Base class in a python file which in hole directory and uses from base import Base

Like add get_base in hole/caller.py

from base import Base

def caller():
    Base.add(1)
    print(Base.shared)
    print(Base.__dict__)

def get_base():
    return Base

And when you want to use the class attribute in Base, first call get_base to get the base object which __module__ is base, and then call base.xxx

Option 2

Import Base like you did in caller.py

Attention: Pylance will report missing imports, lmao

main.py

from hole import caller
from base import Base

caller.caller()
print(Base.shared)
print(Base.__dict__)