Python Properties and Class Methods

Learn about Python Class Attributes, Class and Static Methods as well as @property decorator

E.Y.
4 min readJan 10, 2021
Photo by Zdeněk Macháček on Unsplash

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_var
def 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 selfpoints 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 attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc 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._brand
benz = 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!

--

--