The right way to 'call' a class attribute inside the same class
Thomas 'PointedEars' Lahn
PointedEars at web.de
Mon Dec 12 09:34:27 EST 2016
Juan C. wrote:
> I'm watching a Python course and was presented a topic regarding classes.
> One of the examples were:
>
> box.py
>
> class Box:
> serial = 100
>
> def __init__(self, from_addr, to_addr):
> self.from_addr = from_addr
> self.to_addr = to_addr
> self.serial = Box.serial
> Box.serial += 1
>
>
> from box import *
>
> a = Box('19 Beech Ave. Seattle, WA 98144', '49 Carpenter Street North
> Brunswick, NJ 08902')
> b = Box('68 N. Church Dr. Vicksburg, MS 39180', '8 Lake Forest Road
> Princeton, NJ 08540')
>
> print(a.serial) # print: 100
> print(b.serial) # print: 101
> print(Box.serial) # print: 102
>
>
> The instructor said that the right way to call a class attribute is to use
> 'Class.class_attr' notation, but on the web I found examples where people
> used 'self.class_attr' to call class attributes. I believe that using the
> first notation is better ('Class.class_attr'), this way the code is more
> explicit, but is there any rules regarding it?
First of all, the proper term for what you are doing there is _not_ “call”;
you are _accessing_ an attribute instead.
To call something means generally in programming, and in Python, to execute
it as a function instead: In the code above, the class object referred to by
“Box” is called twice in order to instantiate twice (calling a class object
in Python means to instantiate it, implicitly calling its constructor
method, “__init__”, if any), and the global “print” function is called three
times.
Second, you do not appear to be aware that the notations
C.foo
and
self.foo
within a method of the class object referred to by “C” are _not_ equivalent:
“C” is _not_ a "more explicit" way to access the class than “self”; it is
referring to a *different* (type of) *object* instead, a *class* object.
(This is different in Python than in, for example, PHP.)
Assignment to “C.foo” modifies an attribute named “foo” of the class object
referred to “C”, i.e. it modifies the value of that attribute *for all
instances of that class* (but see below).
Assignment to “self.foo” modifies only the “foo” attribute *of a particular
instance* of the class, or adds an attribute of that name to it; namely, the
instance on which the method was called that modifies/adds the “foo”
attribute (referred to by the formal parameter “self” of the method¹).
$ python3 -c '
class C:
foo = 23
def bar (self):
self.foo = 42
def baz (self):
C.foo = 42
print(C.foo)
o = C()
o2 = C()
print(C.foo, o.foo, o2.foo)
o.bar()
print(C.foo, o.foo, o2.foo)
o.baz()
print(C.foo, o.foo, o2.foo)
'
23
23 23 23
23 42 23
42 42 42
To illustrate, the (simplified) UML diagrams² for the state of the program
after each relevant executed chunk of code (as always recommended, use a
fixed-width font for display):
#--------------------
class C:
foo = 23
def bar (self):
self.foo = 42
def baz (self):
C.foo = 42
#--------------------
,-----------------.
C ------> : :type :
:=================:
: +foo : int = 23 :
:-----------------:
: +bar() :
: +baz() :
`-----------------'
#--------------------
o = C()
#--------------------
,-----------------.
C ------> : :type :
:=================:
: +foo : int = 23 :
:-----------------:
: +bar() :
: +baz() :
`-----------------'
^
:
,-------------.
o --------> : :__main__.C :
:-------------:
`-------------'
#--------------------
o2 = C()
#--------------------
,-------------------.
C --------> : :type :
:===================:
: +foo : int = 23 :
:-------------------:
: +bar() :
: +baz() :
`-------------------'
^ ^
: :
,-------------. ,-------------.
o ---> : :__main__.C : : :__main__.C : <--- o2
:-------------: :-------------:
`-------------' `-------------'
#--------------------
o.bar()
#--------------------
,-------------------------.
C --------> : :type :
:=========================:
: +foo : int = 23 :
:-------------------------:
: +bar() :
: +baz() :
`-------------------------'
^ ^
: :
,-----------------. ,-------------.
o ---> : :__main__.C : : :__main__.C : <--- o2
:-----------------: :-------------:
: +foo : int = 42 : `-------------'
`-----------------'
#--------------------
o.baz()
#--------------------
,-------------------------.
C --------> : :type :
:=========================:
: +foo : int = 42 :
: +bar() :
: +baz() :
`-------------------------'
^ ^
: :
,-----------------. ,-------------.
o ---> : :__main__.C : : :__main__.C : <--- o2
:-----------------: :-------------:
: +foo : int = 42 : `-------------'
`-----------------'
Note that (AIUI) in this example the instances of the class referred by “C”
do not have an *own* “foo” property in the beginning, so until bar() is
called on them, they inherit that property (and its value) from that class.³
IOW, only as long as the instance on which a method is called does not have
an *own* attribute of name “foo” are the notations “C.foo” and “self.foo”,
where C is the class of that instance, equivalent in that method.
Since you cannot be certain without testing the instance and the class (and
that would be less efficient), it is prudent to refer to class attributes
through the class name and to attributes of instances through “self”.
________
¹ It should be noted that (different to, e.g., PHP), the “self” in Python
is not a predefined name, but just a code convention. In fact, if you
call a method of a class on one of its instances, the method is
implicitly passed a reference to the instance as the first argument.
Therefore,
class C:
foo = 23
def bar (x)
x.foo = 42
o = C()
o.bar()
is syntactically valid and functionally equivalent to
class C:
foo = 23
def bar (self)
self.foo = 42
o = C()
o.bar()
² Legend: X ---> Y The value of X is a reference to Y
Y
^
: X inherits from Y
X
+X X is public
X() X is a method
:X The (return) type (of the preceding) is X
X = Y The current value of X is Y
,--------.
: X : X is a class
:========:
`--------'
,--------.
: X : X is an instance of a class
:--------:
`--------'
³ How can one tell the difference in Python between a pre-initialized,
inherited attribute value and one own that is just equal to the inherited
one? In ECMAScript, this.hasOwnProperty("foo") would return “false” if
the property were inherited, “true” otherwise. But in Python,
hasattr(self, "foo") returns “True” regardless whether the “foo”
attribute of the calling instance has been assigned a value explicitly.
What is the Python equivalent of ECMAScript’s
Object.prototype.hasOwnProperty() method?
--
PointedEars
Twitter: @PointedEars2
Please do not cc me. / Bitte keine Kopien per E-Mail.
More information about the Python-list
mailing list