Python Properties and Class Methods
Learn about Python Class Attributes, Class and Static Methods as well as @property decorator
If you are like me who have a few months experience of Python, then you might be in the same position as me — know something about Python, but not enough. So I spent sometime to gather bits and pieces of scattered knowledge to dig deeper into Python. In this blog, the piece will be the Python Class Attributes, Class and Static Methods as well as @property decorator.
Class and Static Attributes
First, let’s look at the class attribute— both exist on the class itself but not on the instance of class.
class MyClass:
class_var = "class variable" def __init__(self, instance_var):
self.instance_var = instance_vardef display(self):
print("This is class variable :" + MyClass.class_var)
print("This is instance variable :" + self.instance_var)
==================================>>>> var = MyClass("instance variable")
>>> var.display()
This is class variable :class variable
This is instance variable :instance variable
You can see class_var
only exist on the class MyClass.
This concept originates from C++ and C, which uses static keywords to make a variable a class variable.
Class and Static Method
Now let’s move on to the class and static method. As convenience, we will compare them with the common instance methods:
class MyClass:
def instance_method(self):
return 'instance method called', self @classmethod
def classmethod(cls):
return 'class method called', cls @staticmethod
def staticmethod():
return 'static method called'
Instance Methods: The first methodmethod
is an instance method. The parameter self
points to an instance of MyClass.
It can also access the class itself through self.__class__
property.
>>> var = MyClass()
>>> var.instance_method()
(‘instance method called’, <__main__.MyClass object at 0x10cdd1e10>)
Class Methods: The classmethod
with the @classmethod
decorator is a class method. It takes a cls
parameter that points to the class, and can only modify class states.
>>> var.classmethod()
(‘class method called’, <class ‘__main__.MyClass’>)
Static Methods: The staticmethod
with@staticmethod
decorator is a static method. It does not take the mandatory self
nor a cls
parameter. As a result, it can’t access class or instance state
>>> var.staticmethod()
‘static method called’
Note that we can also call the later two methods on the class directly (As expected) but not on the instance method:
>> MyClass.instance_method()
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
TypeError: instance_method() missing 1 required positional argument: ‘self’>>> MyClass.classmethod()
(‘class method called’, <class ‘__main__.MyClass’>)>>> MyClass.staticmethod()
‘static method called’
When choosing between`@staticmethod
and @classmethod
, think about the latter requires access to the class object to call other class methods, while the former has no access needed to either class or instance objects. It can be moved up to the module-scope.
On the use cases, one example with @classmethod
is to have the flexibility to use it as factory functions to initiate variants of class instances without using inheritance. It is also a good practice to communicate your design intent with others:
>>> class MyCar:
... def __init__(self, brand):
... self.brand = brand
... @classmethod
... def benz(cls):
... return cls(['benz'])
... @classmethod
... def bmw(cls):
... return cls(['bmw'])
... def __repr__(self):
... return repr('This is my ' + self.brand )
Now to make car
with certain brand.
>>> MyCar.benz()
MyCar(['benz'])>>> MyCar.bmw()
MyCar(['bmw'])
As a result, we use the factory functions to create new MyCar
objects that are configured with certain brand, which all use the same __init__
constructor and provide a shortcut to create cars with different brands.
@Property decorator
And finally, the @property decorator. Simply put, the property()
method provides an interface to instance attributes, aka, getter
, setter
and deleter
.
Why that is needed?
Well, it is common to allow dynamically setting and getting an instance attribute when you build a class, e.g. the brand attribute below:
class Mycar:
def __init__(self, brand=None):
self._brand = brand# getter
def get_brand(self):
print("Getting brand")
return self._brand
# setter
def set_brand(self, brand):
self._brand = value
print("Setting brand", self._brand)benz = MyCar
benz.set_brand("benz")
benz.get_brand===========================
Setting brand benz
Getting brand
This can do, but it’s a bit tedious. Also what if the other developer comes in and wants to have another naming convention like instead of calling set_brand
and get_brand
, it will be setting_brand
and getting_brand
?
We can use property()
method in Python — a built-in function that creates and returns a property
object, to solve this problem:
property(fget=None, fset=None, fdel=None, doc=None)
fget
is function to get value of the attributefset
is function to set value of the attributefdel
is function to delete the attributedoc
is a docstring
So now we can have:
class Mycar:
def __init__(self, brand=None):
self._brand = brand# getter
def get_brand(self):
print("Getting brand")
return self._brand
# setter
def set_brand(self, brand):
self._brand = value
print("Setting brand", self._brand) brand = property(get_brand, set_brand)benz = MyCar
benz.brand = "benz" # NEW SYNTAX
benz.brand # NEW SYNTAX===========================
Setting brand benz
Getting brand
As you might have sensed, the property
can be implemented as a decorator to your instance method:
class Mycar:
def __init__(self, brand=None):
self._brand = brand@property
def brand(self):
print("Getting brand")
return self._brand
@brand.setter
def brand(self, value):
self._brand = value
print("Setting brand", self._brand)@brand.deleter
def brand(self):
print("Deleting brand", self._brand)
del self._brandbenz = MyCar
benz.brand = "benz"
benz.brand ===========================
Setting brand benz
Getting brand
Above example can be generalised into below pattern:
class C:
def __init__(self):
self._x = None @property
def x(self):
"""I'm the 'x' property."""
return self._x @x.setter
def x(self, value):
self._x = value @x.deleter
def x(self):
del self._x
So that’s it for today!
Happy Reading!