4

Here's the minimal reproduction for something I'm working on. This is using Python 3.6.5:

sample.py:

import importlib.util
import inspect

from test import Test

t = Test()

spec = importlib.util.spec_from_file_location('test', './test.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

loaded_test = None
for name, obj in inspect.getmembers(module):
    if inspect.isclass(obj):
        loaded_test = obj

print(type(t))
print(loaded_test)
print(isinstance(t, loaded_test))
print(issubclass(t.__class__, loaded_test))

test.py (in the same directory):

class Test(object):
    pass

Running this code will give you the following output:

<class 'test.Test'>
<class 'test.Test'>
False
False

So why is the object that we load using importlib, which is identified as 'test.Test', not an instance or subclass of the 'test.Test' class I created using import? Is there a way to programmatically check if they're the same class, or is it impossible because the context of their instantiation is different?

Daniel Drexler
  • 527
  • 4
  • 14
  • 1
    Related (maybe duplicate): [Are classobjects singletons?](https://stackoverflow.com/q/33924950/674039) – wim Apr 15 '18 at 03:18
  • 1
    A clue: `import test` / `print(module, test, module == test)`. Apparently the classes are different because they live in different modules. – Robᵩ Apr 15 '18 at 03:27
  • Because despite the fact that Python goes to great lengths to ensure that casually used classes and modules are effectively singletons, it still allows you the freedom to do almost anything you want. – Mad Physicist Apr 15 '18 at 03:51

1 Answers1

2

Why is the object that we load using importlib, which is identified as test.Test, not an instance or subclass of the test.Test class I created using import?

A class is "just" an instance of a metaclass. The import system generally prevents class objects from being instantiated more than once: classes are usually defined at a module scope, and if a module has already been imported the existing module is just reused for subsequent import statements. So, different references to the same class all end up pointing to an identical class object living at the same memory location.

By using exec_module you prevented this "cache hit" in sys.modules, forcing the class declaration to be executed again, and a new class object to be created in memory.

issubclass is not doing anything clever like a deep inspection of the class source code, it's more or less just looking for identity (CPython's implementation here, with a fast-track for exact match and some complications for supporting ABCs)

Is there a way to programmatically check if they're the same class, or is it impossible because the context of their instantiation is different?

They are not the same class. Although the source code is identical, they exist in different memory locations. You don't need the complications of exec_module to see this, by the way, there are simpler ways to force recreation of the "same" class:

>>> import test
>>> t = test.Test()
>>> isinstance(t, test.Test)
True
>>> del sys.modules['test']
>>> import test
>>> isinstance(t, test.Test)
False

Or, define the class in a function block and return it from the function call. Or, create classes from the same source code by using the three-argument version of type(name, bases, dict). The isinstance check (CPython implementation here) is simple and will not detect these misdirections.

wim
  • 338,267
  • 99
  • 616
  • 750