Classes - converting external function to class's method.

Thomas 'PointedEars' Lahn PointedEars at web.de
Sun Dec 14 18:15:09 EST 2014


Ivan Evstegneev wrote:

> I have stuck a bit with this example(took this one from the book).
> 
> Here are a description steps of what I've done till now:
>  
> 
> Step 1 - creating an empty namespace:
> 
>>>>class rec: pass

IMHO that is not actually creating a namespace; it is just 
declaring/defining an empty class.

The equivalent of a namespace in Python is a module; the file path starting 
from an entry in the PYTHONPATH works like a namespace (the module is 
considered to be in a package if the directory contains an __init__.py 
file).  (One can assume that this was derived from Java.)

BTW, the recommended Python 3.x way is

class Rec (object):
    pass

(whereas “pass” is a placeholder statement, a bit like the yada-yada 
operator of Python) and you should start class identifiers uppercase 
(therefore, “Rec” instead) in order to avoid name collisions with built-in 
class identifiers (“str”, “int” etc.) and make it easier to tell apart 
instantiations from function/method calls.  This recommendation applies to 
other similar OOPLs too, but ISTM is especially important in Python code 
where there is no “new” keyword used in instantiation.

> Step 4 - write down external function: 
> 
> >>>def uppername(obj):
>         return obj.name.upper()
> 
> […]
> Step 6 - Creating a class method:
> 
>>>rec.method = uppername
> […]
>>>>x.method()
> 'BOB'
> 
>>>>rec.method()
> .....
> .....
> TypeError: uppername() missing 1 required positional argument: 'obj'
> 
> and then:
> 
>>>>rec.method(rec)
> 'BOB'
> 
> So my the question is: what is going on here? How this actually works?

Short answer: Calling rec.method(x) is equivalent to calling x.method() if 
type(x) == rec.

| >>> class C (object): pass
| >>> def foo (self):
| ...     print(self)
| >>> C.foo = foo
| >>> C.foo()
| Traceback (most recent call last):
| File "<stdin>", line 1, in <module>
| TypeError: foo() missing 1 required positional argument: 'self'
| >>> o = C()
| >>> C.foo(o)
| <__main__.C object at 0x7f64a7458be0>
| >>> o.foo()
| <__main__.C object at 0x7f64a7458be0>

Long answer:

If you call that method as the method of a class (object), you need to pass 
an argument for the first parameter because you have made it positional, not 
optional (it has no default value).  If you pass (a reference to) the class 
as first argument of that method, the class is the object whose attribute is 
accessed.  Classes are objects (this, too, was probably derived from Java).  
(In fact, AFAIK almost everything is an object in Python; there are no 
primitive data types at all.)

If instead you call that method as the method of an instance, the first 
argument is a reference to that instance implicitly, so the signature of the 
method is met implicitly and there is no error.  (See below for an 
explanation as to why “self.name” still works.)  Any arguments that you pass 
then in the argument list are passed as second, third aso. argument to the 
method.  To this method, then, you MUST NOT pass other arguments because it 
does not take any more than already supplied implicitly.

[Fixed comb-quote; please keep to at most 80 characters per line, best 72]

> According to the book's downside note:
> 
> ************************************************************************
> In fact, this is one of the reasons the self argument must always be
> explicit in Python methods-because methods can be created as simple 
> functions independent of a class, they need to make the implied instance
> argument explicit. They can be called as either functions or methods,
> and Python can neither guess nor assume that a simple function might 
> eventually become a class's method.
> The main reason for the explicit self argument, though, is to make the 
> meanings of names more obvious: names not referenced through 
> self are simple variables mapped to scopes, while names referenced
> through self with attribute notation are obviously instance attributes.
> ***********************************************************************

Exactly.

> 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, maybe.  The difference is that in the original code the class has the 
attribute and the instance merely inherits it, while in the code above only 
the instance has the attribute.

IOW, in the code above the attribute (value) is not implicitly shared with 
other instances of that class.  And ISTM that with the original code it is 
not possible to modify the attribute value except by accessing it directly 
or through a method that is passed the reference to the *class* (because a 
simple assignment in a method merely defines a local variable; there appears 
to be no closure over class attributes).

Compare

,----
| >>> class C (object):
| ...     foo = 42
| ...     def bar (self):
| ...         print(self.foo)
| ...         self.foo = 23
| ... 
| >>> C.foo
| 42
| >>> o = C()
| >>> o.foo
| 42
| >>> C.foo
| 42
| >>> o.bar()
| 42
| >>> o.foo
| 23
| >>> C.foo
| 42
`----

vs.

,----
| >>> class C2 (object):
| ...     def __init__ (self):
| ...         self.foo = 42
| ... 
| >>> C2().foo
| 42
| >>> C2.foo
| Traceback (most recent call last):
|   File "<stdin>", line 1, in <module>
| AttributeError: type object 'C2' has no attribute 'foo'
`----

> On the other hand, when I checked further:
> 
>>>>dir(x.method)
> [........, '__self__',.......]
> 
> but
>>>>dir(rec.method)
> [.... list of method and  '__self__'  wasn't there.....]

,----
| >>> print(dir.__doc__)
| dir([object]) -> list of strings
| 
| If called without an argument, return the names in the current scope.
| Else, return an alphabetized list of names comprising (some of) the 
| attributes of the given object, and of attributes reachable from it.
| If the object supplies a method named __dir__, it will be used; otherwise
| the default dir() logic is used and returns:
|   for a module object: the module's attributes.
|   for a class object:  its attributes, and recursively the attributes
|     of its bases.
|   for any other object: its attributes, its class's attributes, and
    ^^^^^^^^^^^^^^^^^^^^
|     recursively the attributes of its class's base classes.
`----
 
> I got a "bit" confused.

There is no good reason that “__self__” should be an attribute of a *class* 
method or a *class*, so it most certainly is not.  On the other hand, it 
makes a lot of sense that a method of an instance has a way to refer to its 
owner.

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

Because classes and instances are not only different objects, but also 
(instances) of different (classes/)types.  It is the way Python OOP (in 
fact, its entire type system) works:

| >>> class C (object): pass
| >>> type(C)
| <class 'type'>
| >>> type(C())
| <class '__main__.C'>

> So  I need to write down an "obj" argument manually.

If you intend the function to be called as the method of an instance, you 
would be well-advised to name the parameter “self”, as recommended.  (Which 
is probably derived from Smalltalk-80 and Self, some of the first OOPLs.)

> Why '__self__' exists only in instance "x"?

If I am not mistaken, you have not yet ascertained that the attribute 
'__self__' exists in the instance at all, only that it exists as an 
attribute of the *method* of an instance.  In fact, I find:

| >>> '__self__' in dir(C())
| False

> (maybe because "rec", initially was created as an empty class?)

Unlikely.  But you can simply test that.
 
> How does a function "uppername()" looks like, when I apply it to "rec"
> class, so it becomes  a class's method?
> 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")?

def uppername (self):
    return self._name.upper()

and the like (BTDT).  (It is recommended to start identifiers of 
implementation-specific symbols, that should not be accessed from outside 
the class, with “_” – PyDev then recognizes them as “protected”.  Therefore, 
“self._name” instead.)

(I prefer the space before the parameter list in declarations/definitions in 
order to tell them apart easily from argument lists in calls/instantiations.  
YMMV.)

> Thanks a  lot in advance,

You’re welcome.

-- 
PointedEars



More information about the Python-list mailing list