2011-07-18 9 views
29

¿es posible agregar el método de extensión a los tipos incorporados de python? Sé que puedo agregar el método de extensión al tipo definido simplemente agregando el nuevo método por. de la siguiente manera:¡Método de extensión para tipos incorporados de Python!

class myClass: 
    pass 

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

Pero alguna manera de añadir método de extensión de Python built'in tipos como lista, dict, ...

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

nota al margen: ruby ​​permite esto. otros idiomas que apoyan esto? –

+0

Karoly: Smalltalk, obviamente :) – Wrameerez

+1

posible duplicado de [¿Puede métodos de parche mono en tipos de núcleo en python?] (Http://stackoverflow.com/questions/192649/can-you-monkey-patch-methods-on-core -types-in-python) – jfs

Respuesta

54

Se puede hacer en Python puro con este módulo de remate:

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

Por ejemplo:

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 

No, eso no se siente bien?

+3

Esto recuerda a [esa escena] (http://imgur.com/lVEdIXO) en The Empire Strikes Back .... – n611x007

+15

¿Qué diablos es todo el código en esta respuesta? Simplemente haz 'pip install forbiddenfruit', luego' from badge install maldita' y comienza a destruir el universo. – ArtOfWarfare

+0

@ArtOfWarfare agregó cookies así que creo que valió la pena – MaLiN2223

3

No, porque estoy bastante seguro de todos los construidos -en los tipos están escritos en C optimizado y, por lo tanto, no se pueden modificar con Python. Cuando lo intento, acabo de obtener:

TypeError: can't set attributes of built-in/extension type 'list' 
16

No. Los tipos definidos en C no pueden ser parcheados.

+1

Es triste pero cierto. Por lo general, termino heredando del parche incorporado y del mono de la clase infantil si es necesario. – Ishpeck

+4

@Ishpeck: Eso suena a subclases ordinarias, no parche de mono en absoluto. –

+2

Por lo general, monopatching, acaba de hacer a la instancia de la subclase. Y es rudo y malo. Nunca lo hagas, niños. – Ishpeck

2

Lo mejor que puede hacer parece derivar una clase del tipo incorporado. Por ejemplo:

class mylist(list): 
    def myfunc(self, x): 
     self.append(x) 

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

(. Incluso es posible el nombre de "lista" con el fin de obtener el mismo constructor, si se desea) Sin embargo, no se puede modificar directamente un tipo incorporado como el ejemplo en su pregunta.

12

¡No, tienes que subclase!

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

O más específica a su ejemplo

>>> class MyList(list): 
...  pass 
... 
>>> MyList.myExtension = lambda self,x:x * 2 
>>> l = MyList() 
>>> l.myExtension(10) 
20 
Cuestiones relacionadas