Reputation: 5968
Let's say I have this class:
class Foo:
@classmethod
def some_decorator(cls, ...):
...
And then I create a subclass which uses the parent class decorator:
class Bar(Foo):
@Foo.some_decorator(...)
def some_function(...)
...
How do I remove the need for Foo.
before the decorator name? The below code doesn't work:
class Bar(Foo):
@some_decorator(...)
def some_function(...)
...
I believe this is possible, because the sly
library does this.
See their example:
from sly import Lexer, Parser
class CalcLexer(Lexer):
...
@_(r'\d+')
def NUMBER(self, t):
t.value = int(t.value)
return t
...
As you can see, you can type in @_(...)
instead of @Lexer._(...)
.
How do they accomplish this?
Upvotes: 2
Views: 194
Reputation: 43166
This is done with a metaclass that implements a __prepare__
method. Excerpt from the docs:
3.3.3.4. Preparing the class namespace
Once the appropriate metaclass has been identified, then the class namespace is prepared. If the metaclass has a
__prepare__
attribute, it is called asnamespace = metaclass.__prepare__(name, bases, **kwds)
(where the additional keyword arguments, if any, come from the class definition).
To put it in simple terms: You make your __prepare__
method return a dictionary that contains an entry for the decorator. Proof of concept:
class MyMeta(type):
def __prepare__(name, bases):
return {'x': 'foobar'}
class MyClass(metaclass=MyMeta):
print(x) # output: foobar
Upvotes: 3
Reputation: 236
I have looked inside the library you are talking about and the Lexer
class inherits a metaclass:
class Lexer(metaclass=LexerMeta):
Inside the LexerMeta
you can find the following:
@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
A metaclass is used to create the class object which then is used to instantiate objects. From what i can see in that method is that here d['_'] = _
that metaclass dynamically attaches the _
method to the class you are going to use.
This means that what they are doing is not much different from:
class Bar:
@staticmethod
def some_decorator(f):
...
@some_decorator
def some_function(self):
...
Upvotes: 3