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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/TUFQWWAIBMTI2SUZUIHDTSZPQCXLOGMW/
Code of Conduct: http://python.org/psf/codeofconduct/