Search code examples
pythonforbiddenfruit

Extension method for python built-in types


is it possible to add extension method to python built-in types? I know that I can add extension method to defined type by simply adding new method by . as following:

class myClass:
    pass

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

But is any way to adding extension method to python built'in types like list, dict, ...

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

Solution

  • It can be done in pure Python with this incredibly clever module:

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

    For example:

    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
    

    There, doesn't that feel good?