On 2021-06-22 3:43 p.m., Brendan Barnwell wrote:
On 2021-06-22 05:14, Chris Angelico wrote:
Fair point. However, I've worked with a good number of languages that have some notion of object methods, and generally, an object has or doesn't have a method based on what the object*is*, not on who's asking.
I agree, and this is the aspect of the proposal that most confuses me. I still can't understand concretely what is being proposed, though, so I'm not sure I even understand it. Can someone clarify? Suppose I have this
### file1.py @extend(list) def len2(self): return len(self)**2
### file2.py # or whatever I do to say "I want to use extensions to list defined in file1" from file1 extend list
def coolness(some_list): return some_list.len2() + 1
my_list = [1, 2, 3] print("My list len2:", my_list.len2()) print("My list coolness:", coolness(my_list))
### file3.py import file2
other_list = [1, 2, 3, 4] print("Other list len2:", other_list.len2()) print("other list coolness:", file2.coolness(other_list)) print("My list len2 from outside:", file2.my_list.len2()) print("My list coolness from outside:", file2.coolness(file2.my_list))
What exactly is supposed to happen here if I run file3? file2 declares use of file1's extensions. file2 does not. But file3 uses a function in file2 that makes use of such extensions. Who sees the extension?
NameError, value, NameError, value, respectively.
The list object my_list in file2 is the same object accessed as file2.my_list in file3. Likewise coolness and file2.coolness. It is going to be super confusing if calling the same function object with the same list object argument gives different results depending on which file you're in. Likewise it's going to be confusing if the same list object sometimes has a .len2 method and sometimes doesn't.
It isn't the list object that has the extension method.
But if it doesn't work that way, then it would seem to mean either every module sees the extensions (even if they didn't opt in), or else my_list in file2 is not the same object as file2.my_list in file3. And that would be even worse. (In this example it may seem okay because you can ask why I would call len2 from file3 if I didn't want to use it. But what if the extension is an override of an existing method? Is that not allowed?)
In addition, if there is a difference between my_list and other_list, then that apparently means that the syntax for lists now does something different in the two files. This is maybe the most reasonable approach, since it's at least remotely reminiscent of a __future__ import, which changes syntactic behavior. But what exactly is the difference between the two objects here? Are both objects lists? If they are, then how can they have different methods? If they're not, then what are they?
Most __future__ imports don't work like this. Maybe the closest thing is the generator_stop one, but at least that places a flag on the code object to indicate the difference. Would "extended lists" have some kind of magic attribute indicating which extensions they're using? That may have been marginally acceptable in the case of PEP 479, which was essentially a bugfix, and set the attribute on code objects which are an obscure internal data structure. But allowing this kind of thing for "user-facing" objects like lists would create a profusion of different list objects with different behavior depending on some combination of attributes indicating "what extends me" --- or, even worse, create different behavior without any such overt indication of which extensions are in use for a given object.
The idea that the file in which code is written would somehow determine this type of runtime behavior seems to me to break my assumption that by knowing an object's identity I should have all the information I need to know about how to use it. Some of the posts earlier in this thread seem to suggest that somehow the module where something was defined (something --- not sure what --- maybe the object with the extended method? maybe the extended method itself?) would somehow get a hook to override attribute access on some objects (again, not sure which objects).
That to me is the exact opposite of encapsulation. Encapsulation means the object itself contains all its behavior. If there is some getattr-like hook in some other module somewhere that is lying in wait to override attribute access on a given object "only sometimes" then that's not encapsulation at all. It's almost as bad as the infamous COME FROM statement!
Existing mechanisms like __getattribute__ are not parallel at all. When you know an object's identity, you know its MRO, which tells you all you need to know about what __getattribute__ calls might happen. You don't need to know anything about where the object "came from" or what file you're using it in. But it seems with this proposal you would need to know, and that's kind of creepy to me.
Think about it like this, extension methods give you the ability to make imported functions that look like this:
look like this instead:
That's all there is to them. They're just a lie to change how you read/write the code. Some languages have an whole operator that has a similar function, where something like bar->foo(baz) is sugar for foo(bar, baz). The OP doesn't specify any particular mechanism for extension methods, so e.g. making the dot operator be implemented by a local function in the module, which delegates to the current attribute lookup mechanism by default, would be perfectly acceptable. It's like deprecating the existing dot operator and introducing a completely different one that has nothing to do with attribute lookup!