On 13 September 2013 22:23, Steven D'Aprano
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
a = A() a.f = A.m a.m > a.f
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()
<__main__.C object at 0x7fb51ad60c50> class D(C): pass ... D() <__main__.D object at 0x7fb51ad60c90> C.__new__ C().__new__
Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia