Extension method for python built-in types
Question:
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)
Answers:
No, because I’m pretty sure all the built-in types are written in optimized C and thus can’t be modified with Python. When I try it, I just get:
TypeError: can't set attributes of built-in/extension type 'list'
No. Types defined in C cannot be monkeypatched.
The best you can do appears to be deriving a class from the built-in type. For example:
class mylist(list):
def myfunc(self, x):
self.append(x)
test = mylist([1,2,3,4])
test.myfunc(99)
(You could even name it “list” so as to get the same constructor, if you wanted.) However, you cannot directly modify a built-in type like the example in your question.
Nope, you gotta subclass!
>>> 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'
Or more specific to your example
>>> class MyList(list):
... pass
...
>>> MyList.myExtension = lambda self,x:x * 2
>>> l = MyList()
>>> l.myExtension(10)
20
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?
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)
No, because I’m pretty sure all the built-in types are written in optimized C and thus can’t be modified with Python. When I try it, I just get:
TypeError: can't set attributes of built-in/extension type 'list'
No. Types defined in C cannot be monkeypatched.
The best you can do appears to be deriving a class from the built-in type. For example:
class mylist(list):
def myfunc(self, x):
self.append(x)
test = mylist([1,2,3,4])
test.myfunc(99)
(You could even name it “list” so as to get the same constructor, if you wanted.) However, you cannot directly modify a built-in type like the example in your question.
Nope, you gotta subclass!
>>> 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'
Or more specific to your example
>>> class MyList(list):
... pass
...
>>> MyList.myExtension = lambda self,x:x * 2
>>> l = MyList()
>>> l.myExtension(10)
20
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?