On Tue, Jun 22, 2021 at 3:43 AM Steven D'Aprano email@example.com wrote:
On Tue, Jun 22, 2021 at 01:49:56AM +1000, Chris Angelico wrote:
So what you're saying is that, in effect, every attribute lookup has to first ask the object itself, and then ask the module? Which module? The one that the code was compiled in? The one that is currently running? Both?
We don't have an implementation yet, so it is too early to worry about precisely where you look up the extension methods, except that it is opt-in (so by default, there's no additional cost involved) and there must be *some* sort of registry *somewhere* that handles the mapping of extension methods to classes.
We certainly don't want this to slow down *every method call*, but if it only slowed down method lookups a little bit when you actually used the feature, that might be acceptable. We already make use of lots of features which are slow as continental drift compared to C, because they add power to the language and are *fast enough*.
E.g. name lookups are resolved at runtime, not compile-time; dynamic attribute lookups using gettattribute and getattr dunders; virtual subclasses; generic functions (functools.singledispatch); descriptors.
I'm actually not concerned so much with the performance as the confusion. What exactly does the registration apply to? And suppose you have a series of extension methods that you want to make use of in several modules in your project, how can you refactor a bunch of method registration calls so you can apply them equally in multiple modules? We don't need an implementation yet - but we need clear semantics.
And how is this better than just using a plain ordinary function? Not everything has to be a method.
You get method syntax, obj.method, which is nice but not essential.
When a method is called from an instance, you know that the first parameter `self` has got to be the correct type, no type-checking is required. That's good. And the same would apply to extension methods.
You get bound methods as first class values, which is useful.
You get inheritance, which is powerful.
And you get encapsulation, which is important.
True, all true, but considering that this is *not* actually part of the class, some of that doesn't really apply. For instance, is it really encapsulation? What does that word even mean when you're injecting methods in from the outside?
I think this is a Blub moment. We don't think it's useful because we have functions, and we're not Java, so "not everything needs to be a method". Sure, but methods are useful, and they do bring benefits that top-level functions don't have. (And vice versa of course.)
We have staticmethod that allows us to write a "function" (-ish) but get the benefits of inheritance, encapsulation, and method syntax. This would be similar.
We acknowledge that there are benefits to monkey-patching. But we can't monkey-patch builtins and we are (rightly) suspicious of those who use monkey-patching in production. And this is good. But this would give us the benefits of monkey-patching without the disadvantages.
*If* we can agree on semantics and come up with a reasonable efficient implementation that doesn't slow down every method call.
And that's a very very big "if". Monkey-patching can be used for unittest mocking, but that won't work here. Monkey-patching can be used to fix bugs in someone else's code, but that only works here if *your* code is in a single module, or you reapply the monkey-patch in every module. I'm really not seeing a lot of value in the proposal.
Let's completely ignore the performance cost for the moment and just try to figure out semantics, with it being actually useful and not unwieldy.