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