changing __call__ on demand

S

Stefan Behnel

Hi!

This somewhat puzzles me:

Python 2.4 (#1, Feb 3 2005, 16:47:05)
[GCC 3.3.4 (pre 3.3.5 20040809)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

..>>> class test(object):
.... def __init__(self):
.... self.__call__ = self.__call1
.... def __call1(self):
.... print 1
.... def __call__(self):
.... print 2
....
..>>> t = test()
..>>> t()
2

If I take out the __call__ method completely and only set it in __init__, I
get a TypeError saying that test is not callable.

I want to use this in order to provide different implementations based on the
object configuration. Calculating the right function to call is non-trivial
and calls are frequent, so I want to change __call__ in order to run the right
function directly.

I know, I could use another level of indirection:

def __call__(self):
self.the_right_method()

and then set the_right_method accordingly, but I find that somewhat
sub-optimal. Is there a way to change __call__ after class creation?

Stefan
 
A

Alan McIntyre

I tried this:
.... def __call1(self):
.... print 1
.... __call__ = __call1
....
Is that what you were looking for?

--
Alan McIntyre
ESRG LLC
http://www.esrgtech.com

Stefan said:
Hi!

This somewhat puzzles me:

Python 2.4 (#1, Feb 3 2005, 16:47:05)
[GCC 3.3.4 (pre 3.3.5 20040809)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

.>>> class test(object):
... def __init__(self):
... self.__call__ = self.__call1
... def __call1(self):
... print 1
... def __call__(self):
... print 2
...
.>>> t = test()
.>>> t()
2

If I take out the __call__ method completely and only set it in
__init__, I get a TypeError saying that test is not callable.

I want to use this in order to provide different implementations based
on the object configuration. Calculating the right function to call is
non-trivial and calls are frequent, so I want to change __call__ in
order to run the right function directly.

I know, I could use another level of indirection:

def __call__(self):
self.the_right_method()

and then set the_right_method accordingly, but I find that somewhat
sub-optimal. Is there a way to change __call__ after class creation?

Stefan
 
M

Michael Hoffman

Stefan said:
Is there a way to change __call__ after class creation?

__call__, like __getitem__, and __getattr__ is called on the
class object, not the instance object. So, no, not as far as I
am aware, without using metaclass trickery. The simplest option
IMO is to use another level of indirection as you suggest.
 
M

Michael Hoffman

Alan said:
... def __call1(self):
... print 1
... __call__ = __call1
Is that what you were looking for?

That still only allows him to have one call function per class.
 
S

Stefan Behnel

Michael said:
__call__, like __getitem__, and __getattr__ is called on the
class object, not the instance object. So, no, not as far as I
am aware, without using metaclass trickery. The simplest option
IMO is to use another level of indirection as you suggest.

Thanks for the quick answer. I didn't know they were class-level methods. Too
bad. Guess I'll stick with indirection then.

Stefan
 
S

Steven Bethard

Stefan said:
Is there a way to change __call__ after class creation?

Check out this thread on the topic:

http://mail.python.org/pipermail/python-list/2004-January/203142.html

Basically, the answer is no -- at least not on a per-instance basis.
You can try something like:

py> class Test(object):
.... def __new__(cls):
.... cls.__call__ = cls.call1
.... return object.__new__(cls)
.... def call1(self):
.... print 'call1'
.... def __call__(self):
.... print '__call__'
....
py> Test()()
call1

But then the call method is changed for all instances:

py> class Test(object):
.... instances = 0
.... def __new__(cls):
.... if cls.instances == 1:
.... print "setting __call__"
.... cls.__call__ = cls.call1
.... cls.instances += 1
.... return object.__new__(cls)
.... def call1(self):
.... print 'call1'
.... def __call__(self):
.... print '__call__'
....
py> t1 = Test()
py> t1()
__call__
py> t2 = Test()
setting __call__
py> t2()
call1
py> t1()
call1

Steve
 
H

Hans Nowak

Stefan said:
Hi!

This somewhat puzzles me:

Python 2.4 (#1, Feb 3 2005, 16:47:05)
[GCC 3.3.4 (pre 3.3.5 20040809)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

.>>> class test(object):
... def __init__(self):
... self.__call__ = self.__call1
... def __call1(self):
... print 1
... def __call__(self):
... print 2
...
.>>> t = test()
.>>> t()
2

If I take out the __call__ method completely and only set it in
__init__, I get a TypeError saying that test is not callable.

Note that it works just fine if you don't use a new-style class:
.... def __init__(self):
.... self.__call__ = self.foobar
.... def foobar(self, *args, **kwargs):
.... print "Called with:", args, kwargs
....Called with: (42,) {'x': 0}
 
K

Kent Johnson

Stefan said:
Hi!

This somewhat puzzles me:

Python 2.4 (#1, Feb 3 2005, 16:47:05)
[GCC 3.3.4 (pre 3.3.5 20040809)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

.>>> class test(object):
... def __init__(self):
... self.__call__ = self.__call1
... def __call1(self):
... print 1
... def __call__(self):
... print 2
...
.>>> t = test()
.>>> t()
2

It works the way you want if test is an old-style class: ... def __init__(self):
... self.__call__ = self.__call1
... def __call1(self):
... print 1
... def __call__(self):
... print 2
...1

Kent
 
M

Michael Hoffman

Stefan said:
Thanks for the quick answer. I didn't know they were class-level
methods. Too bad. Guess I'll stick with indirection then.

Here is one way of doing that indirection I just thought of--have
the class __call__ attribute call on the instance __call__
attribute:
.... def __init__(self, func):
.... self.__call__ = func
.... def __call__(self, *args, **keywds):
.... return self.__call__(*args, **keywds)
....42

I still can't figure out whether this is elegant, or opaque and
to be avoided. <wink>
 
F

F. Petitjean

Le Sun, 13 Feb 2005 13:19:03 -0500, Hans Nowak a écrit :
Stefan said:
Hi!

This somewhat puzzles me:

Python 2.4 (#1, Feb 3 2005, 16:47:05)
[GCC 3.3.4 (pre 3.3.5 20040809)] on linux2
Type "help", "copyright", "credits" or "license" for more information.

.>>> class test(object):
... def __init__(self):
... self.__call__ = self.__call1 # self.__call__ is bound
... def __call1(self):
... print 1
... def __call__(self): # self.__call__ is rebound
... print 2
...
.>>> t = test()
.>>> t()
2 2 because the last defined __call__ wins. Try dir(t)

If I take out the __call__ method completely and only set it in
__init__, I get a TypeError saying that test is not callable.
This seems logical.

Note that it works just fine if you don't use a new-style class:
... def __init__(self):
... self.__call__ = self.foobar
... def foobar(self, *args, **kwargs):
... print "Called with:", args, kwargs
...Called with: (42,) {'x': 0}
Are you sure that if you add a __call__() method, it will still work
fine ?


Regards
 
S

Steven Bethard

F. Petitjean said:
Le Sun, 13 Feb 2005 13:19:03 -0500, Hans Nowak a écrit :

Are you sure that if you add a __call__() method, it will still work
fine ?

Simple enough to check, isn't it?

py> class C:
.... def __init__(self):
.... self.__call__ = lambda: "__init__"
.... def __call__(self):
.... return "__call__"
....
py> C()()
'__init__'

Old-style classes lookup methods like __call__ on the instance[1].
New-style classes look them up on the type:

py> class C:
.... def __init__(self):
.... self.__iter__ = lambda: iter(["__init__"])
.... def __iter__(self):
.... return iter(["__call__"])
....
py> list(C())
['__init__']
py> class C(object):
.... def __init__(self):
.... self.__iter__ = lambda: iter(["__init__"])
.... def __iter__(self):
.... return iter(["__call__"])
....
py> list(C())
['__call__']

AFAICT, non-magic methods are looked up on instance first:

py> class C:
.... def __init__(self):
.... self.f = lambda: "__init__"
.... def f(self):
.... return "__call__"
....
py> C().f()
'__init__'
py> class C(object):
.... def __init__(self):
.... self.f = lambda: "__init__"
.... def f(self):
.... return "__call__"
....
py> C().f()
'__init__'

STeVe

[1] Well, they look there first.
 

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

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,906
Latest member
SkinfixSkintag

Latest Threads

Top