【问题标题】:Auto instantiation of class after import in Python在 Python 中导入后自动实例化类
【发布时间】:2018-06-12 02:11:04
【问题描述】:

我想在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 python-2.7 metaclass


    【解决方案1】:

    方案一:修改sys.modules

    这是 Python 2 版本。 将类添加到其模块中是可行的,因为它解决了使用super()时类还没有退出的问题:

    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>}
    

    【讨论】:

    • 由于我在回答中解释的原因,您的解决方案 2 将不起作用(即:“超级”的无参数形式仅在元类中的代码完成后才起作用)。第一个方案很聪明,确实可行。
    • @jsbueno 感谢您的评论。但是我的解决方案 2 在MetaTest._instances 中确实得到了两个实例。我在这里错过了什么吗?
    • 也许它适用于 Python 2。我知道它不适用于 Python 3,因为我对另一个复杂的相关问题进行了研究:stackoverflow.com/questions/13126727/…
    • 你是对的。它在 Python 3 中不起作用(用 3.6 测试)。字典是空的。也许这与future 中的super() 的实际实现方式有关。
    • 我实际上在我的脑海中为元类的“UserType”事物编写了一个 PEP,它可以完全控制通过自定义“类型”无法解决的一些进程,并且很少用于证明使所有类实例化更加复杂的合理性。示例是创建__class__ 单元的步骤,以及调用__init_subclass__ 本身的步骤。
    【解决方案2】:

    第一注:

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

    因此,Foo 的类本身的名称显然不会在 class Foo 的主体中可用:该名称仅在类实例化完成后才绑定。

    在 Python 3 中,有 super() 调用的无参数版本,它可以将一个人定向到超类,而无需显式引用类本身。这是该语言对其非常可预测的机制做出的少数例外之一。它在编译时将数据绑定到super 调用。然而,即使在 Python 3 中,也无法在类仍在被实例化时调用使用 super 的方法 - 即在从元类 __new____init__ 方法返回之前。无参super需要的数据在此之后被静默绑定。

    然而,在 Python 3.6+ 中,有一种机制甚至排除了元类:在创建类之后,将在超类上调用名为 __init_subclass__ 的类方法,它可以使正常使用(无参数)超级。这适用于 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 一起可用,它始终引用使用它的主体的类,而不是子类。

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

    现在,如果您真的需要在 Python 2 中使用它,我认为最简单的方法是创建一个函数,该函数将在您的每个模块的页脚处调用,该函数将“注册”最近创建的类。 顺带一提:

    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()
    

    再次声明:对这个“注册”函数的调用应该出现在每个模块的最后一行。

    这里的“元”是内省调用函数本身的框架以自动获取其全局变量。您可以通过将 globals() 设置为 register 函数所需的显式参数来摆脱它,并使代码更易于第三方理解。

    为什么选择 Python 3

    截至今天,Python 2 距离 End of Line 已有 2 年的时间。在过去的 8 年中,该语言发生了很多改进,包括类和元类模型中的特性。我似乎有很多人似乎坚持使用 python 2,因为“python”在他们的 Linux 安装中链接到 Python 2,他们认为这是默认设置。不正确:它只是为了向后兼容 - 越来越多的 Linux 安装使用 python3 作为默认 Python,通常与 Python 2 一起安装。对于您的项目,使用 virtuaelnv 创建的隔离环境会更好。

    【讨论】:

    • 我喜欢那个注册功能的想法。
    • 感谢您的精彩回答。但是我坚持使用 Python 2.7,因为我的代码在嵌入式 Python 中运行(准确地说是在 3dsMax 中)。最初我想做注册器功能,但后来我想做自动实例化,所以我正在创建的框架的用户不需要手动注册。我现在将使用@mike 解决方案,但如果它失败了,你可以使用事件系统来解决这个问题,因为我有整个 Qt 可用。
    【解决方案3】:

    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 的无参数形式,更成功:

    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'>
    

    我必须承认,我对类自动创建自己的实例的整个概念不太满意:“显式优于隐式”。我觉得 jsbueno 提出的使用注册功能的建议是一个很好的折衷方案。

    【讨论】:

      猜你喜欢
      • 2022-06-23
      • 1970-01-01
      • 1970-01-01
      • 2017-08-11
      • 2021-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-18
      相关资源
      最近更新 更多