What is the difference between an instance attribute/property and a class attribute/property? When do I use one and when do I use another in Python?
That is, if we have:
class Foo:
a = 5
def print_a(self):
print(self.a)
Y:
class Foo:
def __init__(self):
self.a = 5
def print_a(self):
print(self.a)
What differences are there with respect to the attribute a
?
The fundamental difference is that class attributes are shared by all instances of that class, while instance attributes are particular to each object created with that class. Thus, instance variables are for data unique to each object and class variables are for attributes that must be shared by all instances of that class.
Instance attributes
instance.attr
are created using the or syntaxself.attr
(self
it's really a convention, it's just an identifier that refers to the instance itself). As has been said, they are local to that instance and therefore only accessible from one of them.As a general rule they are created within a method. Instance attributes can be created on any instance method, but it is generally good practice to do so in the class's initializer (
__init__
) or constructor (__new__
) or at most in a method that is called directly from one of them. If this is not done (even just initializing them to a null value) the code loses readability and more importantly, the attribute does not exist until the method that creates it is called, which creates uncertainty as to whether an instance does or does not have a certain attribute at a given time.As for their utility, we can generically say that they allow defining a certain "state" for that particular object and that it differentiates it from other objects of its class. Such as the "license plate" attribute in a "Car" class.
They are stored in a dictionary (instance or object dictionary) which you can access via
NombreInstancia.__dict__
.Class attributes are defined outside of any class methods, usually just below the documentation string. They can be referenced directly from the class and can also be referenced from any instance of the class. Common uses are to create an enumerated constant, as a control variable in a Singleton, to set default values for instance variables, to define constants, etc.
They are stored in a different dictionary (class dictionary) and independent from the one used for instance attributes, which you can query through
NombreClase.__dict__
or from an instance withNombreInstancia.__class__.__dict__
. Properties and descriptors in general are also at the class level, not the instance level.It is important to know the order in which an attribute access is resolved, Python follows the following order when looking up an attribute from an object:
__dict__
,__slots__
,__weakref__
, etc.).__set__
) in class dictionary and possible parent classes.__get__
but do not__set__
) in class dictionary and parents.__getattr__
.if we ignore the data descriptors (and therefore properties with setter ) Python first looks for the attribute in the instance attributes, and if not found it looks in the class attributes. This implies that if you create an instance attribute with the same name as an already present class attribute, the class attribute is "hidden" if the path
self.atributo
or is attempted to be accessedinstancia.atributo
.In the case of assignment, the order is:
__setattr__
The above basically answers the question, the following is somewhat longer (mainly for the examples) and tries to explain a common error caused by the difference between assigning to a class attribute from its class or from an instance and directly related with the resolution order explained above.
If someone doesn't want to read more ツ, the moral is:
As discussed above we can access class attributes in both ways:
When instead of just accessing the value we want to perform an assignment, things change:
If we assign through the class the behavior is as expected, the value changes for all instances of the class:
You can see how modifying the value of
n
either usingFoo.n
or through a class method (where equalscls
refers to ) the change is reflected in all instances of the class.Foo
__class__
If we assign a new value to the class variable using an instance to refer to it we get a surprise:
Attempting to assign a new object to the class attribute using
instancia.atributo
or from a via instance methodself.atributo
(as long as we are not dealing with a data descriptor that as we saw has precedence and would behave as we expect) the class attribute is not modified, it is created an instance attribute with the same name and the new value is assigned .Let's remember that the attributes (again apart data descriptors) are first searched in
__dict__
and then in__class__.__dict__
if they are not found, this causes the class attribute to be hidden behind the instance one, we can see it through the content of__dict__
and__class__.__dict__
:Finally, remember that in Python there are mutable objects like (
list
,dict
,set
) and immutable (int
,float
,bool
,tuple
,frozenset
,str
). Immutable objects cannot be modified once created, when we concatenate a string a new object is created, which does not happen with a list:Python handles assignment by reference, a variable or attribute is nothing more than an identifier, a name, that refers to an object in memory. Class attributes are shared between all instances because they reference the same object in memory, so when we modify mutable class attributes via
instancia.atributo
orself.atributo
the change if that is reflected in all instances:This does not happen with the instance attribute since it is specific to each object:
If the class attribute refers to an immutable object, although we try to "modify" it in one instance, this does not imply doing it in the rest, since "modifying" an immutable object always implies an assignment in between and, as seen before, an assignment from instance causes the creation of a new instance attribute, not the modification of the class attribute (unless it is a data descriptor):
Unlike what happened with the list, string concatenation (or operations with integers, floats, etc.) implies the creation of a new object and an attempt to assign it to the attribute, which implies the creation of an instance attribute like we saw before and not the alteration of the class attribute.
Although the descriptors are intimately linked to all this and are mentioned repeatedly, they are not entered because explaining the descriptor protocol would imply extending the response too much when it is already possibly too long, we can see in the documentation itself a general guide about this: Descriptor HowTo Guide
A class variable is inherited by all instances that derive from it. If you look at this example:
pancho
andfido
are two instancesPerro
that have inherited the value of the variablenumero_patas
. Now we change the value of the variable in the instancefido
Now we add a new instance variable to fido:
In Python, class and instance variables are stored in different dictionaries. If we look in the dictionary of each created instance:
We see that
pancho
it has no instance variables. However, when we have asked for it , itnumero_patas
has returned us4
. This is because there is a second dictionary, the class dictionary, which is accessed when the instance variable is not found in its dictionary. If we access it:You will see that they are identical. If you're deriving a single instance of a class, you should use instance variables, since accessing the instance variable dictionary first will be a bit faster.
The difference between a class attribute and an instance attribute is in the way of accessing them , therefore for instance attributes it is only possible to access through an object, for class attributes it is accessed directly from the class and/or also by means of an object.