J'ai écrit une métaclasse qui compte le nombre d'instances pour chacune de ses classes enfants. Donc ma meta a un dict comme {classname: number_of_instances}.

C'est la mise en œuvre:

class MetaCounter(type):
    
    _counter = {}

    def __new__(cls, name, bases, dict):
        cls._counter[name] = 0
        return super().__new__(cls, name, bases, dict)

    def __call__(cls, *args, **kwargs):
        cls._counter[cls.__name__] += 1
        print(f'Instantiated {cls._counter[cls.__name__]} objects of class {cls}!')
        return super().__call__(*args, **kwargs)



class Foo(metaclass=MetaCounter):
    pass


class Bar(metaclass=MetaCounter):
    pass


x = Foo()
y = Foo()
z = Foo()
a = Bar()
b = Bar()
print(MetaCounter._counter)
# {'Foo': 3, 'Bar': 2} --> OK!
print(Foo._counter)
# {'Foo': 3, 'Bar': 2} --> I'd like it printed just "3"
print(Bar._counter) 
# {'Foo': 3, 'Bar': 2} --> I'd like it printed just "2"

Cela fonctionne bien mais je veux ajouter un niveau de masquage d'informations. Autrement dit, les classes dont la métaclasse est MetaCounter ne devraient pas avoir le compteur d'instances d'autres classes avec la même méta. Ils doivent simplement disposer des informations concernant le nombre de leurs instances. Donc MetaCounter._counter devrait afficher l'intégralité de dict , mais Foo._counter (soit Foo une classe avec MetaCounter comme son < em> metaclass ) doit simplement renvoyer le nombre d'instances Foo .

Existe-t-il un moyen d'y parvenir?

J'ai essayé de remplacer __getattribute__ mais cela a fini par déranger la logique de comptage.

J'ai aussi essayé de mettre _counter comme attribut dans le paramètre dict de la méthode __new__, mais ensuite il est devenu aussi un membre d'instance et je ne le voulais pas.

0
dc_Bita98 15 sept. 2020 à 11:29

2 réponses

Meilleure réponse

Je ne connais pas assez de métaclasses pour dire si c'est une mauvaise idée de faire ce que vous essayez mais c'est possible.

Vous pouvez faire en sorte que MetaCounter ne garde que les références à ses classes "métaclassées" et ajouter un compteur d'instances spécifique à chacune en mettant à jour la méthode dict in MetaCounter.__new__:

class MetaCounter(type):
    _classes = []

    def __new__(cls, name, bases, dict):
        dict.update(_counter=0)
        metaclassed = super().__new__(cls, name, bases, dict)
        cls._classes.append(metaclassed)
        return metaclassed

Chaque fois qu'une nouvelle classe MetaCounter est instanciée, vous incrémentez le compteur

    def __call__(cls, *args, **kwargs):
        cls._counter += 1
        print(f'Instantiated {cls._counter} objects of class {cls}!')
        return super().__call__(*args, **kwargs)

Cela garantit que chaque classe MetaCounter possède son compteur d'instances et ne sait rien des autres classes MetaCounter.

Ensuite, pour obtenir tous les compteurs, vous pouvez ajouter une méthode statique _counters à MetaCounter, qui retourne le compteur pour chaque classe MetaCounter:

    @classmethod
    def _counters(cls):
        return {klass: klass._counter for klass in cls._classes}

Ensuite, vous avez terminé:

print(MetaCounter._counters())  # {<class '__main__.Foo'>: 3, <class '__main__.Bar'>: 2}
print(Foo._counter)  # 3
print(Bar._counter)  # 2
2
Tryph 15 sept. 2020 à 12:56

Ceci est un exemple de travail avec l'utilisation d'un descripteur pour modifier le comportement de l'attribut _counter selon qu'il est accédé par instance (la classe) ou par class (la métaclasse dans ce cas)

class descr:

    def __init__(self):
        self.counter = {}
        self.previous_counter_value = {}

    def __get__(self, instance, cls):
        if instance:
            return self.counter[instance]
        else:
            return self.counter

    def __set__(self, instance, value):
        self.counter[instance] = value
    

class MetaCounter(type):
    
    _counter = descr()

    def __new__(cls, name, bases, dict):
        new_class = super().__new__(cls, name, bases, dict)
        cls._counter[new_class] = 0
        return new_class


    def __call__(cls, *args, **kwargs):
        cls._counter += 1
        print(f'Instantiated {cls._counter} objects of class {cls}!')
        return super().__call__(*args, **kwargs)
0
dc_Bita98 16 sept. 2020 à 08:35