On Tue, Jun 22, 2021 at 05:50:48PM +1000, Chris Angelico wrote:

> Hmm, that's not what I'd usually understand "encapsulation" to mean.
> That's what would normally be called "namespacing".

Pfft, who you going to believe, me or some random folx on the internet 
editing Wikipedia? *wink*

Okay, using the Wikipedia/OOP definition still applies. Presumably most 
extension methods are going to be regular instance methods or class 
methods, rather than staticmethod. So they will take a `self` (or `cls`) 
parameter, and presumably most such extension methods will actually act 
on that self parameter in some way.

There is your "bundling of data with the methods that operate on that 
data", as required :-)

The fact that the methods happen to be written in an separate file, and 
(in some sense) added to the class as extension methods, is neither here 
nor there. While we *could* write an extension method that totally 
ignored `self` and instead operated entirely on global variables, most 
people won't -- and besides, we can already do that with regular 
methods.

    class Weird:
        def method(self, arg):
            global data, more_data, unbundled_data, extra_data
            del self  # don't need it, don't want it
            do_stuff_with(data, more_data, unbundled_data, extra_data)


So the point is that extension methods are no less object-orientey than 
regular methods. They ought to behave just like regular methods with 
respect to encapsulation, namespacing, inheritance etc, modulo any minor 
and necessary differences.

E.g. in C# extension methods can only extend a class, not override an 
existing method.



[...]
> I don't think it's safer necessarily. With this proposal, we have the
> notion that obj.method() can mean two completely different things *at
> the same time* and *on the same object* depending on how you refactor
> the code.
> 
> # file1.py
> from file2 import func
> # and apply some extension methods
> def spamify(obj):
>     print(obj.method())
>     print(func(obj))
> 
> # file2.py
> def func(obj):
>     return obj.method()


Yes, we can write non-obvious code in any language, using all sorts of 
"confusing" techniques, especially when you do stuff dynamically.

    class K:
        def __getattr__(self, attrname):
            if attrname == 'method':
                if __name__ == '__main__':
                    raise AttributeError
                return something()


Regarding your example, you're only confused because you haven't take on 
board the fact that extension methods aren't interpreter-global, 
just module-global. Because it's new and unfamiliar. But we can do 
exactly the same thing, right now, with functions instead of methods, 
and you will find it trivially easy to diagnose the fault:


    # file1.py
    from file2 import func
    # instead of "applying an extension method" from elsewhere,
    # import a function from elsewhere
    from extra_functions import g
    def spamify(obj):
         print(g(obj)) # works fine
         print(func(obj))  # fails


    # file2.py
    def func(obj):
        return g(obj)  # NameError


This example looks easy and not the least bit scary to you because 
you've been using Python for a while and it has become second nature to 
you. But you might remember back when you were a n00b, it probably 
confused you: why doesn't `g(obj)` work when you imported it? How 
weird and confusing! What do you mean, if I want to use g, I have to 
import it in each and every module where I want to use it? That's just 
dumb. Importing g once should make it available EVERYWHERE, right?

Been there, done that.

You learned about modules and namespaces, and why Python's design is 
*safer and better* than a single interpreter-global namespace, and now 
that doesn't confuse you one bit.

And if you were using Kotlin, or C#, or Swift, or any one of a number of 
other languages with extension methods, you would likewise learn that 
extension methods work in a similar fashion. Why does obj.method raise 
AttributeError from file2? *Obviously* its because you neglected to 
"apply the extension method", duh.

That's as obvious as neglecting to import something and getting a 
NameError. Maybe even more obvious, if your IDE or linter knows about 
extension methods.

And its *safer and better* than monkey-patching.

We have two people in this thread who know Kotlin and C#, at least one 
of them is a fan of the technique. Why don't we ask them how often this 
sort of error is a problem within the Kotlin and C# communities?


> Is that really beneficial? All I'm seeing is myriad ways for things to
> get confusing - just like in the very worst examples of
> monkey-patching.
> 
> And yes, I have some experience of monkey-patching in Python,
> including a situation where I couldn't just "import A; import B", I
> had to first import a helper for A, then import B, and finally import
> A, because there were conflicting monkey-patches. But here's the
> thing: extension methods (by this pattern) would not have solved it,
> because the entire *point* of the monkey-patch was to fix an
> incompatibility. So it HAD to apply to a completely different module.

Sure. Nobody says that extension methods are a Silver Bullet that cures 
all programming ills. Some things will need a monkey-patch.

Python is great because we have a rich toolbox of tools to choose from. 
To extend a class with more functionality at runtime, we can:

- monkey-patch the class;

- subclass it;

- single or multiple inheritance;

- or a virtual subclass;

- or use it as a mixin or a trait (with third-party library support);

- use delegation and composition;

- or any one of a number of Design Patterns;

- add methods onto the instance to override the methods on the class;

- swizzling (change the instance's class at runtime to change its 
  behaviour);

- just write a function.

Have I missed anything? Probably. None of those techniques is a silver 
bullet, all of them have pros and cons. Not all of the techniques will 
work under all circumstances. We should use the simplest thing that will 
work, for whatever definition of "work" we need for that task.

Extension methods are just another tool in the tool box, good for some 
purposes, not so good for others.


> That's why, despite its problems, I still think that monkey-patching
> is the cleaner option. It prevents objects from becoming
> context-dependent.

It might be a necessary thing under rather usual circumstances, but 
under the great bulk of circumstances, it is a bad thing.

Chris, here you are defending monkey-patching, not just as a necessary 
evil under some circumstances, but as a "cleaner" option, and then in 
your very next sentence:

> And the Ruby community is starting to see the risks of
> monkey-patching.

Indeed.



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

Reply via email to