3

Here is a snippet of example from Mark Lutz's book "Learning Python". I found it difficult to understand as to how name accesses are translated into getattr() calls in the metaclass:

>>> class A(type):
        def __getattr__(cls, name): 
            return getattr(cls.data, name) 

>>> class B(metaclass=A):                   
        data = 'spam'
>>> B.upper()
'SPAM'
>>> B.upper
<built-in method upper of str object at 0x029E7420>
>>> B.__getattr__
<bound method A.__getattr__ of <class '__main__.B'>>

>>> B.data = [1, 2, 3]
>>> B.append(4)
>>> B.data
[1, 2, 3, 4]
>>> B.__getitem__(0)
1
>>> B[0]
TypeError: 'A' object does not support indexing

I have the following questions:

  1. how does B.upper() yield 'SPAM'? Is it because B.upper() => A.__getattr__(B, upper()) => getattr(B.data, upper())? but a call like getattr('spam', upper()) gives error "NameError: name 'upper' is not defined"

  2. what path does B.upper go to yiled <built-in method upper of str object at 0x029E7420>. does it go through getattr too, what is the true value of the arguments?

  3. Does B.append(4) go through A.__getattr__(cls, name)? if it does, what is the true values of the arguments in getattr(cls.data, name) in this case?

  4. how does B.__getitem__(0) yield 1? what is the true values of the arguments in getattr(cls.data, name) in this case?

techie11
  • 1,243
  • 15
  • 30
  • using `B[0]` does not look up `B.__getitem__` but rather `type(B).__getitem__(B, 0)`, everything else is explained by the [method resolution order](http://stackoverflow.com/questions/1848474/method-resolution-order-mro-in-new-style-python-classes) – Tadhg McDonald-Jensen Jun 15 '16 at 04:54
  • `B.upper()` => `A.__getattr__(B, "upper")()` – spectras Jun 15 '16 at 04:58
  • Would you mind elaborating the MRO in the context of this example?My difficulty is more on the format, or the true value of the argument, of getattr() calls in the metaclass which various name accesses are translated to. – techie11 Jun 15 '16 at 05:02
  • Thank you spectras. That answered my question 1 and 3. would you care to explain question 2 and 4? – techie11 Jun 15 '16 at 05:12
  • I think that answered my question 1, 3 and 4. would you care to explain question 2? – techie11 Jun 15 '16 at 05:18
  • I think I got it. it's getattr('spam', "upper"). Thanks a lot, spectras – techie11 Jun 15 '16 at 05:23

2 Answers2

3
  1. B.upper() first looks up B.upper and then calls it with no arguments. B.upper is looked up by trying several options in a certain order, eventually trying type(B).__getattr__(B, 'upper'), which in this case is A.__getattr__(B, 'upper'), which returns 'spam'.upper.
  2. As mentioned above, B.upper goes through several options, in this case reaching type(B).__getattr__(B, 'upper') which is A.__getattr__(B, 'upper').
  3. Yes, in this case, B.append will reach A.__getattr__(B, 'append') which will return B.data.append.
  4. B.__getitem__(0) will in this case look up B.__getitem__ and find it via A.__getattr__(B, '__getitem__') which will return B.data.__getitem__.

Also, note the final example, B[0], doesn't work because the B class doesn't directly define a __getitem__ method. This is because "special" methods, such as __getitem__, are looked up differently when used via their special syntax, such as B[0] in this case.

taleinat
  • 8,441
  • 1
  • 30
  • 44
  • My question 2 is: how does B.upper yiled ? does it go through A.__getattr__ and then getattr too? if so what is the true value of getattr's arguments? – techie11 Jun 15 '16 at 05:20
  • 1
    This. Actually, questions #1 and #2 are the same. #2 yields the method, and #1, by adding the parenthesizes, calls it. Parenthesizes are not a special syntax in python, they just mean *“take what's on the left and try to invoke it as a function, passing those arguments”*. – spectras Jun 15 '16 at 05:28
  • @AlexL, if you find that this answer does answer your question, please accept it. – taleinat Jun 15 '16 at 06:18
1

First you don't need to add the usual confusion of a meta class to get this behaviour, you can just as easily use a regular class and an instance to use as an example:

class A():
    def __getattr__(cls, name): 
        return getattr(cls.data, name) 

B = A()
B.data = "spam"

>>> B.data
'spam'
>>> B.upper
<built-in method upper of str object at 0x1057da730>
>>> B.upper()
'SPAM'
>>> B.__getitem__
<method-wrapper '__getitem__' of str object at 0x1057da730>
>>> B.__getitem__(0)
's'
>>> B[0]
Traceback (most recent call last):
  File "<pyshell#135>", line 1, in <module>
    B[0]
TypeError: 'A' object does not support indexing

Next keep in mind that B[0] does not look up B.__getitem__ using your special method but rather tries to access it directly on type(B) which does not have a __getitem__ so the indexing fails.

  1. how does B.upper() yield 'SPAM'? Is it because B.upper() => A.getattr(B, upper()) => getattr(B.data, upper())? but a call like getattr('spam', upper()) gives error "NameError: name 'upper' is not defined"

getattr('spam', upper()) does not make any sense, the name of the attribute is always a string, so using B.upper (no calling yet) would be equivelent to getattr(B, "upper") then you call the method.

  1. what path does B.upper go to yiled . does it go through getattr too, what is the true value of the arguments?

Is there any reason you are not just adding a print statement to check?

class A():
    def __getattr__(cls, name):
        print("calling __getattr__ for this name: %r"%name)
        return getattr(cls.data, name) 
>>> B = A()
>>> B.data = "spam"
>>> B.upper
calling __getattr__ for this name: 'upper'
<built-in method upper of str object at 0x1058037a0>

both 3 and 4 are answered by adding this print statement:

>>> B.data = [1,2,3,4]
>>> B.append(5)
calling __getattr__ for this name: 'append'
>>> B.__getitem__(0)
calling __getattr__ for this name: '__getitem__'
1
Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59