Disons que j'ai cette classe:

class Foo:
    @classmethod
    def some_decorator(cls, ...):
        ...

Et puis je crée une sous-classe qui utilise le décorateur de classe parent:

class Bar(Foo):
    @Foo.some_decorator(...)
    def some_function(...)
        ...

Comment supprimer la nécessité de Foo. avant le nom du décorateur? Le code ci-dessous ne fonctionne pas:

class Bar(Foo):
    @some_decorator(...)
    def some_function(...)
        ...

Je pense que cela est possible, car la bibliothèque sly fait cela.

Voir leur exemple:

from sly import Lexer, Parser

class CalcLexer(Lexer):
    ...

    @_(r'\d+')
    def NUMBER(self, t):
        t.value = int(t.value)
        return t

    ...

Comme vous pouvez le voir, vous pouvez taper @_(...) au lieu de @Lexer._(...).

Comment font-ils cela?

2
David Callanan 15 mars 2019 à 23:56

2 réponses

Meilleure réponse

Cela se fait avec une metaclass qui implémente une __prepare__ méthode. Extrait de la doc :

3.3.3.4. Préparation de l'espace de noms de classe

Une fois la métaclasse appropriée identifiée, la classe l'espace de noms est préparé. Si la métaclasse a un attribut __prepare__, il s'appelle namespace = metaclass.__prepare__(name, bases, **kwds) (où les arguments de mot-clé supplémentaires, le cas échéant, proviennent de la classe définition).

Pour le dire en termes simples : vous faites en sorte que votre méthode __prepare__ renvoie un dictionnaire qui contient une entrée pour le décorateur. Preuve de concept :

class MyMeta(type):
    def __prepare__(name, bases):
        return {'x': 'foobar'}

class MyClass(metaclass=MyMeta):
    print(x)  # output: foobar
3
Aran-Fey 16 mars 2019 à 09:04

J'ai regardé à l'intérieur de la bibliothèque dont vous parlez et la classe Lexer hérite d'une métaclasse:

class Lexer(metaclass=LexerMeta):

À l'intérieur de LexerMeta, vous trouverez les éléments suivants:

@classmethod
    def __prepare__(meta, name, bases):
        d = LexerMetaDict()

        def _(pattern, *extra):
            patterns = [pattern, *extra]
            def decorate(func):
                pattern = '|'.join(f'({pat})' for pat in patterns )
                if hasattr(func, 'pattern'):
                    func.pattern = pattern + '|' + func.pattern
                else:
                    func.pattern = pattern
                return func
            return decorate

        d['_'] = _
        d['before'] = _Before
        return d

Une métaclasse est utilisée pour créer l'objet classe qui est ensuite utilisé pour instancier des objets. D'après ce que je peux voir dans cette méthode, c'est qu'ici d['_'] = _ cette métaclasse attache dynamiquement la méthode _ à la classe que vous allez utiliser.

Cela signifie que ce qu'ils font n'est pas très différent de:

class Bar:
    @staticmethod
    def some_decorator(f):
        ...

    @some_decorator
    def some_function(self):
        ...
3
Alexandru Martin 15 mars 2019 à 21:14