Python中是否可以对内置方法应用自己的装饰器?

6

我刚刚接触到Python装饰器。出于兴趣,你能否将自己的装饰器应用于内置对象方法?比如说,如果我想要应用这个装饰器:

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

到这个:

some_string.split('\n')

如何去除空字符串?这是可能的吗?或者说这是一个好主意吗?


1
您希望在应用程序中调用split()方法时始终删除空字符串吗? - Vaughn Cato
好观点。这可能是一个不好的例子,但我仍然想知道是否可能... - muskrat
3个回答

7
在某种意义上是可能的;这取决于你的具体意思。像这样的装饰器语法...
@dec
def foo():
    pass

实际上只是这个的简写:

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

因此,您可以在全局命名空间中的预定义函数上使用装饰器,没有任何阻碍。

func = dec(func)

但是内置类的方法存在于该类的命名空间中,该命名空间无法直接修改,正如chepner所指出的那样。这是一件好事,因为它确保了str类型的对象行为符合预期!然而,您可以子类化 str 并以这种方式装饰方法。(以下内容适用于 Python 2,在 Python 3 中,请将 filter 的输出传递给列表。super 也可能有些不同;我将在未来发布 Python 3 更新。)

>>> 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']

或者更直接地说(更符合装饰器使用的精神):
>>> class WeirdString2(str):
...     split = remove_empty(str.split)
... 
>>> WeirdString2('This decorator is unnecessary\n\n\n').split('\n')
['This decorator is unnecessary']

在这个特定的例子中,我更喜欢使用显式过滤器。但是我可以想象一个内置类的子类,做一些记忆化或类似的事情。

1
还可以通过替换内置函数来让你的子类替换现有的 str__builtins__.str = WeirdString。但这不会影响 ''"" 构造函数,只有在你真正理解自己在做什么时才应该这样做。 - Matthew Trevor
这在Python 3中可行吗?我得到的是<filter at 0x4d3b9b0>而不是['This decorator is unnecessary'] - Adrian Keister
@AdrianKeister,不完全正确,因为在Python 3中,filter返回的值是一个迭代器而不是列表。因此,调用filter需要包装在list的调用中。我会添加一条注释--感谢您的评论! - senderle
我明白了。如果我尝试使用 list[filter(...,我会得到 TypeError: 'type' object is not subscriptable 的错误提示。 - Adrian Keister
它将是 list(filter(... - senderle

5
很抱歉,答案是否定的。装饰器是在函数定义时应用的,而 str.split 是预定义的。你可能会认为可以做一些显式的操作,比如:
str.split = remove_empty(str.split)

但是这是不被允许的:

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'

1

当然可以。只需要写下来。

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

这只是返回一个新函数,您需要再次调用它来过滤 some_string。它不会将装饰器应用于 str.split - chepner
1
@chepner 是将一个装饰器应用于一个函数,并调用其结果。 - ecatmur
抱歉,我有括号盲。我没有看到结尾处多余的一对括号来调用函数。 - chepner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接