如何创建一个可以给 class 实例数组并提供作用于所有 class 实例的 "voodoo" 实例的 metaclass?
How to create a metaclass that can give a class an array of instances and provide a "voodoo" instance that acts on all class instances?
我想知道如何在 Python 中创建一个可以创建其他 class 的元class:
- 自动将它们的实例存储在数组中
- 有一个特殊实例,
NonMetaClass.all
,其属性:
- 设置后,将具有相同键的所有 class 实例设置为相同值(例如,
Foo.all.num = 3
使 Foo
的所有实例都具有 num
共 3)
- 访问 (get) 时,returns 所有 class 实例键值的数组(例如,
Foo.all.num
returns [5, 3, 2]
)
- 无法删除。
- 调用时(如果属性是函数),在 class.
的所有实例上调用该方法
用 Python 来说,我想转一个 class 是这样的:
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
print 'pointless.'
def change_a(self):
self.a = 52
进入这个:
class Foo(object):
BAR = 23
instances = []
all = # Some black magic to create the special "all" instance
def __init__(self):
self.a = 5
Foo.instances.append(self)
def pointless(self):
print 'pointless.'
def change_a(self):
self.a = 52
并且能够像这样使用它:
>>> Foo()
>>> Foo.instances[0]
<__main__.Foo instance at 0x102ff5758>
>>> Foo()
>>> len(Foo.instances)
2
>>> Foo.all.a = 78
78
>>> Foo.all.a
[78, 78]
>>> Foo.all.change_a()
>>> Foo.all.a
[52, 52]
>>>
唯一需要 metaclass 的地方其实很简单:
准确创建 intances
和 all
属性。
它所要做的就是将它们插入命名空间。啊,它还必须包装 class __new__
方法以将新实例插入 instances
列表。
all
想要的行为部分很有趣,可以使用描述符协议和属性访问控制来实现,因此我们必须制作一些特殊的 classes ,当在“.”之后请求时,将 return 适当的对象。
"All" 是将被实例化为 "all" 的 class - 它只需要一个 __get__
方法到 return 另一个特殊对象,从AllAttr
class,已经绑定到父级 class。
"AllAttr" 是一个特殊对象,在任何属性访问时,对所有者 class "instance" 属性的成员执行您的要求。
而"CallAllList"是一个可调用的特殊列表subclass,依次调用其所有成员。如果所有者 class 的必需属性本身是可调用的,则 AllAttr 使用它。
class CallAllList(list):
def __call__(self, *args, **kwargs):
return [instance(*args, **kwargs) for instance in self]
class AllAttr(object):
def __init__(self, owner):
self._owner = owner
def __getattr__(self, attr):
method = getattr(self._owner, attr, None)
cls = CallAllList if callable(method) else list
return cls(getattr(obj, attr) for obj in self._owner.instances)
def __setattr__(self, attr, value):
if attr == "_owner":
return super(AllAttr, self).__setattr__(attr, value)
for obj in self._owner.instances:
setattr(obj, attr, value)
class All(object):
def __get__(self, instance, owner):
return AllAttr(owner)
def __repr__(self):
return "Representation of all instances of '{}'".format(self.__class__.__name__)
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
original_new = getattr(cls, "__new__")
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
cls.__new__ = __new__
return cls
class Foo(metaclass=MetaAll):
pass
上面的代码是 Python 3 和 Python 2 兼容的,因为在你的 "print" 示例中你似乎仍在使用 Python2 .
唯一不能与这两种形式兼容的是 metaclass using 声明本身 - 如果您使用 [=53=,只需在 Foo class 的主体内声明一个 __metaclass__ = MetaAll
] 2. 但你不应该真的使用 Python2,只要你能尽快改成 Python 3。
更新
碰巧 Python 2 有 "unbound method" 数字,__new__
的特殊外壳不像 Python 3 那样工作:你不能只是将名为 __new__
的函数赋予 class。为了从superclasses中得到正确的__new__
方法,最简单的方法是创建一个一次性的class,这样就可以线性查找了。否则,必须重新实现 MRO 算法才能获得正确的 __new__
方法。
所以,对于 Python 2,metaclass 应该是这样的:
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
if "__new__" in namespace:
original_new = namespace["__new__"]
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
else:
# We create a disposable class just to get the '__mro__'
stub_cls = super(MetaAll, metacls).__new__(metacls, name, bases, {})
for parent in stub_cls.__mro__[1:]:
if "__new__" in parent.__dict__:
original_new = parent.__dict__["__new__"]
break
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
namespace["__new__"] = __new__
final_cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
return final_cls
class Foo(object):
__metaclass__ = MetaAll
(再一次,这东西很古老。只需满足于 Python 3.6)
好的,我自己想出了如何为 Python 2.7 执行此操作。这是我认为最好的解决方案,尽管它可能不是唯一的解决方案。它允许您对 Class.all
的属性进行设置、获取和函数调用。我已将元类命名为 InstanceUnifier
,但如果您认为有更好(更短、更具描述性)的名称,请发表评论。
class InstanceUnifier(type):
'''
What we want: A metaclass that can give a class an array of instances and provide a static Class.all object, that, when a method is called on it, calls the same method on every instance of the class.
'''
def __new__(cls, name, base_classes, dct):
dct['all'] = None
dct['instances'] = []
return type.__new__(cls, name, base_classes, dct)
def __init__(cls, name, base_classes, dct):
class Accessor(object):
def __getattribute__(self, name):
array = [getattr(inst, name) for inst in cls.instances]
if all([callable(item) for item in array]):
def proxy_func(*args, **kwargs):
for i in range(len(cls.instances)):
this = cls.instances[i]
func = array[i]
func(*args, **kwargs)
return proxy_func
elif all([not callable(item) for item in array]):
return array
else:
raise RuntimeError('Some objects in class instance array for key "'+name+'" are callable, some are not.')
def __setattr__(self, name, value):
[setattr(inst, name, value) for inst in cls.instances]
def __delattr__(self, name):
[delattr(inst, name) for inst in cls.instances]
cls.all = Accessor()
return type.__init__(cls, name, base_classes, dct)
def __call__(cls, *args, **kwargs):
inst = type.__call__(cls, *args, **kwargs)
cls.instances.append(inst)
return inst
我想知道如何在 Python 中创建一个可以创建其他 class 的元class:
- 自动将它们的实例存储在数组中
- 有一个特殊实例,
NonMetaClass.all
,其属性:- 设置后,将具有相同键的所有 class 实例设置为相同值(例如,
Foo.all.num = 3
使Foo
的所有实例都具有num
共 3) - 访问 (get) 时,returns 所有 class 实例键值的数组(例如,
Foo.all.num
returns[5, 3, 2]
) - 无法删除。
- 调用时(如果属性是函数),在 class. 的所有实例上调用该方法
- 设置后,将具有相同键的所有 class 实例设置为相同值(例如,
用 Python 来说,我想转一个 class 是这样的:
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
print 'pointless.'
def change_a(self):
self.a = 52
进入这个:
class Foo(object):
BAR = 23
instances = []
all = # Some black magic to create the special "all" instance
def __init__(self):
self.a = 5
Foo.instances.append(self)
def pointless(self):
print 'pointless.'
def change_a(self):
self.a = 52
并且能够像这样使用它:
>>> Foo()
>>> Foo.instances[0]
<__main__.Foo instance at 0x102ff5758>
>>> Foo()
>>> len(Foo.instances)
2
>>> Foo.all.a = 78
78
>>> Foo.all.a
[78, 78]
>>> Foo.all.change_a()
>>> Foo.all.a
[52, 52]
>>>
唯一需要 metaclass 的地方其实很简单:
准确创建 intances
和 all
属性。
它所要做的就是将它们插入命名空间。啊,它还必须包装 class __new__
方法以将新实例插入 instances
列表。
all
想要的行为部分很有趣,可以使用描述符协议和属性访问控制来实现,因此我们必须制作一些特殊的 classes ,当在“.”之后请求时,将 return 适当的对象。
"All" 是将被实例化为 "all" 的 class - 它只需要一个 __get__
方法到 return 另一个特殊对象,从AllAttr
class,已经绑定到父级 class。
"AllAttr" 是一个特殊对象,在任何属性访问时,对所有者 class "instance" 属性的成员执行您的要求。
而"CallAllList"是一个可调用的特殊列表subclass,依次调用其所有成员。如果所有者 class 的必需属性本身是可调用的,则 AllAttr 使用它。
class CallAllList(list):
def __call__(self, *args, **kwargs):
return [instance(*args, **kwargs) for instance in self]
class AllAttr(object):
def __init__(self, owner):
self._owner = owner
def __getattr__(self, attr):
method = getattr(self._owner, attr, None)
cls = CallAllList if callable(method) else list
return cls(getattr(obj, attr) for obj in self._owner.instances)
def __setattr__(self, attr, value):
if attr == "_owner":
return super(AllAttr, self).__setattr__(attr, value)
for obj in self._owner.instances:
setattr(obj, attr, value)
class All(object):
def __get__(self, instance, owner):
return AllAttr(owner)
def __repr__(self):
return "Representation of all instances of '{}'".format(self.__class__.__name__)
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
original_new = getattr(cls, "__new__")
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
cls.__new__ = __new__
return cls
class Foo(metaclass=MetaAll):
pass
上面的代码是 Python 3 和 Python 2 兼容的,因为在你的 "print" 示例中你似乎仍在使用 Python2 .
唯一不能与这两种形式兼容的是 metaclass using 声明本身 - 如果您使用 [=53=,只需在 Foo class 的主体内声明一个 __metaclass__ = MetaAll
] 2. 但你不应该真的使用 Python2,只要你能尽快改成 Python 3。
更新
碰巧 Python 2 有 "unbound method" 数字,__new__
的特殊外壳不像 Python 3 那样工作:你不能只是将名为 __new__
的函数赋予 class。为了从superclasses中得到正确的__new__
方法,最简单的方法是创建一个一次性的class,这样就可以线性查找了。否则,必须重新实现 MRO 算法才能获得正确的 __new__
方法。
所以,对于 Python 2,metaclass 应该是这样的:
class MetaAll(type):
def __new__(metacls, name, bases, namespace):
namespace["all"] = All()
namespace["instances"] = []
if "__new__" in namespace:
original_new = namespace["__new__"]
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
else:
# We create a disposable class just to get the '__mro__'
stub_cls = super(MetaAll, metacls).__new__(metacls, name, bases, {})
for parent in stub_cls.__mro__[1:]:
if "__new__" in parent.__dict__:
original_new = parent.__dict__["__new__"]
break
def __new__(cls, *args, **kwargs):
instance = original_new(cls, *args, **kwargs)
cls.instances.append(instance)
return instance
namespace["__new__"] = __new__
final_cls = super(MetaAll, metacls).__new__(metacls, name, bases, namespace)
return final_cls
class Foo(object):
__metaclass__ = MetaAll
(再一次,这东西很古老。只需满足于 Python 3.6)
好的,我自己想出了如何为 Python 2.7 执行此操作。这是我认为最好的解决方案,尽管它可能不是唯一的解决方案。它允许您对 Class.all
的属性进行设置、获取和函数调用。我已将元类命名为 InstanceUnifier
,但如果您认为有更好(更短、更具描述性)的名称,请发表评论。
class InstanceUnifier(type):
'''
What we want: A metaclass that can give a class an array of instances and provide a static Class.all object, that, when a method is called on it, calls the same method on every instance of the class.
'''
def __new__(cls, name, base_classes, dct):
dct['all'] = None
dct['instances'] = []
return type.__new__(cls, name, base_classes, dct)
def __init__(cls, name, base_classes, dct):
class Accessor(object):
def __getattribute__(self, name):
array = [getattr(inst, name) for inst in cls.instances]
if all([callable(item) for item in array]):
def proxy_func(*args, **kwargs):
for i in range(len(cls.instances)):
this = cls.instances[i]
func = array[i]
func(*args, **kwargs)
return proxy_func
elif all([not callable(item) for item in array]):
return array
else:
raise RuntimeError('Some objects in class instance array for key "'+name+'" are callable, some are not.')
def __setattr__(self, name, value):
[setattr(inst, name, value) for inst in cls.instances]
def __delattr__(self, name):
[delattr(inst, name) for inst in cls.instances]
cls.all = Accessor()
return type.__init__(cls, name, base_classes, dct)
def __call__(cls, *args, **kwargs):
inst = type.__call__(cls, *args, **kwargs)
cls.instances.append(inst)
return inst