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

Posted

技术标签:

【中文标题】在 Python 中导入后自动实例化类【英文标题】:Auto instantiation of class after import in 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

有没有办法做到这一点?

【问题讨论】:

【参考方案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,因为我对另一个复杂的相关问题进行了研究:***.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 提出的使用注册功能的建议是一个很好的折衷方案。

【讨论】:

以上是关于在 Python 中导入后自动实例化类的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中实例化类的区别

在实例化类之前在 Python 类中定义的方法的正确术语是啥?

Python 在类定义中实例化类

python如何用字符串实例化类

python实例化类,数据混乱串内容问题(可变类型属性的初始化)

有没有一种用构造函数参数实例化类的捷径? [复制]