Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 361
muskrat Avatar asked Sep 18 '12 11:09

muskrat


People also ask

How do I create a custom decorator in Python?

To create a decorator function in Python, I create an outer function that takes a function as an argument. There is also an inner function that wraps around the decorated function. To use a decorator ,you attach it to a function like you see in the code below.

Can a function have multiple decorators Python?

Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together. It is also known as nested decorators in Python.

What are Python decorators on what you can apply them?

Decorators are a very powerful and useful tool in Python since it allows programmers to modify the behaviour of a function or class. Decorators allow us to wrap another function in order to extend the behaviour of the wrapped function, without permanently modifying it.

How do you decorate a method in Python?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function.


3 Answers

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.

like image 146
senderle Avatar answered Oct 19 '22 16:10

senderle


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'
like image 40
chepner Avatar answered Oct 19 '22 16:10

chepner


Of course it is. Just write

remove_empty(lambda: some_string.split('\n'))()
like image 41
ecatmur Avatar answered Oct 19 '22 16:10

ecatmur