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

我想知道如何在Python中创建一个元类来创建其他类:

  • 将它们的实例自动存储在数组中
  • 有一个特殊实例,
    非MetaClass.all
    ,其属性:
    • 设置时,将具有相同键的所有类实例设置为相同的值(例如,
      Foo.all.num=3
      使
      Foo
      的所有实例的
      num
      为3)
    • 访问(get)时,返回类的所有实例键值的数组(例如,
      Foo.all.num
      返回
      [5,3,2]
    • 无法删除
    • 调用时(如果属性是函数),对类的所有实例调用该方法
用Python术语来说,我想把一个类变成这样:

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