On 2021-06-23 5:21 a.m., Steven D'Aprano wrote: > On Tue, Jun 22, 2021 at 08:44:56AM -0300, Soni L. wrote: > > > Oh this is a long one. > > > > Hypothetically, let's say you have a proxy object: > > > > class Foo: > > def __getattribute__(self, thing): > > return getattr(super().__getattribute__(self, "proxied"), thing) > > > > Should this really include extension methods into it by default? > > By default? Absolutely not. Extension methods are opt-in. > > > > This is clearly wrong. > > What is clearly wrong? Your question? A "yes" answer? A "no" answer? > Your proxy object?
We're saying including local extension methods into the proxy object's attribute lookup is wrong. > Soni, and Chris, you seem to be responding as if extension methods are > clearly, obviously and self-evidently a stupid idea. Let me remind you > that at least ten languages (C#, Java, Typescript, Oxygene, Ruby, > Smalltalk, Kotlin, Dart, VB.NET and Swift) support it. Whatever the pros > and cons of the technique, it is not self-evidently wrong or stupid. That's funny because we (Soni) have been arguing about and pushing for a specific implementation of them. We're not opposed to them, quite the opposite we even have an implementation we'd like to see, altho we don't really see much of a use for them ourselves. > > > The local override for the LOAD_ATTR opcode > > should NOT apply to proxy methods except where explicitly requested. > > Why not? > > Let me ask you this: > > - should proxy objects really include `__slots__` by default? > > - should they really include dynamic attributes generated by > `__getattr__`? > > - should they include attributes in the inheritence hierarchy? > > - why should extension methods be any different? > > > Let's step back from extension methods and consider a similar technique, > the dreaded monkey-patch. If I extend a class by monkey-patching it with > a new method: > > import library > library.Klass.method = patch_method > > would you expect that (by default) the patched method are invisible to > proxies of Klass? Would you expect there to be a way to "opt-out" of > proxying that method? > > I hope that your answers are "No, and no", because if either answer is > "yes", you will be very disappointed in Python. > > Why should extension methods be different from any other method? Let's > go through the list of methods which are all treated the same: Why shouldn't extension methods be different from monkey-patching? If they were to be the same, why call them something different? > > - methods defined on the class; > - methods defined on a superclass or mixin; > - methods added onto the instance; > - methods created dynamically by `__getattr__`. > > (Did I miss any?) > > And the list of those which are handled differently, with ways to > opt-out of seeing them: > > - ... um... er... > > Have I missed any? > > > > The point is that the caller using your proxy object should opt-in to > > the extension methods, rather than break with no way to opt-out of them. > > You opt-out by not opting in. > > > > Your extension methods shouldn't propagate to proxy objects. > > Fundamentally, your proxy object is just doing attribute lookups on > another object. If you have a proxy to an instance `obj`, there should > be no difference in behaviour between extension methods and regular > methods. If `obj.method` succeeds, so should `proxy.method`, because > that's what proxies do. > > The origin of obj.method should not make any difference. I'm sorry to > have to keep harping on this, but it doesn't matter to the proxy whether > the method exists in the instance `__dict__`, or the class `__dict__`, > or `__slots__`, or a superclass, or is dynamically generated by > `__getattr__`. A method is a method. > > Extension methods are methods. Extension methods are functions and are scoped like other functions. > > > > To go even further, should all your class definitions that happen to > > extend a class with in-scope extension methods automatically gain those > > extension methods? Because with actual extension methods, that doesn't > > happen. > > We might want to follow the state of the art here, assuming there was > consensus in other languages about inheriting extension methods. But I > would expect this behaviour: > > > # --- extensions.py library --- > > @extends(list) > def flatten(self): > ... > > > # --- module A.py --- > > uses extensions # opt-in to use the extension method > > class MyListA(list): > pass > > MyListA.flatten # inherits from list > > > # --- module B.py --- > > class MyListB(list): > pass > > MyListB.flatten # raises AttributeError > > > However, there may be factors I haven't considered. > > > > You can have class MyList(list): pass and other callers would > > not get MyList.flatten even with you being able to use MyList.flatten > > locally. > > > > Extension methods are more like Rust traits than inheritance-based OOP. > > I understand that Rust doesn't support inheritance at all, and that Rust > traits are more like what everyone else calls "interfaces". > > > > > Also note that they use instance method syntax, but no other. That is > > they apply to LOAD_ATTR opcodes but should not apply to getattr! > > (Indeed, reflection in C#/Kotlin doesn't see the extension methods!) > > Okay, that's a good data point. The question is, why doesn't reflection > see the extension methods? That will help us decide whether that's a > limitation of reflection in those languages, or a deliberate design > feature we should follow. > > A brief search suggests that people using C# do want to access extension > methods via reflection, and that there are ways to do so: > > https://duckduckgo.com/?q=c%23+invoke+extension+method+via+reflection > > Yes, and where they do so, they have to explicitly re-opt-in to it. And that's a good thing, because it gives you more flexibility - you're not *forced* to shadow any object methods with your extension methods when using reflection, and this can be useful when you wanna e.g. use extension methods for your own benefit and still write a programming language interpreter that integrates with the host language using reflection. The "obvious" thing is that reflection only cares about the object(s), but not the context they're in. If you want to bring in the context, you need to bring it in yourself. _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-le...@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/7L3TS2SWHUBJUHLCA2EJDNK4QX6DGYJX/ Code of Conduct: http://python.org/psf/codeofconduct/