muskrat
muskrat

Reputation: 589

Is it possible to apply my own decorators to builtin methods in Python?

I've just come across Python decorators. Just out of interest, can you apply your own decorator to a built-in object method somehow? Say I wanted to apply this:

def remove_empty(fn):
    def filtered():
        return filter(lambda x: x != '', fn())
    return filtered

To this:

some_string.split('\n')

in order to remove empty strings. Is it possible? Or even a good idea?

Upvotes: 7

Views: 2888

Answers (3)

chepner
chepner

Reputation: 532053

I'm afraid the answer is no. Decorators are applied when the function is defined, and str.split is pre-defined. You might think you could do something explicit like

str.split = remove_empty(str.split)

but that is not permitted:

Traceback (most recent call last):
  File "tmp.py", line 8, in <module>
    str.split = remove_empty(str.split)
TypeError: can't set attributes of built-in/extension type 'str'

Upvotes: 6

senderle
senderle

Reputation: 151107

It's possible in a sense; it depends on what exactly you mean. Decorator syntax like this...

@dec
def foo():
    pass

is really just sugar for this:

def foo():
    pass
foo = dec(foo)

So there's nothing to stop you from using a decorator on a predefined function in the global namespace.

func = dec(func)

But the methods of built-in classes live in the namespace of that class, and that namespace can't be modified directly, as chepner has already pointed out. That's a good thing, because it ensures that objects of type str will behave as expected! However, you could subclass str and decorate the method that way. (The below works in Python 2; in Python 3, pass the output of filter to a list. super also may work a little differently; I'll post a Python 3 update in the future.)

>>> def remove_empty(fn):
...     def filtered(*args, **kwargs):
...         return filter(lambda x: x != '', fn(*args, **kwargs))
...     return filtered
... 
>>> class WeirdString(str):
...     @remove_empty
...     def split(self, *args, **kwargs):
...         return super(WeirdString, self).split(*args, **kwargs)
... 
>>> 'This decorator is unnecessary\n\n\n'.split('\n')
['This decorator is unnecessary', '', '', '']
>>> WeirdString('This decorator is unnecessary\n\n\n').split('\n')
['This decorator is unnecessary']

Or more directly (and so more in the spirit of decorator use):

>>> class WeirdString2(str):
...     split = remove_empty(str.split)
... 
>>> WeirdString2('This decorator is unnecessary\n\n\n').split('\n')
['This decorator is unnecessary']

In the case of this particular example, I'd prefer an explicit filter. But I can imagine, for example, a subclass of a built-in class that does some memoization or something like that.

Upvotes: 7

ecatmur
ecatmur

Reputation: 157454

Of course it is. Just write

remove_empty(lambda: some_string.split('\n'))()

Upvotes: 1

Related Questions