Python 如何创建一个元类,该元类可以为类提供一个实例数组并提供一个;“伏都教”;作用于所有类实例的实例?
我想知道如何在Python中创建一个元类来创建其他类:Python 如何创建一个元类,该元类可以为类提供一个实例数组并提供一个;“伏都教”;作用于所有类实例的实例?,python,class,oop,metaclass,Python,Class,Oop,Metaclass,我想知道如何在Python中创建一个元类来创建其他类: 将它们的实例自动存储在数组中 有一个特殊实例,非MetaClass.all,其属性: 设置时,将具有相同键的所有类实例设置为相同的值(例如,Foo.all.num=3使Foo的所有实例的num为3) 访问(get)时,返回类的所有实例键值的数组(例如,Foo.all.num返回[5,3,2]) 无法删除 调用时(如果属性是函数),对类的所有实例调用该方法 用Python术语来说,我想把一个类变成这样: class Foo(objec
- 将它们的实例自动存储在数组中
- 有一个特殊实例,
,其属性:非MetaClass.all
- 设置时,将具有相同键的所有类实例设置为相同的值(例如,
使Foo.all.num=3
的所有实例的Foo
为3)num
- 访问(get)时,返回类的所有实例键值的数组(例如,
返回Foo.all.num
)[5,3,2]
- 无法删除
- 调用时(如果属性是函数),对类的所有实例调用该方法
- 设置时,将具有相同键的所有类实例设置为相同的值(例如,
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
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]
>>>
为此:
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
并且能够像这样使用它:
class Foo(object):
BAR = 23
def __init__(self):
self.a = 5
def pointless():
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]
>>>
>>Foo()
>>>Foo.instances[0]
>>>Foo()
>>>len(Foo.instances)
2.
>>>Foo.all.a=78
78
>>>Foo.all.a
[78, 78]
>>>Foo.all.change_a()
>>>Foo.all.a
[52, 52]
>>>
元类所需要的唯一东西实际上非常简单:
准确地创建intance
和所有属性
它所要做的就是将它们插入到名称空间中。啊,它还必须包装class\uuuu new\uuuu
方法,以便将新实例插入到实例
列表中
all
中需要的行为部分很有趣,可以使用描述符协议和属性访问控制来实现,因此我们必须创建两个特殊类,在“.”之后请求时返回相应的对象
“All”是将被实例化为“All”的类-它只需要一个\uuuu get\uuuu
方法从AllAttr
类返回另一个特殊对象,该对象已经绑定到父类
“AllAttr”是一个特殊的对象,在任何属性访问中,它都可以对所有者类“instance”属性的成员执行您的要求
“CallAllList”是一个特殊的列表子类,它是可调用的,并依次调用其所有成员。如果所有者类中的必需属性本身是可调用的,则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兼容而编写的,因为在“打印”示例中,您似乎仍然在使用Python 2。
唯一不能与这两种形式兼容的是使用声明本身的元类——如果您使用的是Python 2,只需在Foo类的主体内声明一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。但您不应该真正使用Python2,只要尽快更改为Python3即可
更新
Python2恰好有一个“unbound method”图,而\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
的特殊大小写与Python3的工作方式不同:您不能只将名为\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。为了从超类中获得正确的\uuuuu new\uuuu
方法,最简单的方法是创建一个一次性类,以便可以线性搜索它。否则,必须重新实现MRO算法以获得正确的\uuuu新方法
因此,对于Python 2,元类应该是:
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即可)元类所需要的唯一东西实际上非常简单:
准确地创建intance
和所有属性
它所要做的就是将它们插入到名称空间中。啊,它还必须包装class\uuuu new\uuuu
方法,以便将新实例插入到实例
列表中
all
中需要的行为部分很有趣,可以使用描述符协议和属性访问控制来实现,因此我们必须创建两个特殊类,在“.”之后请求时返回相应的对象
“All”是将被实例化为“All”的类-它只需要一个\uuuu get\uuuu
方法从AllAttr
类返回另一个特殊对象,该对象已经绑定到父类
“AllAttr”是一个特殊的对象,在任何属性访问中,它都可以对所有者类“instance”属性的成员执行您的要求
“CallAllList”是一个特殊的列表子类,它是可调用的,并依次调用其所有成员。如果所有者类中的必需属性本身是可调用的,则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兼容而编写的,因为在“打印”示例中,您似乎仍然在使用Python 2。
唯一不能与这两种形式兼容的是使用声明本身的元类——如果您使用的是Python 2,只需在Foo类的主体内声明一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。但您不应该真正使用Python2,只要尽快更改为Python3即可
更新
Python2恰好有一个“unbound method”图,而\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
的特殊大小写与Python3的工作方式不同:您不能只将名为\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。为了从超类中获得正确的\uuuuu new\uuuu
方法,最简单的方法是创建一个一次性类,以便可以线性搜索它。否则,必须重新实现MRO算法以获得正确的\uuuu新方法
因此,对于Python 2,元类应该是:
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中实现这一点。这是我认为最好的解决办法,尽管它可能不是唯一的办法。它允许您对类的属性进行设置、获取和函数调用。我把这个名字命名为m