在 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'>
但是,如果您尝试提供 Foo
或 Bar
__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 类中定义的方法的正确术语是啥?