在 Python 中导入后自动实例化 class

Auto instantiation of class after import in Python

我想在 Python 中的某些 类 模块导入后自动实例化,因此无需直接实例化调用。 我的设置可以简化为以下代码:

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        super(Foo, self).__init__()

class Bar(Foo):

    def __init__(self):
        super(Bar, self).__init__()

但是 super 上的实例化失败了:

    super(Foo, self).__init__()
NameError: global name 'Foo' is not defined

请问有什么办法吗?

第一个注释:

对于这种代码,使用 Python 3. 关于第一个解决方案和答案末尾的更多信息。

因此,class 本身的名称 Foo 显然不会在 class Foo 的主体内可用:此名称仅在 [=72 之后绑定=] 实例化完成。

在Python 3中,有super()调用的无参数版本,它可以将一个指向superclass而不需要显式引用[=72] =] 本身。这是该语言对其非常可预测的机制做出的少数例外之一。它在编译时将数据绑定到 super 调用。但是,即使在 Python 3 中,也无法调用使用 super 的方法,而 class 仍在被实例化 - 即在 returning 从元class __new____init__ 方法。无参super需要的数据之后默默绑定

然而,在 Python 3.6+ 中有一种机制甚至排除了 metaclass:一个名为 __init_subclass__ 的 class 方法将在 superclasses after 创建了一个 class,它可以正常使用(无参数)super。这适用于 Python 3.6:

class Base:

   _instances = {}

   def __init_subclass__(cls):
        __class__._instances[cls.__name__] = cls()

   ...

class Foo(Base):

    def __init__(self):
        super().__init__()

在这里,__class__(不是 obj.__class__)是另一个神奇的名字,与无参数 super 一起可用,它总是引用它在其中使用的主体 class , 永远不会是 subclass。

现在,关于其他方法的几个注意事项:metaclass 本身中的代码无法实例化 class,并且 class 方法需要引用其名称中的 class 无济于事。 class 装饰器也不起作用,因为名称仅绑定在装饰器的 return 之后。如果你在一个有事件循环的框架内编写你的系统,你可以在 metaclass __init__ 上安排一个事件来创建一个 class 实例 - 这会起作用,但是你需要在这样的上下文中进行编码(gobject、Qt、django、sqlalchemy、zope/pyramid、Python asyncIO/tulip 是具有可以进行所需回调的事件系统的框架示例)

现在,如果您在 Python 2 中真的需要这个,我认为最简单的方法是在每个模块的页脚处创建一个函数,该函数将 "register" 最近创建了 classes。 一些东西:

class MetaTest(type):

    _instances = {}

def register():
    import sys
    module_namespace = sys._getframe(1).f_globals

    for name, obj in f_globals.items():
        if isinstance(obj, MetaTest):
            MetaTest._instances[obj.__name__] = obj()

再次:对这个 "register" 函数的调用应该出现在每个模块的最后一行。

这里的 "meta" 是内省调用函数本身的框架以自动获取其全局变量。您可以通过使 globals() 成为 register 函数的必需显式参数来摆脱它,并使代码更容易被第三方理解。

为什么Python3

截至今天,Python 2 距离行尾还有 2 年。在过去的 8 年里,该语言发生了很多改进,包括 class 和 metaclass 模型中的功能。我似乎很多人似乎坚持使用 python 2,因为在他们的 Linux 安装中 "python" 链接到 Python 2,他们认为这是默认设置。不正确:它只是为了向后兼容 - 越来越多的 Linux 安装使用 python3 作为默认 Python,通常与 Python 一起安装 2. 对于你的项目, 无论如何,最好使用由 virtuaelnv 创建的隔离环境。

jsbueno 说的。我一直在尝试在 Python 2 中完成这项工作,但收效甚微。这是我得到的最接近的:

class MetaTest(type):
    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()

class Foo(object):
    __metaclass__ = MetaTest

class Bar(Foo):
    pass

for name, obj in Bar._instances.items():
    print name, obj, type(obj)

输出

Foo <__main__.Foo object at 0xb74626ec> <class '__main__.Foo'>
Bar <__main__.Bar object at 0xb746272c> <class '__main__.Bar'>

但是,如果您尝试提供 FooBar __init__ 方法,那么由于 jsbueno 给出的原因,它都会崩溃。

这是一个 Python 3.6 版本,使用 super 的 argless 形式更成功:

class MetaTest(type):
    _instances = {}

    def __init__(cls, name, bases, attrs):
        print('cls: {}\nname: {}\nbases: {}\nattrs: {}\n'.format(cls, name, bases, attrs))
        super().__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()

class Foo(metaclass=MetaTest):
    def __init__(self):
        super().__init__()

class Bar(Foo):
    def __init__(self):
        super().__init__()

for name, obj in Bar._instances.items():
    print(name, obj, type(obj))

输出

cls: <class '__main__.Foo'>
name: Foo
bases: ()
attrs: {'__module__': '__main__', '__qualname__': 'Foo', '__init__': <function Foo.__init__ at 0xb7192dac>, '__classcell__': <cell at 0xb718c2b4: MetaTest object at 0xb718d3cc>}

cls: <class '__main__.Bar'>
name: Bar
bases: (<class '__main__.Foo'>,)
attrs: {'__module__': '__main__', '__qualname__': 'Bar', '__init__': <function Bar.__init__ at 0xb7192d64>, '__classcell__': <cell at 0xb718c2fc: MetaTest object at 0xb718d59c>}

Foo <__main__.Foo object at 0xb712514c> <class '__main__.Foo'>
Bar <__main__.Bar object at 0xb71251ac> <class '__main__.Bar'>

我必须承认,我对 类 自动创建自己实例的整个概念不太满意:"Explicit is better than implicit"。我认为jsbueno建议使用注册功能是一个很好的折衷方案。

解决方案 1:修改 sys.modules

这是 Python2 版本。 将 class 添加到它的模块中是可行的,因为它解决了当您使用 super():

时 class 还没有退出的问题
import sys

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        setattr(sys.modules[cls.__module__], cls.__name__, cls)
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        print('init Foo')
        super(Foo, self).__init__()

class Bar(Foo):

    def __init__(self):
        print('init Bar')
        super(Bar, self).__init__()

print(MetaTest._instances)

输出:

init Foo
init Bar
init Foo
{'Foo': <__main__.Foo object at 0x10cef90d0>, 'Bar': <__main__.Bar object at 0x10cef9110>}

解决方案 2:使用 future

future 库为 Python 2 提供了 super(),它的工作方式与 Python 3 的一样,即没有参数。 这有助于:

from builtins import super

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        print('init Foo')
        super().__init__()

class Bar(Foo):

    def __init__(self):
        print('init Bar')
        super().__init__()

print(MetaTest._instances)

输出:

init Foo
init Bar
init Foo
{'Foo': <__main__.Foo object at 0x1066b39d0>, 'Bar': <__main__.Bar object at 0x1066b3b50>}