Monkeypatching an object to become callable

N

Nikolaus Rath

Hi,

I want to monkeypatch an object so that it becomes callable, although
originally it is not meant to be. (Yes, I think I do have a good reason
to do so).

But simply adding a __call__ attribute to the object apparently isn't
enough, and I do not want to touch the class object (since it would
modify all the instances):
.... pass
.... .... print 'bar'
.... Traceback (most recent call last):
Traceback (most recent call last):
bar


Is there an additional trick to get it to work?


Best,

-Nikolaus
 
D

Diez B. Roggisch

Nikolaus said:
Hi,

I want to monkeypatch an object so that it becomes callable, although
originally it is not meant to be. (Yes, I think I do have a good reason
to do so).

But simply adding a __call__ attribute to the object apparently isn't
enough, and I do not want to touch the class object (since it would
modify all the instances):

... pass
...
... print 'bar'
...
Traceback (most recent call last):

Traceback (most recent call last):

bar


Is there an additional trick to get it to work?

AFAIK special methods are always only evaluated on the class. But this
works:

class Foo(object):

pass

f = Foo()

def make_callable(f):
class Callable(f.__class__):

def __call__(self):
print "foobar"

f.__class__ = Callable

make_callable(f)
f()

Diez
 
C

Carl Banks

Hi,

I want to monkeypatch an object so that it becomes callable, although
originally it is not meant to be. (Yes, I think I do have a good reason
to do so).

But simply adding a __call__ attribute to the object apparently isn't
enough, and I do not want to touch the class object (since it would
modify all the instances):


Override the class's __call__, and program it to call a certain method
(say _instancecall) on the object. Catch AttributeError and raise
TypeError so that it matches the behavior when no __call__ is defined.


def __call__(self,*args,**kwargs):
try:
func = self._instancecall
except AttributeError:
raise TypeError("'%s' object not callable" % self.__class__)
return func(*args,**kwargs)


Note: don't call _instancecall inside the try clause; you don't want
to catch attribute errors raised inside the _instancecall method.

Then set _instancecall on any objects you want to be callable.


Carl Banks
 
7

7stud

Hi,

I want to monkeypatch an object so that it becomes callable, although
originally it is not meant to be. (Yes, I think I do have a good reason
to do so).

But simply adding a __call__ attribute to the object apparently isn't
enough, and I do not want to touch the class object (since it would
modify all the instances):


...   pass
...>>> t = foo()

...   print 'bar'
...>>> t()

Traceback (most recent call last):


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'foo' object is not callable>>> t.__call__()

bar

Is there an additional trick to get it to work?

Best,

   -Nikolaus


With an old-style class your code will work:

class A:
pass

def test():
print "test"

a = A()
a.__call__ = test

a()

--output:--
test


a2 = A()
a2()

--output:--

a2()
AttributeError: A instance has no __call__ method



Another option is to use the *decorator pattern*. The decorator
pattern can be used when you want to add additional methods and
attributes to an object, and you don't want to disturb the original
class:

class A(object):
def __init__(self, x):
self.x = x

def sayhi(self):
print "hi"


class Wrapper(object):
def __init__(self, obj, func):
self.obj = obj
self.func = func

def __call__(self, *args):
return self.func(*args)

def __getattr__(self, name):
return object.__getattribute__(self.obj, name)


def test():
print "test"

a = A(10)
w = Wrapper(a, test)
w()
print w.x
w.sayhi()

--output:--
test
10
hi
 
B

Bruno Desthuilliers

7stud a écrit :
(snip)
class Wrapper(object):
def __init__(self, obj, func):
self.obj = obj
self.func = func

def __call__(self, *args):
return self.func(*args)

def __getattr__(self, name):
return object.__getattribute__(self.obj, name)

This should be

return getattr(self.obj, name)

directly calling object.__getattribute__ might skip redefinition of
__getattribute__ in self.obj.__class__ or it's mro.
 
N

Nikolaus Rath

Bruno Desthuilliers said:
7stud a écrit :
(snip)

This should be

return getattr(self.obj, name)

directly calling object.__getattribute__ might skip redefinition of
__getattribute__ in self.obj.__class__ or it's mro.

Works nicely, thanks. I came up with the following shorter version which
modifies the object in-place:

class Modifier(obj.__class__):
def __call__(self):
return fn()

obj.__class__ = Modifier


To me this seems a bit more elegant (less code, less underscores). Or
are there some cases where the above would fail?


Best,

-Nikolaus
 
G

Gabriel Genellina

Works nicely, thanks. I came up with the following shorter version which
modifies the object in-place:

class Modifier(obj.__class__):
def __call__(self):
return fn()

obj.__class__ = Modifier


To me this seems a bit more elegant (less code, less underscores). Or
are there some cases where the above would fail?

I assume the above code is inside a function like make_callable(obj, fn)
Then, a new class is created for every instance you make callable; you may
want to cache all those classes (classes aren't cheap).

Might fail: when obj is not a new-style class, or its __class__ isn't
writable (e.g. builtin types).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top