Python内置类型的扩展方法

57

能否向Python内置类型添加扩展方法? 我知道可以通过在.后添加新方法来向已定义类型添加扩展方法,如下所示:

class myClass:
    pass

myClass.myExtensionMethod = lambda self,x:x * 2
z = myClass()
print z.myExtensionMethod(10)

但是否有任何方法将扩展方法添加到Python内置类型,例如列表、字典等?

list.myExtension = lambda self,x:x * 2
list.myExtension(10)

1
旁注:Ruby 允许这样做。还有其他编程语言支持吗? - Karoly Horvath
Karoly:显然是Smalltalk :) - Wrameerez
5个回答

81

使用这个极其巧妙的模块,可以纯粹地用Python完成:

https://pypi.python.org/pypi/forbiddenfruit

例如:

import functools
import ctypes
import __builtin__
import operator

class PyObject(ctypes.Structure):
    pass

Py_ssize_t = hasattr(ctypes.pythonapi, 'Py_InitModule4_64') and ctypes.c_int64 or ctypes.c_int

PyObject._fields_ = [
    ('ob_refcnt', Py_ssize_t),
    ('ob_type', ctypes.POINTER(PyObject)),
]

class SlotsPointer(PyObject):
    _fields_ = [('dict', ctypes.POINTER(PyObject))]

def proxy_builtin(klass):
    name = klass.__name__
    slots = getattr(klass, '__dict__', name)

    pointer = SlotsPointer.from_address(id(slots))
    namespace = {}

    ctypes.pythonapi.PyDict_SetItem(
        ctypes.py_object(namespace),
        ctypes.py_object(name),
        pointer.dict,
    )

    return namespace[name]

def die(message, cls=Exception):
    """
        Raise an exception, allows you to use logical shortcut operators to test for object existence succinctly.

        User.by_name('username') or die('Failed to find user')
    """
    raise cls(message)

def unguido(self, key):
    """
        Attempt to find methods which should really exist on the object instance.
    """
    return functools.partial((getattr(__builtin__, key, None) if hasattr(__builtin__, key) else getattr(operator, key, None)) or die(key, KeyError), self)

class mapper(object):
    def __init__(self, iterator, key):
        self.iterator = iterator
        self.key = key
        self.fn = lambda o: getattr(o, key)

    def __getattribute__(self, key):
        if key in ('iterator', 'fn', 'key'): return object.__getattribute__(self, key)
        return mapper(self, key)

    def __call__(self, *args, **kwargs):
        self.fn = lambda o: (getattr(o, self.key, None) or unguido(o, self.key))(*args, **kwargs)
        return self

    def __iter__(self):
        for value in self.iterator:
            yield self.fn(value)

class foreach(object):
    """
        Creates an output iterator which will apply any functions called on it to every element
        in the input iterator. A kind of chainable version of filter().

        E.g:

        foreach([1, 2, 3]).__add__(2).__str__().replace('3', 'a').upper()

        is equivalent to:

        (str(o + 2).replace('3', 'a').upper() for o in iterator)

        Obviously this is not 'Pythonic'.
    """
    def __init__(self, iterator):
        self.iterator = iterator

    def __getattribute__(self, key):
        if key in ('iterator',): return object.__getattribute__(self, key)
        return mapper(self.iterator, key)

    def __iter__(self):
        for value in self.iterator:
            yield value

proxy_builtin(list)['foreach'] = property(foreach)

import string

print string.join([1, 2, 3].foreach.add(2).str().add(' cookies').upper(), ', ')

>>> 3 COOKIES, 4 COOKIES, 5 COOKIES

感觉不错,对吧?


7
这让人想起了《星球大战:帝国反击战》中的那个场景... - n611x007
35
这个回答中所有的代码都是做什么用的?只需要执行pip install forbiddenfruit,然后 from forbiddenfruit import curse,就可以开始毁灭宇宙了。请注意,不要改变原本的意思。 - ArtOfWarfare
@ArtOfWarfare他添加了cookies,所以我认为这是值得的。 - MaLiN2223
4
forbiddenfruit 只知道如何穿透类的字典代理并干扰底层的字典。它不知道如何修复它破坏的其他任何东西,比如类型属性缓存或用于运算符重载的 C-API 插槽。如果使用 forbiddenfruit,很容易导致 Python 崩溃或进入不一致的状态,在这种状态下,应该调用相同方法的操作会执行不同的操作。 - user2357112

20

不可以对 C 语言中定义的类型进行 Monkey Patching。


1
这是令人沮丧但却真实的事实。如果必须这样做,我通常会从内置类中继承并对子类进行 monkey patch。 - Ishpeck
4
那听起来像是普通的子类化,并不是猴子补丁。 - S.Lott
2
通常是猴子补丁,只是针对子类实例进行的。这很不好。孩子们永远不要这样做。 - Ishpeck
4
为何不在子类化时直接应用更改? - S.Lott
1
@Ishpeck:就像我说的。为什么?“混乱和糟糕”并不是一个很好的理由。 - S.Lott
显示剩余5条评论

12

不行,你必须要子类化!

>>> import string
>>> class MyString(str):
...     def disemvowel(self):
...         return MyString(string.translate(self, None, "aeiou"))
... 
>>> s = MyString("this is only a test")
>>> s.disemvowel()
'ths s nly  tst'
或者更加针对你的例子。
>>> class MyList(list):
...     pass
... 
>>> MyList.myExtension = lambda self,x:x * 2
>>> l = MyList()
>>> l.myExtension(10)
20

3
不行,因为我相当确定所有内置类型都是用优化后的 C 编写的,因此无法使用 Python 进行修改。当我尝试时,只会出现以下结果:
TypeError: can't set attributes of built-in/extension type 'list'

2
您可以从内置类型中派生一个类来实现最佳效果。例如:
class mylist(list):
    def myfunc(self, x):
        self.append(x)

test = mylist([1,2,3,4])
test.myfunc(99)

你甚至可以将其命名为“list”,以便获得相同的构造函数(如果你愿意的话)。但是,你不能直接修改内置类型,就像你问题中的示例一样。


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