我不明白以下代码的行为。有人可以帮助我吗?

时间:2018-02-12 17:40:09

标签: python metaclass

class A(type):
    _l = []
    def __call__(cls,*args,**kwargs):
        if len(cls._l)<=5:
            cls._l.append(super().__call__(*args,**kwargs))
        else:
            return cls._l

class B(metaclass=A):
    pass


#testing starts

a=B()
a1=B()

assert a is a1

上述陈述成功。但根据我的理解,它不应该成功。因为同一个类的两个实例应共享不同的内存位置。

任何建议都表示赞赏。

1 个答案:

答案 0 :(得分:0)

在某些地方,它似乎是一个破碎的,试图为给定的类实现5个实例的限制,就像单例是给定类的一个实例的限制一样。

此外,测试也被打破了。

问题在于,每当你尝试在Python中实例化一个类时,它与调用任何其他对象没什么不同:__call__方法在类的类中运行 - 也就是说,它的元类。通常,元类的__call__负责打电话给班级&#39; __new____init__方法 - 在上面的示例中调用super().__call__时完成。然后,__call__返回的值将用作新实例 - 并且上面可能存在最严重的错误:在前5次A.__call__次运行中,它返回None

所以None分配给aa1,而None是单身 - 这就是你的断言有效的原因。

现在,如果要修复它并在len(l) < 5时返回新创建的实例,那将限制具有A作为元类的所有类的实例总数,而不仅仅是五个实例B。那是因为要按类进行工作,_l属性应该位于类本身 - 上面设置代码的方式,它将从类的类中检索 - 这是A._l - (除非在B和其他类中明确创建_l属性)。

最后,没有机制可以在上面的代码中提供有限创建实例的循环,这是人们可以想象的可以有一些实用的东西 - 而不是选择一个实例,列表与所有创建的实例都以raw格式返回。

所以,在发布这个版本的工作版之前,让我们直截了当地说:你问

  

同一个类的两个实例应该共享不同的内存   位置。

是的 - 但是实际创造两种不同的意义的是type.__call__A.__call__每次都不会调用它,并且它将返回的任何对象都将用于代替新实例。如果它返回一个预先存在的对象,那就是调用者代码获得的对象。

重新编写它是毫无疑问的:实例化类与调用Python中的任何其他对象没有什么不同:运行关联对象的__call__并返回一个值。

现在,如果想要限制类的实例,并平衡正在检索的实例,那么元类代码可能是这样:

MAX_INSTANCES = 5

class A(type):

    def __call__(cls, *args, **kw):
        cls._instances = instances = getattr(cls, "_instances", [])
        cls._index = getattr(cls, "_index", -1)
        if len(instances) < MAX_INSTANCES:
            # Actually creates a new instance:
            instance = super().__call__(cls, *args, **kw)
            instances.append(instance)
        cls._index = (cls._index + 1) % MAX_INSTANCES
        return cls._instances[cls._index]


class B(metaclass=A):
    pass

b_list = [B() for B in range(MAX_INSTANCES * 2)]

for i in range(MAX_INSTANCES):
    assert b_list[i] is b_list[i + MAX_INSTANCES]
    for j in range(1, MAX_INSTANCES):
        assert b_list[i] is not b_list[i + j]