Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > __getattr__ Confusion

Reply
Thread Tools

__getattr__ Confusion

 
 
Saul Spatz
Guest
Posts: n/a
 
      02-04-2013
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.

 
Reply With Quote
 
 
 
 
Chris Angelico
Guest
Posts: n/a
 
      02-04-2013
On Mon, Feb 4, 2013 at 12:08 PM, Saul Spatz <(E-Mail Removed)> wrote:
> 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
 
Reply With Quote
 
 
 
 
Terry Reedy
Guest
Posts: n/a
 
      02-04-2013
On 2/3/2013 8:08 PM, Saul Spatz wrote:
> 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.

--
Terry Jan Reedy

 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      02-04-2013
On Sun, 03 Feb 2013 17:08:47 -0800, Saul Spatz wrote:

> 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.



--
Steven
 
Reply With Quote
 
Saul Spatz
Guest
Posts: n/a
 
      02-04-2013
On Sunday, February 3, 2013 10:35:30 PM UTC-6, Steven D'Aprano wrote:
> On Sun, 03 Feb 2013 17:08:47 -0800, Saul Spatz wrote:
>
>
>
> > 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.
>
>
>
>
>
>
>
> --
>
> Steven


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?

 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      02-04-2013
Saul Spatz wrote:

> 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):

>>> def nz():

.... print "nonzero"
.... return 0
....
>>> class Classic: pass

....
>>> c = Classic()
>>> c.__nonzero__ = nz
>>> not c

nonzero
True
>>> class New(object): pass

....
>>> n = New()
>>> n.__nonzero__ = nz
>>> not n

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.

 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      02-04-2013
Peter Otten wrote:

> Saul Spatz wrote:
>
>> 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.



--
Steven

 
Reply With Quote
 
Saul Spatz
Guest
Posts: n/a
 
      02-04-2013
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.p y", 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

On Monday, February 4, 2013 8:15:47 AM UTC-6, Peter Otten wrote:
> Saul Spatz wrote:
>
>
>
> > 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):
>
>
>
> >>> def nz():

>
> ... print "nonzero"
>
> ... return 0
>
> ...
>
> >>> class Classic: pass

>
> ...
>
> >>> c = Classic()

>
> >>> c.__nonzero__ = nz

>
> >>> not c

>
> nonzero
>
> True
>
> >>> class New(object): pass

>
> ...
>
> >>> n = New()

>
> >>> n.__nonzero__ = nz

>
> >>> not n

>
> 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.


 
Reply With Quote
 
Saul Spatz
Guest
Posts: n/a
 
      02-04-2013
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.p y", 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

On Monday, February 4, 2013 8:15:47 AM UTC-6, Peter Otten wrote:
> Saul Spatz wrote:
>
>
>
> > 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):
>
>
>
> >>> def nz():

>
> ... print "nonzero"
>
> ... return 0
>
> ...
>
> >>> class Classic: pass

>
> ...
>
> >>> c = Classic()

>
> >>> c.__nonzero__ = nz

>
> >>> not c

>
> nonzero
>
> True
>
> >>> class New(object): pass

>
> ...
>
> >>> n = New()

>
> >>> n.__nonzero__ = nz

>
> >>> not n

>
> 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.


 
Reply With Quote
 
Peter Otten
Guest
Posts: n/a
 
      02-04-2013
Saul Spatz wrote:

> 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.p y", 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...



 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Re: Re: py2.1->py2.3.3 __getattr__ confusion Holger Joukl Python 2 07-09-2004 12:41 PM
py2.1->py2.3.3 __getattr__ confusion Holger Joukl Python 1 07-02-2004 02:00 PM
Operator overloading and __getattr__ Samuel Kleiner Python 6 01-13-2004 06:39 AM
__setattr__ and __getattr__ with derived classes Anand Python 0 12-18-2003 09:36 PM
__getattr__ weirdness Greg Brunet Python 3 08-22-2003 08:29 PM



Advertisments