如何创建一个元类,它可以为一个类提供一个实例数组并提供一个" voodoo"作用于所有类实例的实例?

时间:2017-11-23 15:37:09

标签: python class oop metaclass

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

  • 自动将实例存储在数组中
  • 有一个特殊的实例NonMetaClass.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

进入这个:

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]
>>> 

2 个答案:

答案 0 :(得分:0)

实际上只需要一个元类就可以了: 准确创建python svneverever <remaining parametersintances属性。

它所要做的就是将它们插入命名空间。啊,它还必须包装类all方法以将新实例插入__new__列表。

instances获取行为的部分很有趣,可以使用描述符协议和属性访问控制来实现,因此我们必须制作一些特殊的类,它们将返回适当的对象在&#34;。&#34;之后提出要求。

&#34;所有&#34;是将被实例化为&#34;所有&#34;的类。 - 它只需要一个all方法从已经绑定到父类的__get__类返回另一个特殊对象。

&#34; AllAttr&#34;是一个特殊的对象,在任何属性访问,对所有者类的成员执行您的要求&#34;实例&#34;属性。

和#34; CallAllList&#34;是一个可调用的特殊列表子类,并依次调用其所有成员。如果所有者类中的必需属性本身是可调用的,则AllAttr使用它。

AllAttr

上面的代码是为了兼容Python 3和Python 2而编写的,因为你似乎仍在使用Python2,因为你的&#34; print&#34;例。 唯一不能与两种形式兼容的东西是使用声明本身的元类 - 如果你使用的是Python 2,只需在你的Foo类体内声明一个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 。但你不应该真正使用Python2,只是尽快更改为Python 3。

更新

事实上,Python 2具有&#34;未绑定的方法&#34;图,__metaclass__ = MetaAll的特殊大小写在Python 3中不起作用:你不能只将一个名为__new__的函数归属于该类。为了从超类中获取正确的__new__方法,最简单的方法是创建一个一次性类,以便可以线性搜索它。否则,必须重新实现MRO算法以获得正确的__new__方法。

因此,对于Python 2,元类应该是这样的:

__new__

(现在,再一次,这件事情很古老。只是满足于Python 3.6)

答案 1 :(得分:0)

好的,我自己想出了如何为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