__getattr__ Confusion

S

Saul Spatz

To the good people on comp.lang.python:

I have the following Tkinter class (python 2.7.3):

from Tkinter import *

class ScrolledCanvas(Frame):
def __init__(self, master, width, height, bg, cursor):
Frame.__init__(self, master)
self.__nonzero__ = lambda: True
canv = self.canvas = Canvas(self, bg=bg, relief=SUNKEN)
# self.__getattr__ = lambda x, name: getattr(self.canvas, name)
canv.config(width=width, height=height) # display area size
canv.config(scrollregion=(0, 0, width, height)) # canvas size corners
canv.config(highlightthickness=0) # no pixels to border

ybar = Scrollbar(self)
ybar.config(command=canv.yview) # xlink sbar and canv
canv.config(yscrollcommand=ybar.set) # move one moves other

xbar = Scrollbar(self)
xbar.config(command=canv.xview) # xlink sbar and canv
canv.config(xscrollcommand=xbar.set) # move one moves other

canv.grid(row = 0, column = 0, sticky = 'news')
ybar.grid(row = 0, column = 1, sticky = 'ns')
xbar.grid(row = 1, column = 0, sticky = 'ew')
self.rowconfigure(0, weight = 1)
self.columnconfigure(0, weight = 1)

self.create_text(20, 20, text = 'Did it!', fill = 'red')

def __getattr__(self, name):
return getattr(self.canvas, name)

root = Tk()
app = ScrolledCanvas(root, 400, 300, 'white', 'hand2')
app.pack()
root.mainloop()

I just added the __getattr__ method, and the program crashed in the Canvas constructor. There is apparently a call to self.__nonzero__ somewhere in Tkinter.py, and since the constructor hasn't exited yet, sel.fcanvas isn't defined yet, so __getattr__ recurses indefinitely.

I fixed this as you see, by defining self.__nonzero__ before the call to the constructor. Now, I have two questions:

1. I originally defined self.__nonzero__ = lambda x: True, on the assumption that when self.__nonzero__ was called, the interpreter would pass self as an argument. Wrong. No arguments were passed. Why is this?

2. I find this solution rather unsatisfactory, since there's a rather obscure line of code here. I tried eliminating the def of __gertattr__ and the definition of self.__nonzero__ and adding this line after the constructor:

self.__getattr__= lambda name: getattr(self.canvas, name)

This get through the constructor all right, but crashes with the message that a ScrolledCanvas object has no create_text attribute. (I've tried passing two arguments to the lambda, but it makes no difference.)

I don't understand what's going on at all. Can't I dynamically define __getattr__? How should I go about it? By the way, another thing that didn't work was calling the method delegate instead of __getattr__. Then after the constructor call, I wrote
self.__getattr__ = self.delegate. This crashed as before on self.create_text.

I just tried a little experiment with __add__ and had no success, so I guess my problem is with overloaded operators in general.

I'd really appreciate an explanation, or a pointer to relevant documentation.
 
C

Chris Angelico

class ScrolledCanvas(Frame):
def __init__(self, master, width, height, bg, cursor):
canv = self.canvas = Canvas(self, bg=bg, relief=SUNKEN)

def __getattr__(self, name):
return getattr(self.canvas, name)

Trying to get my head around what you're doing here. Why are you
inheriting Frame, but passing all attribute queries through to the
Canvas? Why not inherit Canvas?

It looks to me like you're going to have some kind of bootstrap
problem no matter how you do it. You're creating a cyclic reference
(you pass self to Canvas), so one way or another, you need to start
the loop.

Dunder methods (like __getattr__) are looked up in the class, not the
instance, so you can't simply set it in the way you describe. I think
your best bet is going to be the "set up a stub, then fill in the
details" method, which is more or less what you're doing (a stubby
__nonzero__).

ChrisA
 
T

Terry Reedy

To the good people on comp.lang.python:

I have the following Tkinter class (python 2.7.3):

from Tkinter import *

class ScrolledCanvas(Frame): def __init__(self, master, width,
height, bg, cursor): Frame.__init__(self, master) self.__nonzero__ =
lambda: True canv = self.canvas = Canvas(self, bg=bg, relief=SUNKEN)
# self.__getattr__ = lambda x, name: getattr(self.canvas, name)
canv.config(width=width, height=height) # display area
size canv.config(scrollregion=(0, 0, width, height)) # canvas size
corners canv.config(highlightthickness=0) # no pixels
to border

ybar = Scrollbar(self) ybar.config(command=canv.yview)
# xlink sbar and canv canv.config(yscrollcommand=ybar.set)
# move one moves other

xbar = Scrollbar(self) xbar.config(command=canv.xview)
# xlink sbar and canv canv.config(xscrollcommand=xbar.set)
# move one moves other

canv.grid(row = 0, column = 0, sticky = 'news') ybar.grid(row = 0,
column = 1, sticky = 'ns') xbar.grid(row = 1, column = 0, sticky =
'ew') self.rowconfigure(0, weight = 1) self.columnconfigure(0, weight
= 1)

self.create_text(20, 20, text = 'Did it!', fill = 'red')

def __getattr__(self, name): return getattr(self.canvas, name)

root = Tk() app = ScrolledCanvas(root, 400, 300, 'white', 'hand2')
app.pack() root.mainloop()

I just added the __getattr__ method, and the program crashed in the
Canvas constructor. There is apparently a call to self.__nonzero__
somewhere in Tkinter.py, and since the constructor hasn't exited yet,
sel.fcanvas isn't defined yet, so __getattr__ recurses indefinitely.

I fixed this as you see, by defining self.__nonzero__ before the call
to the constructor. Now, I have two questions:

1. I originally defined self.__nonzero__ = lambda x: True, on the
assumption that when self.__nonzero__ was called, the interpreter
would pass self as an argument. Wrong. No arguments were passed.
Why is this?

Because you made __nonzero__ an instance function attribute instead of
an instance method class attribute as would be the case if you wrote

def __nonzero__(self): return True

outside of __init__.
2. I find this solution rather unsatisfactory, since there's a rather
obscure line of code here. I tried eliminating the def of
__gertattr__ and the definition of self.__nonzero__ and adding this
line after the constructor:

self.__getattr__= lambda name: getattr(self.canvas, name)

I presume __getattr__ is only looked up on the class and never on the
instance, not even as a backup.
This get through the constructor all right, but crashes with the
message that a ScrolledCanvas object has no create_text attribute.
(I've tried passing two arguments to the lambda, but it makes no
difference.)

If you are just starting out, consider 3.3 unless you really have to use
2.7.
 
S

Steven D'Aprano

I don't understand what's going on at all. Can't I dynamically define
__getattr__? How should I go about it?

Special "dunder" methods (DoubleUNDERscore) are looked up only on the
class, not on instances. That means that if you try to dynamically
provide a dunder method by assigning it to an instance, say with:

self.__nonzero__ = lambda self: True

it will *not* be automatically used by Python. The only way to
dynamically add dunder methods is to add them to the class, but of course
that means that all instances see the same method.


In your case, you try doing this inside the __init__:

self.__getattr__ = lambda x, name: getattr(self.canvas, name)


Why not just define a __getattr__ method the normal way? In your class,
define a method:

def __getattr__(self, name):
return getattr(self.canvas, name)

This technique is called automatic delegation.

Even if this does not quite do what you are trying to do, you will
eliminate one major stumbling block and be that much closer to a working
solution.

By the way, another thing that
didn't work was calling the method delegate instead of __getattr__.
Then after the constructor call, I wrote self.__getattr__ =
self.delegate. This crashed as before on self.create_text.

It is pointless to tell us that Python "crashed" if you don't show us
*exactly* what you did, by copying and pasting the *actual* code,
complete with the full traceback. Otherwise we are just guessing what you
did and what error you saw.

I'm pretty confident that Python didn't "crash", in the commonly accepted
meaning of the word meaning a core dump or equivalent. I'm guessing you
meant that Python raised a perfectly normal exception, like


Traceback (most recent call last):
...
NameError: name 'self' is not defined


If you pay attention to the exception messages that Python prints for
you, you will not only find it easier to debug your code, but you can
also ask more sensible questions using accepted terminology.
 
S

Saul Spatz

Special "dunder" methods (DoubleUNDERscore) are looked up only on the

class, not on instances. That means that if you try to dynamically

provide a dunder method by assigning it to an instance, say with:



self.__nonzero__ = lambda self: True



it will *not* be automatically used by Python. The only way to

dynamically add dunder methods is to add them to the class, but of course

that means that all instances see the same method.





In your case, you try doing this inside the __init__:



self.__getattr__ = lambda x, name: getattr(self.canvas, name)





Why not just define a __getattr__ method the normal way? In your class,

define a method:



def __getattr__(self, name):

return getattr(self.canvas, name)



This technique is called automatic delegation.



Even if this does not quite do what you are trying to do, you will

eliminate one major stumbling block and be that much closer to a working

solution.











It is pointless to tell us that Python "crashed" if you don't show us

*exactly* what you did, by copying and pasting the *actual* code,

complete with the full traceback. Otherwise we are just guessing what you

did and what error you saw.



I'm pretty confident that Python didn't "crash", in the commonly accepted

meaning of the word meaning a core dump or equivalent. I'm guessing you

meant that Python raised a perfectly normal exception, like





Traceback (most recent call last):

...

NameError: name 'self' is not defined





If you pay attention to the exception messages that Python prints for

you, you will not only find it easier to debug your code, but you can

also ask more sensible questions using accepted terminology.

Thanks. The class versus instance lookup explains it.

I didn't mean that python crashed, but that my app did.

Now I have another question. If dunder methods are looked up only in the class, not the instance, why did defining __nonzero__ the way I did work? Shouldn't I have had to define it with a def? Is __nonzero__ a special case?
 
P

Peter Otten

Saul said:
Now I have another question. If dunder methods are looked up only in the
class, not the instance, why did defining __nonzero__ the way I did work?
Shouldn't I have had to define it with a def? Is __nonzero__ a special
case?

Unfortunately the situation is a bit more complex. Classic classes (like
Tkinter.Frame) behave differently from newstyle classes (subclasses of
object):
.... print "nonzero"
.... return 0
....
nonzero
True
False

So Steven is wrong here.
Shouldn't I have had to define it with a def?

If you mean as opposed to a lambda, there is no difference between

f = lambda ...

and

def f(...): ...

other than that the last one gives you a nice name in a traceback.
 
S

Steven D'Aprano

Peter said:
Saul said:
Now I have another question. If dunder methods are looked up only in the
class, not the instance, why did defining __nonzero__ the way I did work?
Shouldn't I have had to define it with a def? Is __nonzero__ a special
case?

Unfortunately the situation is a bit more complex. Classic classes (like
Tkinter.Frame) behave differently from newstyle classes (subclasses of
object): [...]
So Steven is wrong here.

Peter is correct -- I've been using Python 3 too much, and completely forgot
about old-style classic classes, which only exist in Python 2.

Sorry for any confusion.

Nevertheless, everything I said about dunder methods applies to "new style"
classes in Python 2, and all classes in Python 3.
 
S

Saul Spatz

Thanks, Peter. I realize this is getting sort of academic now, as I know how to do exactly what I want, but I'm still confused. Is __getattr__ a special case then, even for classic classes?

class Adder(): # python 2.7, classic class
def __init__(self, x):
self.x = x
self.__add__= lambda other: Adder(self.x+other.x)
self.__getattr__ = lambda name: self.test(name)

def __str__(self):
return str(self.x)

def test(self, name):
print("Hello from test")
raise AttributeError

x = Adder(3)
y = Adder(4)
print(x+y)
x.junk()

7
Traceback (most recent call last):
File "C:\Users\Saul\Documents\PythonProjects\test.py", line 18
AttributeError: Adder instance has no attribute 'junk'

Why does this work for __add__ and not for __getattr__?

Of course, they both work if I write instead

def __add__self, other):
return Adder(self.x+other.x)

def __getattr__(self, name):
print(name)
raise AttributeError

like a sensible person.

Saul
 
S

Saul Spatz

Thanks, Peter. I realize this is getting sort of academic now, as I know how to do exactly what I want, but I'm still confused. Is __getattr__ a special case then, even for classic classes?

class Adder(): # python 2.7, classic class
def __init__(self, x):
self.x = x
self.__add__= lambda other: Adder(self.x+other.x)
self.__getattr__ = lambda name: self.test(name)

def __str__(self):
return str(self.x)

def test(self, name):
print("Hello from test")
raise AttributeError

x = Adder(3)
y = Adder(4)
print(x+y)
x.junk()

7
Traceback (most recent call last):
File "C:\Users\Saul\Documents\PythonProjects\test.py", line 18
AttributeError: Adder instance has no attribute 'junk'

Why does this work for __add__ and not for __getattr__?

Of course, they both work if I write instead

def __add__self, other):
return Adder(self.x+other.x)

def __getattr__(self, name):
print(name)
raise AttributeError

like a sensible person.

Saul
 
P

Peter Otten

Saul said:
Thanks, Peter. I realize this is getting sort of academic now, as I know
how to do exactly what I want, but I'm still confused. Is __getattr__ a
special case then, even for classic classes?

Well, it never occured to me to try a per-instance __getattr__(), but you
are about to answer your own question:
class Adder(): # python 2.7, classic class
def __init__(self, x):
self.x = x
self.__add__= lambda other: Adder(self.x+other.x)
self.__getattr__ = lambda name: self.test(name)

def __str__(self):
return str(self.x)

def test(self, name):
print("Hello from test")
raise AttributeError

x = Adder(3)
y = Adder(4)
print(x+y)
x.junk()

7
Traceback (most recent call last):
File "C:\Users\Saul\Documents\PythonProjects\test.py", line 18
AttributeError: Adder instance has no attribute 'junk'

Why does this work for __add__ and not for __getattr__?

I don't know, I wasn't around when these decisions were made. It could be
the initial performance tweak that would lead to a generalisation with
newstyle classes. Or it is some kind of bootstrapping issue...
 

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,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top