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/

Reply via email to