On Thu, Jun 24, 2021 at 12:17:17AM +1000, Chris Angelico wrote:
On Wed, Jun 23, 2021 at 11:25 PM Steven D'Aprano email@example.com wrote:
To a first approximation (ignoring shadowing) every dot lookup can be replaced with getattr and vice versa:
obj.name <--> getattr(obj, 'name')
A simple source code transformation could handle that, and the behaviour of the code should be the same. Extension methods shouldn't change that.
Alright. In that case, getattr() has stopped being a function, and is now a magical construct of the compiler.
How do you come to that conclusion?
Did you miss the part where I said "To a first approximation (ignoring shadowing)"?
What I'm describing is not some proposed change, it is the status quo. getattr is equivalent to dot notation, as it always has been, all the way back to 1.5 or older. There's no change there.
getattr is a function. A regular, plain, ordinary builtin function. You can shadow it, or bind it to another name, or reach into builtins and delete it, just like every other builtin function. But if you don't do any of those things, then it is functionally equivalent to dot lookup.
What happens if I do this?
if random.randrange(2): def getattr(obj, attr): return lambda: "Hello, world"
def foo(thing): return getattr(thing, "in_order")()
According to the result of the random number generator, either the lambda will be returned by the shadowed getattr, or the attribute "in_order" will be looked up on obj.
Just like today.
If getattr is a perfectly ordinary function, as it now is, then it should be perfectly acceptable to shadow it.
It should also be perfectly acceptable to use any other way of accessing attributes - for instance, the PyObject_GetAttr() function in C.
Why should getattr() become magical?
What exactly are the semantics of getattr?
Oh gods, I don't know the exact semantics of attribute look ups now! Something like this, I think:
obj.attr (same as getattr(obj, 'attr'):
That is exactly what's weird about it. Instead of looking up the name getattr and then calling a perfectly ordinary function, now it has to be a magical construct of the compiler, handled right there. It is, in fact, impossible to craft equivalent semantics in a third-party function.
I don't think that it is impossible to emulate attribute lookup in pure Python code. It's complicated, to be sure, but I'm confident it can be done. Check out the Descriptor How To Guide, which is old but as far as I can tell still pretty accurate in its description of how attributes are looked up.
Currently, getattr() can be defined in C on top of the C API function PyObject_GetAttr, which looks solely at the object and not the execution context. By your proposal, getattr() can only be compiler magic.
The only "magic" that is needed is the ability to inspect the call stack to find out the module being called from. CPython provides functions to do that in the inspect library: `inspect.stack` and `inspect.getmodule`. Strictly speaking, they are not portable Python, but any interpreter ought to be able to provide analogous abilities.
Do you think that the functions in the gc library are "compiler magic"? It would be next to impossible to emulate them from pure Python in an interpeter-independent fashion. Some interpreters don't even have reference counts.
How about locals()? That too has a privileged implementation, capable of doing things likely impossible from pure, implementation-independent Python code. Its still a plain old regular builtin function that can be shadowed, renamed and deleted.
Aside from the possibility that it might be shadowed or deleted from builtins, can you give me any examples where `obj.attr` and `getattr(obj. 'attr')` behave differently? Even *one* example?
That is *precisely* the possibility.
And it will remain the possibility.
That is exactly why it is magical by your definition
It really won't.
But if it's possible to do a source code transformation from getattr(obj, "attr") to obj.attr, then it is no longer possible to do *ANY* of this. You can't have an alias for getattr, you can't have a wrapper around it, you can't write your own version of it.
In the absence of any shadowing or monkey-patching of builtins.