Python 3 isinstance unexpected behavior when impor

2019-07-20 05:26发布

I am trying to import a class from one file and check if is an instance of that class in the file that it was defined in. The problem is that instead of returning True from theisinstance() function, it returns False, because it was initialised in a different file.

Here is a working example.

Say you have file1.py:

class Foo:
    def __init__(self, arg1):
        self.arg1 = arg1

def main(class_obj):
    # Prints false and is type <class 'file1.Foo'>
    print(type(class_obj))
    print(isinstance(class_obj, Foo))

if __name__ == '__main__':
    from file2 import get_class
    main(get_class())

And file2.py:

from file1 import Foo

def get_class():
    foo = Foo("argument")
    return foo

It prints False and the type is <class 'file1.Foo'>. What I found interesting is that if you initialize the Foo class in file1 where is was defined, it returns True.

# Add to main() function in file1
# Returns true and is type <class '__main__.Foo'>
foo_local = Foo("argument")  # Class initiated in __main__ seems to work
print(type(foo_local))
print(isinstance(foo_local, Foo))

I found that if you initiate a class outside of the file where it is defined, it is a different "class" than if you initiate the class inside the file where it was defined.

# Different Classes?
<class 'file1.Foo'>  # From other file (`file2.py`)
<class '__main__.Foo'>  # From same file (`file1.py`)

So my question is:

How can I work around this so that even the classes initiated outside of the file1 can return True on the isinstance() function? To reword it, how can I make it so that the Foo class is the "same" in file1.py and file2.py? I Python 3.6.7 if it matters.

1条回答
干净又极端
2楼-- · 2019-07-20 05:45

The starkly simple answer is to never use if __name__=="__main__". It’s a clever trick, to be sure, but it doesn’t do what anyone thinks it does. It’s supposed to make a file be a module and a script, but (because the processes for finding and running modules and scripts are so different) what it actually does is let the file be a module or a script, separately. The trick contains a hint as to this shortcoming: __name__ in a module is supposed to be its key in sys.modules, and if it’s “__main__” that’s not any normal module at all. (It is in fact possible to import __main__ and get a module object whose attributes are the global variables in the script!)

In your case, where file1.py gets used once as a script and then, via the module file2, as a module, it is actually loaded twice. Each load creates an unrelated (if similar) class Foo; it doesn’t matter where each class is used, just which one. (file1 could even import itself and would get the “module version”.) Note that the classes need not be “the same”; the very same if trick could be used to give them different members or base classes, or could even control which class Foo statement is executed.

If you want to use python -m, which is a perfectly reasonable desire for installation reasons, the least broken way of using it is via a __main__.py in a package otherwise used via import. It’s still possible to import it, which probably doesn’t do anything good, but no one (aside from naïve code that recursively imports every module in a package) will do so.

查看更多
登录 后发表回答