[Python-Dev] PEP 447: add type.locallookup (original) (raw)

Nick Coghlan ncoghlan at gmail.com
Sat Sep 14 08:30:54 CEST 2013


On 13 September 2013 22:23, Steven D'Aprano <steve at pearwood.info> wrote:

On Fri, Sep 13, 2013 at 08:42:46PM +1000, Nick Coghlan wrote:

Perhaps "getdescriptor" would work as the method name? Yes, it can technically return a non-descriptor, So technically that name is, um, what's the term... oh yes, "a lie". :-)

In this case, "getdescriptor" means "I am looking for a descriptor, please don't invoke the descriptor methods or traverse the MRO, just return the raw object", not "you must give me a descriptor".

The name suggestion comes from the fact that name bindings on the instance and names bindings on the class are different, in that only the latter participate in the descriptor protocol:

class A: ... def m(self): pass ... A.m <function A.m at 0x7fb51ad63320> a = A() a.f = A.m a.m <bound method A.m of <__main__.A object at 0x7fb51ad60550>> a.f <function A.m at 0x7fb51ad63320>

It's the same function object underneath, so what's going on?

The trick is that only types get to play the descriptor game, where the interpreter looks for get, set and delete on the returned object and invokes them with found. Ordinary instances don't have that behaviour - instead, they go through type(self) to look for descriptors, and anything they find in the instance dictionary is returned unaltered.

This difference in how attribute lookups are handled is actually the most fundamental difference between normal class instances and classes themselves (which are instances of metaclasses).

but the primary purpose is to customise the retrieval of objects that will be checked to see if they're descriptors. If that's the case, the PEP should make that clear.

Technically, that's what "Currently object.getattribute and super.getattribute peek in the dict of classes on the MRO for a class when looking for an attribute." means.

However, I agree the current wording only conveys that to the handful of people that already know exactly when in the attribute lookup sequence that step occurs, which is a rather niche audience :)

It's also why I like getdescriptor as a name - it's based on why we're doing the lookup, rather than how we expect it to be done.

[Aside: the PEP states that the method shouldn't invoke descriptors. What's the reason for that? If I take the statement literally, doesn't it mean that the method mustn't use any other methods at all? Surely that can't be what is intended, but I'm not sure what is intended.]

It means it shouldn't invoke get, set or delete on the returned object, since that's the responsibility of the caller.

For example, there are times when a descriptor will be retrieved to check for the presence of a set or delete method, but never actually have its methods invoked because it is shadowed in the instance dictionary:

class Shadowable: ... def get(self, *args): ... print("Shadowable.get called!") ... class Enforced: ... def get(self, *args): ... print("Enforced.get called!") ... def set(self, *args): ... print("Enforced.set called!") ... class Example: ... s = Shadowable() ... e = Enforced() ... def getattribute(self, attr): ... print("Retrieving {} from class".format(attr)) ... return super().getattribute(attr) ... x = Example() x.s Retrieving s from class Shadowable.get called! x.s = 1 x.s Retrieving s from class 1

This is the key line: we retrieved 's' from the class, but didn't invoke the get method because it was shadowed in the instance dictionary.

It works this way because if the descriptor defines set or delete, then Python will ignore the instance variable:

x.e Retrieving e from class Enforced.get called! x.dict["e"] = 1 Retrieving dict from class x.e Retrieving e from class Enforced.get called!

So my proposed name is based on the idea that what Ronald is after with the PEP is a hook that only gets invoked when the interpreter is doing this hunt for descriptors, but not for ordinary attribute lookups.

It won't be invoked when looking for ordinary attributes in an instance dict, but will be invoked when looking on the class object. Just to be clear, if I have: instance = MyClass() x = instance.name and "name" is found in instance.dict, then this special method will not be invoked. But if "name" is not found in the instance dict, then "name" will be looked up on the class object MyClass, which may invoke this special method. Am I correct?

Well, that's my proposal. While re-reading the current PEP, I realised my suggested change actually goes quite a bit further than just proposing a different name: unlike the current PEP, my advice is that the new hook should NOT be invoked for instance attribute lookups and should not replace looking directly into the class dict. Instead, it would be the descriptor lookup counterpart to getattr: whereas getattr only fires for lookups on instances of the class, getdescriptor would only fire for lookups on the class itself (so, almost exactly equivalent to defining getattr on the metaclass, but without needing a custom metaclass). A class that wanted to affect both would be able to define getdescriptor for the fallback lookup on the class and then call it from getattr.

This also suggests to me that getdescriptor should be an implicit static method like new (it would also be possible to make it an implicit classmethod, but an implicit staticmethod would be more consistent with the way new works, so if we're going to have implicit magic, it may as well be consistent implicit magic):

class C: ... def new(cls): ... print(cls) ... return super().new(cls) ... C() <class '__main__.C'> <__main__.C object at 0x7fb51ad60c50> class D(C): pass ... D() <class '__main__.D'> <__main__.D object at 0x7fb51ad60c90> C.new <function C.__new__ at 0x7fb51ad638c0> C().new <class '__main__.C'> <function C.__new__ at 0x7fb51ad638c0>

Cheers, Nick.

-- Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia



More information about the Python-Dev mailing list