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? This is clearly wrong. The local override for the LOAD_ATTR opcode should NOT apply to proxy methods except where explicitly requested. Also sometimes you are supposed to call the dunder directly, like in the above example. It's not *bad* to do it if you know what you're doing. 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. Your extension methods shouldn't propagate to proxy objects. 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. 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. 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!) On 2021-06-22 6:57 a.m., Steven D'Aprano wrote: > I'm sorry Soni, I don't understand what you are arguing here. See below. > > > On Mon, Jun 21, 2021 at 10:09:17PM -0300, Soni L. wrote: > > > > > > On 2021-06-21 9:39 p.m., Steven D'Aprano wrote: > > > > > > > > > > Fourth step is that you go ahead and use lists as normal. Whether you > > > use getattr or dot syntax, any extension methods defined in spam.py will > > > show up, as if they were actual list methods. > > > > > > hasattr([], 'head') # returns True > > > list.tail # returns the spam.tail function object (unbound method) > > > > > > They're not monkey-patched: other modules don't see that. > > > > > > > > > > Python is a dynamic language. Maybe you're using hasattr/getattr to > > forward something from A to B. If "other modules don't see that" then > > this must work as if there were no extension methods in place. > > What's "forward something from A to B" mean? What are A and B? > > If "this" (method lookups) "must work as if there were no extension > methods in place" then extension methods are a no-op and are pointless. > You write an extension method, register it as applying to a type, the > caller opts-in to use it, and then... nothing happens, because it "must > work as if there were no extension methods in place". > > Surely that isn't what you actually want to happen. But if not, I have > no idea what you mean. > > The whole point of extension methods is that once the caller opts in to > use them, method look ups (and that includes hasattr and getattr) must > work as if the extension methods **are in place**. > > The must be no semantic difference between: > > obj.method(arg) > > and > > getattr(obj, 'method')(arg) > > regardless of whether `method` is a regular method or an extension > method. > > > > So you > > actually wouldn't want the local load_attr override to apply to those. > > If you did... well, just call the override directly. > > I have no idea what that means. What is "the local load_attr override"? > > > > If the override was > > called __opcode_load_attr_impl__ you'd just call > > __opcode_load_attr_impl__ directly instead of going through getattr. > > As a general rule, you should not be calling dunders directly. > > You seem to have missed the point that extension methods are intended as > a mechanism to **extend a type** by giving it new methods on an opt-in > basis. I want to call them "virtual methods" except that would add > confusion regarding virtual subclasses and ABCs etc. > > Maybe you need to read the Kotlin docs: > > https://kotlinlang.org/docs/extensions.html > > and the C# docs: > > https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods > > Wikipedia also has a broad overview from a language-agnostic > perspective: > > https://en.wikipedia.org/wiki/Extension_method > > Here's an example in TypeScript and Javascript: > > https://putridparrot.com/blog/extension-methods-in-typescript/ > > > > In particular note these comments: > > # Kotlin > "Such functions are available for calling in the usual way as if they > were methods of the original class." > > # C# > "Extension methods are only in scope when you explicitly import the > namespace into your source code with a using directive." > > Both C# and Kotlin are statically typed languages, and Python is not, > but we ought to aim to minimise the differences in semantics. Aside from > extension methods being resolved at runtime instead of at compile time, > the behaviour ought to be as close as possible. > > Just as single dispatch in Python is resolved dynamically, but aims to > behave as close as possible to single dispatch in statically typed > languages. > > Another important quote: > > "Because extension methods are called by using instance method syntax, > no special knowledge is required to use them from client code. To enable > extension methods for a particular type, just add a `using` directive > for the namespace in which the methods are defined." > > > "No special knowledge is required" implies that, aside from the opt-in > step itself, extension methods must behave precisely the same as regular > methods. That means they will be accessible as bound methods on the > instance: > > obj.method > > and unbound methods (functions) on the type: > > type(obj).method > > and using dynamic lookup: > > getattr(obj, 'method') > > and they will fully participate in inheritance heirarchies if you have > opted in to use them. > > > > > There needs to be an escape hatch for this. > > The escape hatch is to *not* opt-in to the extension method. If the > caller doesn't opt-in, they don't get the extension methods. > > That is the critical difference between extension methods and monkey- > patching the type. Monkey-patching effects everyone. Extension methods > have to be opt-in. > > > > Or you *could* have getattr be special (called by load_attr) and > > overridable, and builtins.getattr be the escape hatch, but nobody would > > like that. > > Huh? Unless you have shadowed getattr with a module-level function, > getattr *is* builtins.getattr. > > _______________________________________________ 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/BJMOTPLDR6L7NXGYNE2GBW4XX23QEU3I/ Code of Conduct: http://python.org/psf/codeofconduct/