__getattr__ Confusion

Discussion in 'Python' started by Saul Spatz, Feb 4, 2013.

  1. Saul Spatz

    Saul Spatz Guest

    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.
    Saul Spatz, Feb 4, 2013
    #1
    1. Advertising

  2. On Mon, Feb 4, 2013 at 12:08 PM, Saul Spatz <> 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
    Chris Angelico, Feb 4, 2013
    #2
    1. Advertising

  3. Saul Spatz

    Terry Reedy Guest

    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
    Terry Reedy, Feb 4, 2013
    #3
  4. 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
    Steven D'Aprano, Feb 4, 2013
    #4
  5. Saul Spatz

    Saul Spatz Guest

    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?
    Saul Spatz, Feb 4, 2013
    #5
  6. Saul Spatz

    Peter Otten Guest

    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.
    Peter Otten, Feb 4, 2013
    #6
  7. 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
    Steven D'Aprano, Feb 4, 2013
    #7
  8. Saul Spatz

    Saul Spatz Guest

    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

    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.
    Saul Spatz, Feb 4, 2013
    #8
  9. Saul Spatz

    Saul Spatz Guest

    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

    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.
    Saul Spatz, Feb 4, 2013
    #9
  10. Saul Spatz

    Peter Otten Guest

    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.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...
    Peter Otten, Feb 4, 2013
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Greg Brunet

    __getattr__ weirdness

    Greg Brunet, Aug 22, 2003, in forum: Python
    Replies:
    3
    Views:
    338
    Greg Brunet
    Aug 22, 2003
  2. Anand
    Replies:
    0
    Views:
    410
    Anand
    Dec 18, 2003
  3. Samuel Kleiner

    Operator overloading and __getattr__

    Samuel Kleiner, Dec 22, 2003, in forum: Python
    Replies:
    6
    Views:
    1,904
    Shalabh Chaturvedi
    Jan 13, 2004
  4. Holger Joukl

    py2.1->py2.3.3 __getattr__ confusion

    Holger Joukl, Jul 2, 2004, in forum: Python
    Replies:
    1
    Views:
    308
    Peter Otten
    Jul 2, 2004
  5. Holger Joukl
    Replies:
    2
    Views:
    254
    Michael Hudson
    Jul 9, 2004
Loading...

Share This Page