在 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'>
但是,如果您尝试提供 Foo
或 Bar
__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>}
我想在 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'>
但是,如果您尝试提供 Foo
或 Bar
__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()
:
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>}