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