web-dev-qa-db-fra.com

Comment puis-je accéder à une méthode de classe depuis l'intérieur d'une classe dans Python

Je voudrais créer une classe dans Python qui gère avant tout les membres statiques. Ces membres doivent déjà être initiés lors de la définition de la classe. En raison du fait qu'il sera nécessaire de réinitialiser le membres statiques plus tard, je mettrais ce code dans une méthode de classe.

Ma question: comment puis-je appeler cette méthode de classe depuis l'intérieur de la classe?

class Test():
    # static member
    x = None
    # HERE I WOULD LOVE TO CALL SOMEHOW static_init!

    # initialize static member in classmethod, so that it can be 
    #reinitialized later on again    
    @classmethod
    def static_init(cls):
        cls.x = 10

Toute aide est appréciée!

Merci d'avance, Volker

27
Volker

Au moment où x=10 est exécuté dans votre exemple, non seulement la classe n'existe pas, mais la méthode de classe n'existe pas non plus.

Exécution en Python va de haut en bas. Si x=10 est au-dessus de la méthode de classe, il n'y a aucun moyen d'accéder à la méthode de classe à ce stade, car elle n'a pas encore été définie.

Même si vous pouviez exécuter la méthode de classe, cela n'aurait pas d'importance, car la classe n'existe pas encore, donc la méthode de classe ne pouvait pas s'y référer. La classe n'est créée qu'après l'exécution de l'ensemble du bloc de classe, donc tant que vous êtes à l'intérieur du bloc de classe, il n'y a pas de classe.

Si vous souhaitez factoriser une certaine initialisation de classe afin de pouvoir la réexécuter plus tard de la manière que vous décrivez, utilisez un décorateur de classe. Le décorateur de classe s'exécute après la création de la classe, il peut donc appeler très bien la méthode de classe.

>>> def deco(cls):
...     cls.initStuff()
...     return cls
>>> @deco
... class Foo(object):
...     x = 10
...     
...     @classmethod
...     def initStuff(cls):
...         cls.x = 88
>>> Foo.x
88
>>> Foo.x = 10
>>> Foo.x
10
>>> Foo.initStuff() # reinitialize
>>> Foo.x
88
21
BrenBarn

Vous appelez une méthode de classe en ajoutant le nom de classe de la même manière:

class.method

Dans votre code, quelque chose comme ça devrait suffire:

Test.static_init()

Vous pouvez également faire ceci:

static_init(Test)

Pour l'appeler dans votre classe, demandez à votre code de faire ceci:

Test.static_init()

Mon code de travail:

class Test(object):

    @classmethod
    def static_method(cls):
        print("Hello")

    def another_method(self):
        Test.static_method()

et Test().another_method() renvoie Hello

5
NlightNFotis

Vous ne pouvez pas appeler un classmethod dans la définition class car la classe n'a pas encore été entièrement définie, il n'y a donc rien pour passer la méthode comme premier argument cls. ..un problème classique de poulet et d'oeufs. Cependant, vous pouvez contourner cette limitation en surchargeant la méthode__new__() dans une métaclasse et en appelant la méthode de classe à partir de là après la création de la classe, comme illustré ci-dessous:

class Test(object):
    # nested metaclass definition
    class __metaclass__(type):
        def __new__(mcl, classname, bases, classdict):
            cls = type.__new__(mcl, classname, bases, classdict)  # creates class
            cls.static_init()  # call the classmethod
            return cls

    x = None

    @classmethod
    def static_init(cls):  # called by metaclass when class is defined
        print("Hello")
        cls.x = 10

print Test.x

Production:

Hello
10
3
martineau

Après avoir relu votre question attentivement cette fois, je peux penser à deux solutions. La première consiste à appliquer le motif de conception Borg. La seconde consiste à ignorer la méthode de classe et à utiliser une fonction de niveau module à la place. Cela semble résoudre votre problème:

def _test_static_init(value):
    return value, value * 2

class Test:
    x, y = _test_static_init(20)

if __name__ == "__main__":
    print Test.x, Test.y

Ancienne réponse incorrecte:

Voici un exemple, j'espère que cela aide:

class Test:
    x = None

    @classmethod
    def set_x_class(cls, value):
        Test.x = value

    def set_x_self(self):
        self.__class__.set_x_class(10)

if __name__ == "__main__":
    obj = Test()
    print Test.x
    obj.set_x_self()
    print Test.x
    obj.__class__.set_x_class(15)
    print Test.x

Quoi qu'il en soit, la réponse de NlightNFotis est meilleure: utilisez le nom de classe lors de l'accès aux méthodes de classe. Cela rend votre code moins obscur.

1
ZalewaPL

Cela semble être une solution raisonnable:

from __future__ import annotations
from typing import ClassVar, Dict
import abc
import string


class Cipher(abc.ABC):
    @abc.abstractmethod
    def encrypt(self, plaintext: str) -> str:
        pass

    @abc.abstractmethod
    def decrypt(self, ciphertext: str) -> str:
        pass


class RotateCipher(Cipher, abc.ABC):
    @staticmethod
    def rotate(n: int) -> str:
        return string.ascii_uppercase[n:] + string.ascii_uppercase[:n]


class VigenereCipher(RotateCipher):
    _TABLE: ClassVar[Dict[str, str]] = dict({(chr(i + ord("A")), RotateCipher.rotate(i)) for i in range(26)})

    def encrypt(self, plaintext: str) -> str:
        pass

    def decrypt(self, plaintext: str) -> str:
        pass


vc = VigenereCipher()

La méthode est maintenant une méthode statique du chiffrement, rien en dehors des classes n'est référencé. Vous pouvez choisir de nommer RotateCipher_RotateCipher à la place, si vous ne voulez pas que les gens l'utilisent seul.

Remarque: j'ai supprimé le Final, car je l'ai exécuté sur 3.7, mais après avoir lu la documentation sur Final, je ne pense pas que cela affecterait la solution? Ajout également d'une importation pour string dont la question était manquante. Et enfin ajouté une implémentation pour les méthodes abstraites, alternativement, aurait pu laisser VigenereCipher hériter de abc.ABC ainsi que.

0
Grismar