On Fri, Jan 22, 2016 at 11:10:39PM -0600, boB Stepp wrote:
> On Thu, Jan 21, 2016 at 5:49 AM, Steven D'Aprano <st...@pearwood.info> wrote:

> > class X:
> >     pass
> >
> > def func(this):
> >     print("instance %r called method" % this)
> >
> > X.method = func
> 
> Am I understanding this correctly?  It appears to me that you have
> successfully added a method to class X from outside of class X!  If
> this is the case, then this clarifies some things I was wondering
> about in my other thread.

Yes, you are reading it correctly.

Of course, in practice you wouldn't do that as shown. Why write the 
method outside the class when you could write it inside the class? There 
are some good reasons for doing something similar though:

(1) Perhaps you have a whole family of classes that share the same 
method. Three traditional solutions to this are to use inheritence, a 
mixin class, or traits. But a fourth is to create your classes without 
the method, then dynamically add the extra, shared, method into each one 
afterwards.

Possibly by using a simple decorator:

def method(self, arg): ...

def decorate(cls):
    cls.method = method
    return cls

@decorate
class Spam: ...

This technique is even more powerful when the method you are injecting 
is *slightly different* each time. For that, you can use a closure, 
created *inside* the decorator function. Play around with this example 
and see if you can understand what is going on:


# Warning: this may expand your mind.

def decorate(number):
    # Create a closure over the number.
    def method(self, x):
        """Return x + %d."""
        return x + number
    method.__doc__ %= number
    # Create a decorator.
    def decorator(cls):
        # Inject the method into the class.
        cls.method = method
        return cls
    # Return the decorator so it can be used.
    return decorator

@decorate(5)
class AddFive:
    pass

@decorate(9)
class AddNine:
    pass



(2) Another reason for doing this may be that you have an existing class 
from some library that you have to use. You can't subclass it, because 
too much of your code already depends on using that specific class. In 
some languages, like Java, perhaps the library authors marked the class 
as unsubclassable. But you want to add a new method for your own use.

Here's an example of this:

http://stackoverflow.com/questions/13730924/java-adding-fields-and-methods-to-existing-class

You'll notice that the answers given don't really solve the problem, 
apart from a vague and scary-sounding suggestion to use "byte-code 
injection". A number of people suggest subclassing, but a comment 
from another person says the the question also applies to him, but in 
his case subclassing isn't practical.

In Python, we have two solutions:

Write a function, and use that. Instead of calling obj.new_method(), 
we simply have new_function(obj). This option is available to Java as 
well, but hardly anyone ever thinks of it because Java is the Kingdom of 
Nouns: 

http://steve-yegge.blogspot.com.au/2006/03/execution-in-kingdom-of-nouns.html

Or *monkey-patch* the class using a new method we create on the outside.

https://en.wikipedia.org/wiki/Monkey_patch

Monkey-patching is a powerful technique, but should be used with care. 
Overuse is considered harmful:

http://devblog.avdi.org/2008/02/23/why-monkeypatching-is-destroying-ruby/


[...]
> I guess I am trying to wrap my mind around this incredible power and
> flexibility that Python provides.  I thought I had asked a relatively
> harmless question.  But it generated some strong responses!  It seemed
> like "self" had a similar utility of use as "print" to me.  After all,
> we can't redefine "print", can we?  But now I realize that I can do
> exactly that if I so choose.  That is both fascinating and scary!

Indeed. And in Python 3, print is a regular function, which means it 
*can* be redefined by shadowing, or even by monkey-patching the 
built-in module.


-- 
Steve
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to