[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

using super

iu2

12/31/2007 1:48:00 PM

Hi

I'm trying to make a method call automatically to its super using this
syntax:

class A:
chained = ['pr']
def pr(self):
print 'Hello from A'

class B(A):
def pr(self):
print 'Hello from B'

chain(B, A)
b = B()
b.pr()

b.pr() will print
Hello from B
Hello from A

I'm doing it using the 'chained' attribute in class A, and with this
function:

def chain(cls, sup):
for m in dir(cls):
if callable(getattr(cls, m)) and m in cls.chained:
cm = getattr(cls, m)
def m2(*p):
cm(*p)
return getattr(sup, m)(*p)
setattr(cls, m, m2)
return cls

which seeks for all 'chained' methods and adjusts them accordingly.
(had there been class decorators the syntax would have been simpler,
something like

class A:
@make_chained
def pr():
print 'Hello from A'

@chained
class B:
def pr():
print 'Hello from B'
)


My problem is this: Currently I pass the base class to 'chain' -
chain(B, A)

I prefer to write
chain(B)

and let 'chain' use the super of B.

So:
def chain(cls):
for m in dir(cls):
if callable(getattr(cls, m)) and m in cls.chained:
print 'chaning', cls, m
cm = getattr(cls, m)
def m2(*p):
cm(*p)
return getattr(super(cls), m)(*p)
setattr(cls, m, m2)

This is probably wrong because I don't give the object instance to
super (I don't have it!) and I also get the error
TypeError: super() argument 1 must be type, not classobj

Can you please help me with this?

Thanks
iu2
16 Answers

Steven D'Aprano

12/31/2007 2:09:00 PM

0

On Mon, 31 Dec 2007 05:47:31 -0800, iu2 wrote:

> Hi
>
> I'm trying to make a method call automatically to its super using this
> syntax:

[snip code]


I'm not sure if this is your only problem or not, but super() only works
with new-style classes, not with classic classes. You must inherit from
object, or it cannot possibly work.

Change "class A" to "class A(object)".

However, I suspect your approach may be too complicated. Try this:


def chain(meth): # A decorator for calling super.
def f(self, *args, **kwargs):
result = meth(self, *args, **kwargs)
S = super(self.__class__, self)
getattr(S, meth.__name__)(*args, **kwargs)
return result
f.__name__ = "chained_" + meth.__name__
return f



class A(object):
def foo(self, x):
print "I am %s" % self
return x

class B(A):
@chain
def foo(self, x):
print "This is B!!!"
return x + 1



>>> a = A()
>>> a.foo(5)
I am <__main__.A object at 0xb7cf676c>
5
>>> b = B()
>>> b.foo(5)
This is B!!!
I am <__main__.B object at 0xb7cf68ac>
6



--
Steven

Scott David Daniels

12/31/2007 4:03:00 PM

0

Steven D'Aprano wrote:
> ...
> I'm not sure if this is your only problem or not, but super() only works
> with new-style classes, not with classic classes. You must inherit from
> object, or it cannot possibly work.
>
> Change "class A" to "class A(object)".
Absolutely correct.

However, the suggested simpler code cannot work on any released Python:

> def chain(meth): # A decorator for calling super.
> def f(self, *args, **kwargs):
> result = meth(self, *args, **kwargs)
> S = super(self.__class__, self)
This line is the problem. The class parameter needs to be the class
name (B in this case) in which the chaining method is defined, not that
of the object itself.

> getattr(S, meth.__name__)(*args, **kwargs)
> return result
> f.__name__ = "chained_" + meth.__name__
> return f
>
>
> class A(object):
> def foo(self, x):
> print "I am %s" % self
> return x
>
> class B(A):
> @chain
> def foo(self, x):
> print "This is B!!!"
> return x + 1

You'll see the problem once you figure out what goes wrong with:
class C(B):
@chain
def foo(self, x):
print "This is C!!!"
return x + 2

C().foo(5)


As to the original idea, better to give it up.
Typically code for a "chained" method "foo" that
returns a result will want to (in some way) use
the result from that call in forming its result.
Python's super allows you to place that call where
it belongs in your code (perhaps after some logging
is enabled, for example) and not just "at the spot
the guru insists the chaining happens." The recursion-
like call to the super method is placed explicitly
in Python so you can see how it works. If super is
too tough to explain, I expect single inheritance is
all you are using. simply remind people to call
A.foo(self, <args>) within the definition of foo in
B.

--Scott David Daniels
Scott.Daniels@Acm.Org

Gabriel Genellina

12/31/2007 4:04:00 PM

0

En Mon, 31 Dec 2007 12:08:43 -0200, Steven D'Aprano
<steve@REMOVE-THIS-cybersource.com.au> escribi�:

> On Mon, 31 Dec 2007 05:47:31 -0800, iu2 wrote:
>
>> I'm trying to make a method call automatically to its super using this
>> syntax:
>
>
> def chain(meth): # A decorator for calling super.
> def f(self, *args, **kwargs):
> result = meth(self, *args, **kwargs)
> S = super(self.__class__, self)
> getattr(S, meth.__name__)(*args, **kwargs)
> return result
> f.__name__ = "chained_" + meth.__name__
> return f
>
>
>
> class A(object):
> def foo(self, x):
> print "I am %s" % self
> return x
>
> class B(A):
> @chain
> def foo(self, x):
> print "This is B!!!"
> return x + 1

If you later inherit from B and try to @chain a method, nasty things
happen... The problem is that the two arguments to super (current class,
and actual instance) are *both* required; you can't fake the first using
self.__class__. But you can't refer to the current class inside the
decorator, because it doesn't exist yet. You could use the decorator to
just "mark" the function to be chained, and then -with the help of a
metaclass- do the actual decoration when the class is created.

def chain(meth):
"""Mark a method to be "chained" later"""
meth.chained = True
return meth

def chain_impl(cls, meth):
"""The original decorator by SD'A"""
def f(self, *args, **kwargs):
result = meth(self, *args, **kwargs)
S = super(cls, self)
getattr(S, meth.__name__)(*args, **kwargs)
return result
f.__name__ = "chained_" + meth.__name__
return f

class ChainedType(type):
def __new__(meta, name, bases, dic):
cls = super(ChainedType, meta).__new__(meta, name, bases, dic)
# replace functions marked "to be chained" with its decorated
version
for name, value in dic.iteritems():
if getattr(value, 'chained', False):
setattr(cls, name, chain_impl(cls, value))
return cls

class A(object):
__metaclass__ = ChainedType
def foo(self, x):
print "I am %s" % self
return x

class B(A):
@chain
def foo(self, x):
print "This is B!!!"
return x + 1

class C(B):
@chain
def foo(self, x):
print "This is C!!!"
return x + 2

py> a = A()
py> a.foo(5)
I am <__main__.A object at 0x00A3C690>
5
py> b = B()
py> b.foo(5)
This is B!!!
I am <__main__.B object at 0x00A3CA90>
6
py> c = C()
py> c.foo(5)
This is C!!!
This is B!!!
I am <__main__.C object at 0x00A3C830>
7

The approach above tries to stay close to the chain decorator as
originally posted. There are other ways that you can search for in the
Python Cookbook.

--
Gabriel Genellina

Steven D'Aprano

12/31/2007 11:24:00 PM

0

On Mon, 31 Dec 2007 08:03:22 -0800, Scott David Daniels wrote:

> Steven D'Aprano wrote:
>> ...
>> I'm not sure if this is your only problem or not, but super() only
>> works with new-style classes, not with classic classes. You must
>> inherit from object, or it cannot possibly work.
>>
>> Change "class A" to "class A(object)".
> Absolutely correct.
>
> However, the suggested simpler code cannot work on any released Python:
>
>> def chain(meth): # A decorator for calling super.
>> def f(self, *args, **kwargs):
>> result = meth(self, *args, **kwargs)
>> S = super(self.__class__, self)
> This line is the problem. The class parameter needs to be the class
> name (B in this case) in which the chaining method is defined, not that
> of the object itself.

One minor correction: the class parameter needs to be the class *itself*,
not the class *name* (which would be the string "B").

I don't quite understand your description though. What do you mean "the
chaining method is defined"? chain() is defined outside of a class.


[snip]

> You'll see the problem once you figure out what goes wrong with:
> class C(B):
> @chain
> def foo(self, x):
> print "This is C!!!"
> return x + 2
>
> C().foo(5)


Hmmm... obviously I did insufficient testing. That's certainly a problem.



--
Steven

Scott David Daniels

1/1/2008 12:19:00 AM

0

Steven D'Aprano wrote:
> On Mon, 31 Dec 2007 08:03:22 -0800, Scott David Daniels wrote:
>> Steven D'Aprano wrote: ...
>>> def chain(meth): # A decorator for calling super.
>>> def f(self, *args, **kwargs):
>>> result = meth(self, *args, **kwargs)
>>> S = super(self.__class__, self)
>> This line is the problem. The class parameter needs to be the class
>> (B in this case) in which the chaining method is defined, not that
>> of the object itself.
> One minor correction: the class parameter needs to be the class *itself*,
> not the class *name* (which would be the string "B").
Point taken.

> I don't quite understand your description though. What do you mean "the
> chaining method is defined"? chain() is defined outside of a class.
The class where f (the chaining method) is defined; equivalently, the
class in which the @chain is used.

-Scott

Steven D'Aprano

1/1/2008 2:36:00 AM

0

On Mon, 31 Dec 2007 16:19:11 -0800, Scott David Daniels wrote:

> Steven D'Aprano wrote:
>> On Mon, 31 Dec 2007 08:03:22 -0800, Scott David Daniels wrote:
>>> Steven D'Aprano wrote: ...
>>>> def chain(meth): # A decorator for calling super.
>>>> def f(self, *args, **kwargs):
>>>> result = meth(self, *args, **kwargs)
>>>> S = super(self.__class__, self)
>>> This line is the problem. The class parameter needs to be the class
>>> (B in this case) in which the chaining method is defined, not that of
>>> the object itself.
>> One minor correction: the class parameter needs to be the class
>> *itself*, not the class *name* (which would be the string "B").
> Point taken.
>
>> I don't quite understand your description though. What do you mean "the
>> chaining method is defined"? chain() is defined outside of a class.
>
> The class where f (the chaining method) is defined; equivalently, the
> class in which the @chain is used.

So why doesn't self.__class__ work? That's the class in which @chain is
used.

I can clearly see that it doesn't work, I just don't understand why. I'd
be inclined to chalk it up to super() being a mysterious black box that
makes no sense *wink* except that the following decorator also doesn't
work:


def chain(meth): # A decorator for not calling super.
def f(self, *args, **kwargs):
result = meth(self, *args, **kwargs)
S = self.__class__.__base__
getattr(S, meth.__name__)(self, *args, **kwargs)
return result
return f



--
Steven

Scott David Daniels

1/1/2008 5:12:00 AM

0

Steven D'Aprano wrote:
> On Mon, 31 Dec 2007 16:19:11 -0800, Scott David Daniels wrote:
>
>> Steven D'Aprano wrote:
>>> On Mon, 31 Dec 2007 08:03:22 -0800, Scott David Daniels wrote:
>>>> Steven D'Aprano wrote: ...
>>>>> def chain(meth): # A decorator for calling super.
>>>>> def f(self, *args, **kwargs):
>>>>> result = meth(self, *args, **kwargs)
>>>>> S = super(self.__class__, self)
>>>> This line is the problem. The class parameter needs to be the class
>>>> (B in this case) in which the chaining method is defined, not that of
>>>> the object itself.
>>> One minor correction: the class parameter needs to be the class
>>> *itself*, not the class *name* (which would be the string "B").
>> Point taken.
>>
>>> I don't quite understand your description though. What do you mean "the
>>> chaining method is defined"? chain() is defined outside of a class.
>> The class where f (the chaining method) is defined; equivalently, the
>> class in which the @chain is used.
>
> So why doesn't self.__class__ work? That's the class in which @chain is
> used.

OK, here's a simple 3-class example:

class A(object):
def meth(self): print 'A.meth:', self.__class__, '---'
def pn(self): return '<A>'

class B(A):
def meth(self):
super(B, self).meth()
print 'B.meth:', self.__class__, super(B, self).pn()
def pn(self): return '<B>'

class C(B):
def meth(self):
super(C, self).meth()
print 'C.meth:', self.__class__, super(C, self).pn()
def pn(self): return '<C>'

c = C()
c.meth()
# Figure out why it printed what it did.

# If not clear yet, how about this:
for class_ in C, B:
print class_.__name__, super(class_, c).pn()

# And a bigger example (re-using A) to show why we
class B0(A):
def meth(self):
super(B0, self).meth()
print 'B0.meth:', self.__class__, super(B0, self).pn()
def pn(self): return '<B0>'

class B1(B0):
def meth(self):
super(B1, self).meth()
print 'B1.meth:', self.__class__, super(B1, self).pn()
def pn(self): return '<B1>'

class B2(B0):
def meth(self):
super(B2, self).meth()
print 'B2.meth:', self.__class__, super(B2, self).pn()
def pn(self): return '<B2>'

class C1(B1, B2):
def meth(self):
super(C1, self).meth()
print 'C1.meth:', self.__class__, super(C1, self).pn()
def pn(self): return '<C1>'

class D1(C1):
def meth(self):
super(D1, self).meth()
print 'D1.meth:', self.__class__, super(D1, self).pn()
def pn(self): return '<D1>'

d = D1()
d.meth()
# Figure out why it printed what it did.

for class_ in D1, C1, B1, B2, B0:
print class_.__name__, super(class_, d).pn()
# Now (after much cogitation) might that do it?

# finally, just a fillip, predict this before you run it:
class E(D1, C):
def meth(self):
super(E, self).meth()
print 'E.meth:', self.__class__, super(E, self).pn()
def pn(self): return '<E>'

e = E()
e.meth()
for class_ in E, D1, C1, B1, B2, B0, C, B:
print class_.__name__, super(class_, e).pn()


> I can clearly see that it doesn't work, I just don't understand why. I'd
> be inclined to chalk it up to super() being a mysterious black box that
> makes no sense *wink* ....

super (and mro) work to get to all the superclasses in an order that
produces subtypes before their supertypes. The diamond inheritance
examples "show" why its needed.

-Scott

iu2

1/1/2008 6:57:00 AM

0

On Dec 31 2007, 6:03 pm, Scott David Daniels <Scott.Dani...@Acm.Org>
wrote:
> As to the original idea, better to give it up.
> Typically code for a "chained" method "foo" that
> returns a result will want to (in some way) use
> the result from that call in forming its result.
> Python's super allows you to place that call where
> it belongs in your code (perhaps after some logging
> is enabled, for example) and not just "at the spot
> the guru insists the chaining happens."  

Indeed I might want to chain methods in all sort of ways:
@chain_call_before - call the parent's method before the derived
method
@chain_call_after - call the parent's method after the derived method
@chain_call_sum - sum the result of the parent's method with the
result of the derived method
@chain_call_list - make a list from the result of the parent's method
and the result of the derived method

An API for easily adding chaining methods is also something to think
about.
This kind of chaining actually exists in Common Lisp (CLOS) and I wish
for something like that in Python
so I gave it a(n unsuccessful) try.

I think that especially Python, which is known (among other things)
for its object oriented properties,
should support this kind of chaining.


>simply remind people to call
> A.foo(self, <args>) within the definition of foo in
> B.

Sorry, I can't agree to this (although there is nothing else I can
do.. :-) . Reminding is not "simply" at all. Why REMIND people do
stuff and not let Python handle it automatically? What's Python (and
all other powerful languages) for if not for this kind of job?
(Although I think this "chaining" is not supperted by none of them
except for Lisp).

Although I actually work in a C++ team, and all the Python I do is for
myself so I don't have to remind anyone, I think this mechansim is
necessary.
What actually made me intereseted in that is a C++ application I write
where I figured that
chaining would help.

If I translate it to Python, it's something like this:
(I'm still not familiar with new style classes so please forgive me..)

class Inetrface_system:
def get_name(self):
return "No_name"

get_name is an interface function which the programmer calls in the
application.
This is a new system:
class System_A(Interface_system):
def get_name(self):
return "System_A"

There are several dereived classes like System_A.
I need to log each call to get_name for any class, but I just can't
rely on remembering
(by progammers) to add this extra themselves for get_name for each
class they derive:

class Inetrface_system:
def get_name(self):
log_to_file('get_name called')
return "No_name"

class System_A(Interface_system):
def get_name(self):
Interface_system.get_name() # can be forgotten
return "System_A"

One solution is:

class Interface_system():
def get_name_api(self):
log_to_file('get name called')
return get_name()
def get_name(self):
return 'No name'

class System_A(Interface_system):
def get_name(self):
return 'System_A'

So programmers need only override get_name. But this makes it
confusing: One has to override
get_name, but must call get_name_api throughout the application. I
don't like it.
So, recalling CLOS I thought of a better way:


class Inetrface_system:
@cained_call_before
def get_name(self):
log_to_file('get_name called')

@chained
class System_A(Interface_system):
def get_name(self):
return 'System_A'

Now the programmer both overrides get_name, and calls get_name
directly in the application. And
no one has to remember to call the parent's get_name method. It's done
automatically.

(A PEP maybe? I sense you don't really like it.. ;-)
What do you think? Good? Too implicit?

iu2

Michele Simionato

1/1/2008 7:59:00 AM

0

On Jan 1, 7:56 am, iu2 <isra...@elbit.co.il> wrote:
> no one has to remember to call the parent's get_name method. It's done
> automatically.
>
> (A PEP maybe? I sense you don't really like it.. ;-)
> What do you think? Good? Too implicit?
>
> iu2

No PEP, this would never pass. There would be no way
to stop a method from calling its parent: you would
lose control of your classes, so I think this is a
bad idea. Having said so, it is easy to implement
what you want with a metaclass:

def callParent(*methodnames):
class Meta(type):
def __init__(cls, name, bases, dic):
for methodname in methodnames:
if methodname in dic:
def new_meth(self, method=methodname):
parent = getattr(super(cls, self), method,
None)
if parent: parent()
child = dic.get(method)
if child: child(self)
setattr(cls, methodname, new_meth)
return Meta

class B(object):
__metaclass__ = callParent('get_name')
def get_name(self):
print "This is B.get_name"

class C(B):
def get_name(self):
print "This is C.get_name"


C().get_name()


Now every subclass of B defining a get_name method
will automagically call its parent method.

Michele Simionato

iu2

1/1/2008 10:00:00 AM

0

On Jan 1, 9:59 am, Michele Simionato <michele.simion...@gmail.com>
wrote:

> No PEP, this would never pass. There would be no way
> to stop a method from calling its parent: you would
> lose control of your classes, so I think this is a
> bad idea.

Not all classes, only classes the programmer chooses to have this
behaviour.

>Having said so, it is easy to implement
> what you want with a metaclass:
>
> def callParent(*methodnames):
>      class Meta(type):
>          def __init__(cls, name, bases, dic):
...
Thanks