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?

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.


> 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:

- 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.


> 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


-- 
Steve
_______________________________________________
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/VLN75QKTOCLKTSTPB5ETIHSPM4GGZITA/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to