简体   繁体   中英

I don't understand the behavior of this below code. Can someone help me here

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

the above statement succeeds.But as per my understanding it should not succeed. as two instances of the same class should share different memory locations.

Any suggestions are appreciated.

It appears to be a broken, in several places, attempt to implementa limit of 5 instances for a given class, just like a singleton is a limit of one instance for a given class.

Moreover, the testing is broken as well.

The thing is that whenever you try to instantiate a class in Python, it is no different than calling any other object: the __call__ method is run in the class's class - that is, its metaclass. Ordinarily, the metaclass's __call__ is responsible for calling the class' __new__ and __init__ method - and that is done when super().__call__ is invoked in the example above. The value returned by __call__ is then used as the new instance - and there is what is perhaps the most gross error above: in the first 5 times A.__call__ runs, it returns None .

So None is assigned both to a and to a1 , and None is a singleton - that is why your assertion works.

Now, if one is to fix that and return the newly created instance whenever len(l) < 5 , that would restrict the total number of instances for all classes that have A as a metaclass, not just five instances of B . Thatis because to work per class, the _l attribute should be located in the class themselves - the way the code is set above, it will be retrieved from the class'class - which is A._l - (unless one explicitly creates a _l attribute in B and other classes) .

And finally, there is no mechanism to provide a round-robin of the limited created instances on the code above, which is what one can imagine as a thing that could have some utility - instead of selecting an instance, the list with all created instances is returned in raw.

So, before posting a working version of this, let's get one thing straight: you ask

two instances of the same class should share different memory locations.

Yes - but what actually creates the two different isntances is type.__call__ . The A.__call__ there won't be calling this everytime, and whatever object it returns will be used in place of the new instance. If it returns a pre-existing object, that is what the caller code gets.

Rewording it so there are no doubts: instantiating classes is no different than calling any other object in Python: the associated object's __call__ is run and returns a value.

Now, if the idea is to have a limit of instances of classes, and balance the instances being retrieved, the metaclass code could be so:

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]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM