[Python-Dev] PEP 447: add type.__locallookup__

Steve Dower Steve.Dower at microsoft.com
Fri Sep 13 18:19:19 CEST 2013


From: Steven D'Aprano
> On Fri, Sep 13, 2013 at 04:26:06AM +0000, Steve Dower wrote:
> 
>> Last I checked, looking up in the instance dict us exactly what it
>> does. Even the example you posted is doing that.
> 
> The example from the PEP shows:
> 
> return cls.__dict__[name]
> 
> not "self.__dict__[name]". It is true that "the instance" in this case refers to
> it being an instance of the metaclass, but that instance is, in fact, a
> class/type. That's why we normally call it "cls" in a metaclass method rather
> than "self".

Right, but where's the difference between these two?

class A:
    def __tdb__(self, name):
        if name == 'some_attribute_on_my_instance_of_A':
            return its_value
        try:
            return self.__dict__[name]
        except KeyError:
            raise AttributeError(name)

class MetaB:
    def __tdb__(cls, name):
        if name == 'some_attribute_on_my_class_B':
            return its_value
        try:
            return cls.__dict__[name]
        except KeyError:
            raise AttributeError(name)

(Yes, either of these could be written with __getattribute__, but that function cannot be called by super().)

As I see it, there are two (correct) ways to interpret what this method is for, which influences what it should be called.

1. It directly replaces obj.__dict__[name] everywhere that is done, including internally in the interpreter.
2. It is the same as __getattribute__ without the final call to object.__getattribute__

I guess it's also correct to view it as a special helper for super(), but it is more generally applicable than that.

[...]

> By the way, I think the PEP should have a more complex example. The SillyObject
> example is nice and easy to understand, but it doesn't really help with the
> motivating use-case "dynamic classes that can grow new methods on demand".
> Ronald, if you're reading this, can you add such an example please? Also,
> there's a typo in the SillyObject M method ("fourtytwo" should not have a U in
> it).

Agreed. No harm in more examples.

Here's a quick example of code that does not behave correctly at present.

class A:
    def __getattribute__(self, name):
        if name == 'foo':
            return 'A.foo'
        return object.__getattribute__(self, name)

class B(A):
    def get_foo(self):
        return super().foo

>>> B().get_foo()  # skips A.__getattribute__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in get_foo
AttributeError: 'super' object has no attribute 'foo'
>>> B().foo
A.foo

After changing to use the __tbd__ method:

class A:
    def __getattribute__(self, name):
        '''Not strictly necessary for this example, but I would expect
        that most types overriding __tbd__ also want to override
        __getattribute__ to use it. Or maybe object.__getattribute__
        should be changed to use __tbd__ too...?'''
        try:
            return self.__tbd__(name)
        except AttributeError:
            return object.__getattribute__(self, name)
    
    def __tbd__(self, name):        # CHANGED
        if name == 'foo':
            return 'A.foo'
        try:
            return self.__dict__[name]
        except KeyError:
            raise AttributeError(name)  # CHANGED

class B(A):
    def get_foo(self):
        return super().foo

>>> B().get_foo()  # does not skip A.__tbd__
A.foo              # hopefully this is the result :)
>>> B().foo
A.foo

A full example of where this may realistically be needed is longer and certainly involves metaclasses, but fundamentally it's just the same as __getattribute__ with slightly different semantics.

Cheers,
Steve


More information about the Python-Dev mailing list