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):
>>> class foo(object):
... pass
...
>>> t = foo()
>>> def test():
... print 'bar'
...
>>> t()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'foo' object is not callable
>>> t.__call__ = test
>>> t()
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
--
»Time flies like an arrow, fruit flies like a Banana.«
PGP fingerprint: 5B93 61F8 4EA2 E279 ABF6 02CF A9AD B7F8 AE4E 425C
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
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
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
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?
> Bruno Desthuilliers <bruno.42.de...@websiteburo.invalid> writes:
>> 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.
>
> 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).
--
Gabriel Genellina