Classes - converting external function to class's method.

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Dec 14 12:28:00 EST 2014


Ivan Evstegneev wrote:

> Hello Everyone,
> 
> I have stuck a bit with this example(took this one from the book).

The example you are working on (adding external functions as methods) is
actually a bit more complicated than it seems, as you have discovered. You
have this:

class rec: pass
rec.name = 'Bob'
rec.age = 40

x= rec()

def uppername(obj):
    return obj.name.upper()

rec.method = uppername


This is almost equivalent to the following:

class rec:
    name = 'Bob'
    age = 40
    def method(obj):
        return obj.name.upper()


You then try to use this new method, by calling it from the instance:

x.method()
--> returns 'BOB' as you expected


In this case, it works as you expect. Python looks up "method" on the
instance, and doesn't find anything attached to the instance. It then looks
at the class "rec", which does have something called "method". Because this
is a function, Python knows to provide the instance x as the first
argument. So the call

    x.method()

ends up turning into this:

   rec.method(x)

Your function uppername() receives x as the argument "obj", it looks up
obj.name (x.name) and returns it as uppercase.

The important part to see here is that when you call a method from an
instance:

    instance.method()

Python *automatically* fills in the "self" argument using the instance. (In
your case, you called that argument "obj" instead of self, but Python
doesn't care what it is called.)

How does Python know to use x as self? Because it's written right there:
x.method uses x as the first argument for method. But this is only done for
*instances*, not classes.


Now you move on to calling the method from the class itself:

rec.method()
--> raises TypeError

rec.method(rec)
--> returns 'BOB'

Looking at this, I can guess you are using Python 3, because that won't work
in Python 2. Am I right?

When you call a method from the class, Python cannot fill in the "self"
argument because it doesn't know what instance to use. This is called
an "unbound method" in Python 2, but in Python 3 it is just a regular
function. 

The important thing here are these two rules:

* when you call a method from an instance, Python automatically binds 
  that instance to the first argument (usually called "self") of the 
  method:

  instance.method() --> method gets instance as the "self" argument

* when you call a method from the class, Python cannot bind an 
  instance to "self", so you have to do it:

  Class.method()  --> no argument is given!

  Class.method(instance)  --> method gets instance as "self"


In your example, you didn't actually pass an instance, you just passed the
class itself:

    rec.method(rec)

But that's okay, because in *this case* the class has a "name" attribute, so
it all works out alright. But normally this would fail.

The actual mechanism for how Python does this is called the descriptor
protocol, and if you look it up you can find some explanations for it, but
be warned that it is quite technical and it is only for very advanced work
that you need to care about it.


> Keeping it in mind, I wrote this code:
> 
> class myclass:
>     def __init__(self, value):
>         self.name = value
>     def myupper(self):
>         return self.name.upper()
> 
> And then:
> 
> b = myclass('bob')
> b.myupper()
> 'BOB'
> 
> I made an assumption that this is what happens(roughly speaking) when I
> write this:
> 
> rec.method = uppername
> 
> Am I right? (close enough maybe?)

Close enough.



> On the other hand, when I checked further:
> 
>>>>dir(x.method)
> [........, '__self__',.......]

That is because you are looking the method up on the instance, x. If you
look it up on rec, there is no instance so Python cannot give it an
__self__ attribute.


> but
>>>>dir(rec.method)
> [.... list of method and  '__self__'  wasn't there.....]

Exactly!

You are looking at a complicated part of Object Oriented Programming, as
done by Python, so it is not surprising if it takes a while to understand
this. Please feel free to ask more questions if anything is unclear.


> I got a "bit" confused.
> 
> Why  actually in instance "x"
>>>>x.method()
> can auto-assign an "x" to uppername() function argument, but it doesn't
> work in a class "rec" itself?

When you have an instance

    x.method

then Python knows which instance to use, because it is right there: x.

But if you have no instance, only the class:

    rec.method

how does Python know which instance to use? There could be a million
instances, or none.

Let us look at some strings: the class is called "str", and we can have
many, many different instances:

"hello".upper()
--> returns "HELLO"

"goodbye".upper()
--> returns "GOODBYE"


But if I call the method from the class, which instance should it use?

str.upper()
???? return "HELLO" or "GOODBYE" or "1234" or "ABCD" or ... ????

The only way is for you to tell Python which instance to use:

str.upper("hello")  # just like "hello".upper()
--> returns "HELLO"



> So  I need to write down an "obj" argument manually.
> Why '__self__' exists only in instance "x"? (maybe because "rec",
> initially was created as an empty class?)

No. __self__ exists when you call the method from x, because __self__ is set
to x. If you call it from the class rec, there is no instance so __self__
cannot be set to an instance.


> How does a function "uppername()" looks like, when I apply it to "rec"
> class, so it becomes  a class's method?

I don't understand this question.


> So in such a case, when instance "x" receives an "update" it reads the new
> method as I described above(I mean the code of "myclass")?

Or this question. Can you explain what you mean?




-- 
Steven




More information about the Python-list mailing list