The right way to 'call' a class attribute inside the same class

Steve D'Aprano steve+python at pearwood.info
Sun Dec 11 20:34:04 EST 2016


On Mon, 12 Dec 2016 07:10 am, 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


If you want to distinguish between an instance attribute and a class
attribute, you must specify the instance or the class:

    self.serial # may be the instance attribute, or the class attribute
    Box.serial  # always the class attribute


But what happens inside a subclass?

class BoxWithLid(Box):
    pass

Generally we expect methods called from the subclass BoxWithLid to refer to
the BoxWithLid attribute, not the Box attribute. So Box.serial will be
wrong when the method is called from a subclass.

    type(self).serial  # still correct when called from a subclass

> The instructor said that the right way to call a class attribute is to use
> 'Class.class_attr' notation, 

That is nearly always wrong, since it will break when you subclass. If you
do that, you should document that the class is not expected to be
subclasses, and may not work correctly if you do.


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

Assignment to self.serial will always create or affect an instance
attribute. But merely retrieving self.serial will use inheritance to try
returning the instance attribute *if it exists*, and if not, fall back on
the class attribute (or a superclass).

This is especially useful for read-only defaults, constants or configuration
settings.


class Document:
    page_size = A4

    def page_area(self):
        dimensions = list(self.page_size)
        dimensions[0] -= self.left_margin
        dimensions[1] -= self.right_margin
        dimensions[2] -= self.top_margin
        dimensions[3] -= self.bottom_margin
        return dimensions


doc = Document()  # uses the default page size of A4

doc.page_size = Foolscape  # override the default


It is a matter of taste and context whether you do this, or the more
conventional way:

class Document:
    def __init__(self):
        self.page_size = A4


Use whichever is better for your specific class.


So... in summary:


When *assigning* to an attribute:

- use `self.attribute = ...` when you want an instance attribute;

- use `Class.attribute = ...` when you want a class attribute in 
  the same class regardless of which subclass is being used;

- use `type(self).attribute = ...` when you want a class attribute
  in a subclass-friendly way.


When *retrieving* an attribute:

- use `self.attribute` when you want to use the normal inheritance
  rules are get the instance attribute if it exists, otherwise a
  class or superclass attribute;

- use `type(self).attribute` when you want to skip the instance
  and always return the class or superclass attribute.




-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list