Class Decorator (part2)

CC(ChenChih)
Chen-Chih’s Portfolio Page
24 min readJan 22, 2024

In my previous post(Understand Decorator in Python(part1)) , I wrote about the concept of decorators being focused on function decorators. In this section, I will talk about some built-in class decorators that we can use. But before diving into the topic I will also mention some common concepts of class.

Please note that I will not cover OOP class fundamentals over here. In the last post, I talked about the decorator but there are still other decorator classes I didn't mention instead I would like to introduce them here. Class is part of OOP, so I will only talk about the basic understanding of it, like objects, and constructors.

If you are interested in a specific topic please click the below link for each section in the Table of contents.

For source code please refer to this link, you can download it. I put it on Jupyter Notebook/Lab for easy tracking or reading.

Part1: Understand Class ¹

Let create a new class name Employee, and know what’s the instance, attribute, and method.

Please understand the terms of the related code for creating a class, please refer to the picture above or the code.

  • terms description:

Instance: are specific objects created from a class

Attributes: are variables that store information about instances. We often call instance attributes or instance variables which are related things.

Method: are function that performs action on the instance or accesses its attribute.

self is nothing but the object itself, which is also initially a constructor( I will mention in the class method section)

  • code example

empl1 and emp2 are instances of the Employee class, you can also say emp1 and emp2 are objects of Employee class

first, last, company are all instance attribute

email and fullname are instance method

So if I create like this:

emp1 = Employee(): this is creating an instance of the class, where emp1 is an object of the Employee class.

emp1 = Employee('John', 'liu'): you are creating an instance and argument which will initial the value of its attribute.

emp1.name: you are accessing the instance attribute or instance variable

Note: if you are creating an instance and passing an argument, then you need to have an __init__ method that contains self parameter else it will occur in error. I will discuss more about self in the class method section

Create a class and access it’s attribute and method ¹

Below is a basic class example of employee name and email address

class Employee:
company='youtube' #class variable
def __init__(self, first, last):
self.first=first
self.last=last
@email.com">self.email=first+'.'+last+'@email.com'
def fullname(self):
return '{} {}'.format(self.first, self.last)

This is called an initial method and there’s a term called constructor. Whenever you see an init this means it’s a constructor, and self is the object itself.

Create an object ¹

Creating an instance of the Employee Class or creating an object. Use the print to access the instance attribute

emp1=Employee('John','Liiu')
print(emp1.first)
print(emp1.email)
print(emp1.fullname())
#output:
'''
John
John.Liiu@email.com
John Liiu
'''

Modify an instance attribute ¹

Now let's change the empl1 instance attribute name, but fullname ,email, name isn’t update

emp1.first='Kelly'
print(emp1.first)
print(emp1.email)
print(emp1.fullname())
#output:
'''
Kelly
John.Liiu@email.com
Kelly Liiu
'''

As you can see from the output when I set the emp1.first, and print print(emp1.first)are displayed correctly, however, when you print the emp1.email it’s not showing correctly, it’s showing John.Liiu@email.com .

Why was that?

In the first code, we create an object and pass two arguments to the instance first and second, which will be set into the variable. Print the attribute in the class, please refer below picture for the two code comparisons.

When we set the attribute emp.first='kelly' only it will be updated, but the rest of the variables will not be updated because they were set during the initialization method (__init__). In class, the value will not automatically be updated in the initialization method.

The key point is that attributes set during the __init__ method are initialized once, and changing other attributes later will not automatically update those initial attributes.

So how to fix it, now we can set a new method to solve this issue:

  • Create a new method for email()

This will solve but this is not the best solution, the reason is that if we have long code and many people need to use this code, then all people have to edit or code, so this is not a good solution, a better solution is using a property decorator, which I will talk about it in below section on how to use @property .

understand attribute and method ¹

So as you can see from the above code we have three attribute variables (first, last, company), and two methods email and fullname. As you can see method contains parenthesis().

emp1 can access attributes and methods, like this:

Now what if I remove the parenthesis for the method, it will display like an object

So after having an understanding of the example I mentioned solving updating attributes by creating a method, however, it’s not the best solution when we have many developers or long code needs to maintain. When we remove the parenthesis it will become an object which is not convenient, instead property the decorator will be much more flexible, let explain the usage of the property in the next section.

Part2: @ Property decorator ¹

Let me first explain the property’s syntax and options:

  • @property: Declares the method as a property.
  • @<property-name>.setter: Specifies the setter method for a property that sets the value to a property.
  • @<property-name>.deleter: Specifies the delete method as a property that deletes a property.

What and why using properties?

As I mentioned previously when many developers want to use the code, if we add a new method everyone will have to change the code. If we don’t want to change the code it will display like an object, just like I showed in the previous example or refer to below picture.

pic1

So to solve these issues we will need to use the property decorator which will treat the method as an attribute, so you don’t have to add a parenthesis instead add @property in front of the method like below.

pic2

So as you can see from the pictures from pic1 and pic2, you can notice that pic1 if I want to call the method I need to add a parenthesis, whereas in pic2 I don’t need to add a parenthesis, instead add @property will treat the method as an argument. This is the reason why using property decorator

Setter decorator ¹

Now what if I want to change or set the method’s attribute value, it will cause an Attribute error, like the picture below. Why is it like that? The reason for occurring error is because we already set a @property decorator for a method this means that you are defining a read-only properly. In other words, you can only access the value (read-only) of that property and are not able to modify it directly.

The solution to modify it is to define a setter method, the syntax looks like this @<property_name or method name>.setter decorator. In our example the fullname is the method which is been added @property decorator, so to add setterproperty like this: @ fullname.setteras below example

email() and fullname() : add property so read only

fullname() able to set or write: @fullname.setter

Deleter decorator ¹

There’s also a deleter decorator that will delete the attribute value, in my example set it to none. So in the below example initial I named it Hela, then I changed the name to Jessica, and then I deleted the value that return none

I think I covered the concept of property decorator, wish this might be helpful.

Part3: Types of class method ¹

Before talking about the class method, let me explain the concept of instance and method. I have mentioned some concepts of the class, but I will still mention a small part here. In Python, we have 2 types of variables: instance or attribute variable, and class variable. We also have three methods, the instance method, class method, and static method, as below picture.

instance method and instance variable ¹

The self is nothing, but the object itself, and it’s also a constructor. So in an instance method, there’s a constructor which you will see the __init__, and instance method which can refer to the normal instance method.

In the beginning, I talk about the basics of the class or you can think of it as an instance method or regular method. Regular methods are defined inside a class that automatically takes the instance itself (self) as the first parameter. The self is nothing, but the object itself or the instance of the class.

Instance variable: ¹ ²

- owned by the instance of a class, owned by the object itself.

- Each object’s instance object is different

- The instance variable will be defined inside the method

This is a flow of how it works, I find on some pages if you want to know the flow of the instance methods.

1.1 What’s is self ¹ ² ³

It’s passed as the first argument to every method of a class, or you can say an instance of the class. We can have many instances of the same class. You can think as emp1 which is the same thing as self so whenever you call a method of that emp1 instance( emp1.display, or emp1.about), this emp1 eventually is passed into self as the first argument of any method in the instance.

So as you can see emp1.display(emp1), you can see it displays the same object reference which in here it’s showing the same memory. In this case, it shows the same instance (emp1) passed as both self and x. So, self and x mean the same thing.

So you can access any variable or method you like, such as below example:

1.2Understanding different variable ¹ ² ³

I still want to mention in Python there are also different variable types, there are public, protect, and private variables. The public variable or method is what we use to define often, so I am not going to do much more explanation to it. Just think in Python all member variables and methods are public by default.

However, there’s also a protect variable and a private variable, which I will introduce here. You don’t have to know it but just in case you see some variable declared this way it’s not strange it’s just a variable type. If you’re not interested in it you can skip this section. I just want to let you know there’s also a special type of variable, and not seeing this and felt curious.

1.2.1 Protected Variable single underscore ¹ ² ³

Protected Variable basely is just adding a prefix of a single underscore _vararablename. In other languages like Java or C++ protected variables are within a class or subclass ( which is the inheritance) able to be accessed or modified. In Python the protected variable is just letting the developer know it’s a protected variable, however it’s still able to access or modify outside the class. Let me show you an example.

There’s another way you can do this is by adding @property to protect your variable. In the above I already mentioned the property decorators, when you use the @propertydecorator you can’t modify the value, because it’s readable, so you can add the setter method to modify it. You can refer to more detail in that section. Let me show you an example

As you can see you can use the @property to protect the variable avoid people modifying it, and use the setter will allow you to modify. If I remove the setter decorator and modify will occur Attribute Error.

Have you seen the below picture, I removed the underscore prefix outside the class print(emp_1.name), but inside the class, I still preserved the underscore prefix self._name. You might wonder why it won’t get an error. The reason is @property decorator on the name method which allows you to access it, and if it was an attribute: emp_1.name.

So overall the protected variable in Python is still able to access or modify outside the class. It’s just letting the developer know it’s a protected variable. But if you want to restrict or modify then you can use the property decorator or the private variable which I will discuss in the next section.

Let me show you another example when i use the property decorator and try to modify and delete the instance attribute which will not take effect

class House:
def __init__(self, priceX):
#Initialize the private attribute '_priceX'
self._priceX = priceX
@property
def priceX(self):
return self._priceX
house = House(50000.0)
#This will not modify because 'price' is a read-only property
house.price = 45000.0
# Access the '_priceX' attribute
print(house._priceX)
#This will not delete because 'price' is a read-only property
del house.price
print(house._priceX)
#output
'''
50000.0
50000.0
'''

So from above when i try to modify the house.price = 45000.0 it will not modify because my price is been decorator property which mean it’s only readable. So in order to fix this to modify and delete need to use this code

..............
@property
def priceX(self):
return self._priceX
@priceX.setter
def priceX(self, value):
self._priceX = value

@priceX.deleter
def priceX(self):
del self._priceX
house = House(50000.0)
house.priceX = 45000.0
print(house.priceX)
del house.priceX
# This will raise an AttributeError because 'priceX' has been deleted
# print(house.priceX)

1.2.2 Private Variable double underscore ¹ ² ³

The private variable is you can’t be overwritten or accessed from anywhere, and it uses the double underscore prefix __ variable name to represent a private variable.

Please refer below picture for the example. As you can see I have defined __name and __salary private variable, and __display() private method, so if you try to access it will occur AttribueError.

From the above picture, the right side is the private variable name that allows you to access or modify it.

How would you know the variable name for a private variable that you can use?

If you want to modify or access the private variable then you can use then use the dir() method to check the private variable name. It will show you private variables, just like the picture below. Below you can see I had marked the private method and variable name. For example std.__name is a private variable, to access it use _Student__name .

private variable avoid people to accessing or modifying it, to access it you can use the dir() to check the variable name to access it, or use the syntax like this <object>_<ClassName><__Privatevariablename>

I hope the explanation of private and protected variables lets you understand more about variable types not just public variables we can use.

class method decorator ¹

Now I’m going to explain class variables and class methods.

Class Variable: in the previous one we mentioned the instance variable, which will be defined inside the method, but the class variable will be defined outside the method and inside the class. This means all the objects of that class can use that variable. Below I have labeled each term so you can refer to it for a clear understand

we can access a variable by the object of that class, or access using the Class variable within class construction which owns the class itself and the class variable.

Class Method: A built-in decorator(@) defines a class method in the class, which receives only a class argument as the first parameter. The decorator is an alternative method of the classmethod() function, which works the same(the below picture shows the difference), but most people prefer to use with @decorator keyword.

Create a class method by using a class decorator and cls as a parameter. In the instance method we will pass the object itself using self. But in the class method, we are not passing the object instead of the class itself.

The first parameter will use cls as a parameter, which can used to access the class attribute. You can name other names you like, but most people name cls. The word class is a keyword in Python, so we aren’t allowed to use it instead we use the cls means class variable name.

Let me show an example using an instance and class method. In instance methods, the instance is created directly by calling the constructor, whereas the class method will call from the factory method. Essential factory methods are class methods to create and return class instance-specific conditions or customized attributes.

Factory methods offer a flexible way or usage to create and return class instances, often on specific conditions or customized attributes, like the below code I use the create condition with the course. Below is a comparison between the instance method, class method, and factory method. Please note here factory method is just to let you know why we use the class method, in case you don’t know what’s factory method is; it’s not the main character here(the class method is the main topic here).

Show you some examples to compare between instance and class methods.

class Student:
def __init__(self,name,course):
self.name=name
self.course=course
def display(self):
if self.course=='NA':
print(f'Hi, {self.name} your course {self.course} is not selected!!!')
else:
print(f'Hi, {self.name} your course {self.course} has select success!!!')
@classmethod
def checkcourse(cls, name, course):
if course in ['French','History','Math','CS']:
return cls(name, course)
else:
return cls(name, 'NA')
st1 = Student('Adam', 'French') #using instance method
st1.display()
st2 = Student.checkcourse('Jerry', 'dd')# using classmethod
st2.display()
# Hi, Adam your course French has select success!!!
# Hi, Jerry your course NA is not selected!!!

overview and characteristics of creating a class method:

- add a @classmethod decorator top of the function to tell it’s a class method.
- need to assign a
cls as the first parameter which basely means a class
- Class can only access class attribute but not instance and attribute

Let us see different examples to be more clear on how the classmethod usage.

Example1: understanding the fundamentals of class ¹ ²

In this example, I just want to let you understand the concept of the class’s attribute or variable, and how we can can access, and modify using classmethod and class variable.

I create 2 objects and initial with an argument of a string:

std_1 = Student(‘angus’)
std_2 = Student(‘mike’)

now as you can see when you access the Student.country, which is the class variable, it displays US.

print(Student.country)#US
print(std_1.country)#US
print(std_2.country)#US

When I access each object or instance std_1, and std_2 (refer to above) it will also show US, which also refers to the class variable.

Now let's set using the class method, it essential meaning I use the class to set the variable of the country, so when I set

Student.setcountry(‘NZ’) #set to NZ , this essential also means Student.country='NZ', as you can see country is class variable, and setcountry is the method. So when I use the below code(which is also part of the above code), setting the class because the cls basely means class, it allows to change of the variable.

  @classmethod
def setcountry(cls, ctry):
cls.country = ctry

as you can see Student.setcountry('countryname') and Student.setcountry('countryname') are doing the same thing one with class method and the other one using class attribute or variable.

class method: directly modifying the class attribute country of the Student . Ex: class. Student.setcountry('countryname')

class attribute: When you modify a class attribute directly through the class name. So it is updating the country attribute for all instances of the Student class, including std_1 and std_2 .Ex:Student.country='countryname'

They can achieve the same result, just in different ways, just to let you know we can use with class method and class attribute to modify attributes.

Example2 construction¹ ²

Let me show another example using the alternative construction method, below picture the left one.

classmethod-ex2–1

In this example, I want to split the string of first, last, and course names into a component: first,last,course= std_A.split('-')

Then I will create an attribute and pass the argument in it, and it will go inside the constructor and save it in the initial method: newstd_A= Student(first,last,course) .Now I can access the object of newstd_A and get its course and email.

Now, instead of manually splitting the string into first, last, and course components and creating a new instance, we can use a class method. We can create a class method named from_stringdecorated with @classmethod which indicates it operated on the class rather than an instance. So I move the first, last, course = emp_str.split('-') into from_string method.

 @classmethod
def from_string(cls, emp_str):
first, last, course = emp_str.split('-')
return cls(first, last, course) # same as Student(first,last,course), just replace cls

The class method's first parameter is the cls represents the class itself, which is Student in this case, and the rest argument, takes the string emp_str as its parameter, splits it into components, and returns a new instance of the class using cls(first, last, course).

There’s one thing you can realize originally I use newstd_A= Student(first,last,course) , then I comment it off and put it inside the from_string method, then return it.

please refer above pic: classmethod-ex2–1 or below pic:classmethod-ex2–2 for more clear understand

classmethod-ex2–2

Since Student is class, we can replace it to cls.So return cls(first,last,course) is it the same as Student(fist,last,course).

return cls(first, last, course) and Student(first,last,course) are the same result.

The print is just to access the instance variable, which will display course, email, or other instance variables inside the constructor.

There’s another related example that can also be referenced. This code is based on changing the percentage using the class method decorator.

using instance method

class Student:
def __init__(self,name,marks):
self.name=name
self.marks=marks
def msg(self):
print(self.name+ " got " + self.marks, "%")
@classmethod
def get_percent(cls,name, marks):
#return(cls('ee',marks))
return cls(name, str((int(marks) / 600) * 100))
std1.msg() #Nick got 90 %
#mark1=str((int(marks)/600)*100)
#std2=Student(name,mark1)
std2 = Student.get_percent(name, marks)
std2.msg() #jenny got 93.33333333333333 %

So when you use the instance method

mark1=str((int(marks)/600)*100)
std2=Student(name,mark1)

using the class method you can do calculations inside the class method

std2 = Student.get_percent(name, marks)
std2.msg() #jenny got 93.33333333333333 %

So overall if you want to do the calculation with a new value, for instance, you have to create a new instance and calculate, whereas in classmethod will calculate in the classmethod

Example3 changes class variable with class method ¹ ²

This example is to change the class variable companyvalue. As you can see initially the class attribute or variable company is set to youtube, and I want to change it to another name using classmethod decorator.

I create an instance by assigning the first, and last to the constructor. Then I access the email1() method, which will display the email address based on the first, last and the company name.

class Employee:
#Class attribute
company='youtube'
# Instance initialization method
def __init__(self, first, last):# Instance attributes
self.first=first
self.last=last
def email(self):# Instance method
#return '{} {}@email.com'.format(self.first, self.last)
return f'{self.first}.{self.last}@{Employee.company}.com'
emp1=Employee('John','Liiu')
print(emp1.email())
Employee.company_edit('facebook') #change class attribute
print(emp1.email())
print(emp1.description())

Now I want to modify or overwrite my existing class attribute, so I use the company_edit the class method with a @classmethod decorator to change the value of the company attribute from youtube to facebook. After that, I accessed the email method again to see if the email address changed.

    @classmethod
def company_edit(cls,newname):
cls.company=newname

Finally, I access the description method, which reads and displays the first name, last name, and email address of the employee instance.

def description(self):
#return f'My name is {cls.name}, I am {cls.country}'
return f"My name is {self.first}, My mail is {self.email()}"

static method decorator ¹

Now after understanding the two methods, let's move one to the last method and type the static method.

A static method is a method that belongs to a class, but not an instance. The decorator tells the Python interpreter that this method is static and should be called on the class rather than an instance of a class. This means that when it performs operations, it does not require a class instance; instead, it can access the class itself. Therefore, when using staticmethod we don’t need to pass any argument.
We don’t have to take either class or instance as the first parameter. In the instance we use the self to access the variable, class method uses the cls to access the method, in static we don’t take self or cls as the first parameter.

But we still need to use a @staticmethod let me show an example

overview and characteristics of creating a static method:

- Declare a static method in the class using a decorator
- Can’t have cls or self parameter
- Can not access the class attribute or instance attribute

One of the biggest benefits of using it is that you can call without creating an instance of the class, and you don’t need to rely on class and instance which are treated like a function in class. It’s mostly helpful for utility functions(or methods), which means inside a class is typically independent of the specific instance of the class, and can work as a standalone function or a normal function. So imagine if you want to calculate something, that is unrelated to the class or instance, then you can use the utility method or static method in this case. Overall it’s function that isolates the instance or class method.

Example1 ¹ ²

Let me show you an example of a static method for checking subject occurs or not.

I create an object and initial argument CS and check the subject is inside available if it’s returned yes will display yes, it’s available ; otherwise, it returns 'sorry not aviable'.

This is a straightforward easy example of how to use the @staticmethod .Since I’m using a static method, there’s no need to create an instance. When I print(std1.checksubject('CS')) it directly goes to check the subject method without using the constructor or init method.

Note: You can also create an instance like below(std1 = Student()), but It’s not necessary for calling a static method. The init contains four arguments, but in this case, we just checking the course using a single argument.

std1 = Student() # create an instance

print(std1.checksubject(‘CS’)) # calling the static method on the instance

Example2 Class and Static Example

class Employee:
#Class attribute
company='youtube'
# Instance initialization method
def __init__(self, first, last):
# Instance attributes
self.first=first
self.last=last

# Instance method
def email(self):
#return '{} {}@email.com'.format(self.first, self.last)
#return f'{self.first}.{self.last}@{Employee.company}.com'
print (f'{self.first}.{self.last}@{Employee.company}.com')
@classmethod
def company_edit(cls,newname):
cls.company=newname
@staticmethod
def static_method():
print('Hello, I am static methodd, welcome!!!')
emp1=Employee('John','Liiu')
#access by static name
Employee.static_method()
# access by object name
emp1.static_method()
# access by class method
emp1.company_edit('amazone')
#display
emp1.email()

#output
'''
Hello, I am static methodd, welcome!!!
Hello, I am static methodd, welcome!!!
John.Liiu@amazone.com
'''

Summary ¹

I will recap and summarize everything what I cover in all the section.

Below show the relate term in Class, include instance, variable, method, object, etc

1. Understand Constructor:

I have mention many stuff beside the method, also the constructor and self. Let me summarize the constructor here.

Why do we need a self and constructor?

When we create an object s1=Student(), we refer to s1 as an object, and Student() as an instance or constructor. Each object will have a different attribute or variable and self act just like a pointer which will direct to the attribute of specific object.

When I have two object, how does it know which attribute will update? The answer is the self, in this case I’m using s1, then self points to s1. Please refer below picture i make a diagram of the object and self constructor.

When we don’t set any name it will contain the default values in the constructor (attributes name and age) with the name Bill. However, when I change s1 object to a different name (below purple), it will overwrite default value Bill to Jane. This is the demonstrates and reason and why we want to use a constructor and self. Please refer below picture.

The same thing as if I have a method also when I call s1.setage, the self-parameter refer to the s1 object . In this case I modify self.age within method, thus s1’s age get updated 24. Please refer below picture.

Ihope this is a good explanation on why using an constructor and self.

2. Types of Method

This is an overview of three method:

When using class to access method, we call called it directly on class without creating an instance

class Hello:
def welcome(name):
print('Hello', name)
Hello.welcome('James')

There are three different types of method in Python:

  • Instance method we can create constructor or instance method or usually called as regular instance which look like this

Access instance method

Access constructor

When accessing a class, we need to use a class method. It’s important to note that if a method doesn’t have either self or cls as the first parameter, it is considered a regular method. Without any decorator (@classmethod, @staticmethod, etc.), a method is treated as an instance method or regular method.

  • Instance Method:

need a class instance and can access instance by the self in constructor.

Can access both class variables and instance variables through self

Defined with self as the first parameter, and it operates on the instance of the class. It can access and modify instance attributes.

  • Class Method:

don’t need a class instance; they can’t access instance but are able to access to the class itself by using the cls parameter.

Can access and modify class variables.

Has access to the class itself via the cls parameter.

Cannot access instance-specific data (individual instance attributes)

Bonded to the class itself, not instance(bond means object associate with it unbound doesn’t have an object associate with it)

Static Method:

don’t have access to class and instance. They work like just a normal function, inside the class.

Can access class variables (attributes defined at the class level).

Cannot access instance variables (attributes tied to a specific instance).

Defined with @staticmethod decorator, and it doesn't have self as the first parameter. It operates on the class itself and doesn't have access to instance-specific data.

Let show comparison of static, class and instance method

When will you use each method:

Static method:
- for utility functions which mean without depending data on instance or class.

Class Method:
- when we need to access or modify class attribute or create new instance of the class. often use
- Factory method create instance base on condition or customize
- accessing or modify class attribute.

Instance method:
- will use it to access the instance method inside the class
- Bound to individual instances of the class.
- Access and modify instance attribute using self
- Access class attribute by using class name

3. Property Decorator

@property: Declares the method as a property.

@<property-name>.setter: Specifies the setter method for a property that sets the value to a property.

@<property-name>.deleter: Specifies the delete method as a property that deletes a property.

What’s and when to just it? Just remember treat the method as an attribute . If developer add a new method and everyone need to use then everyone have to change their code to parenthesis. Instead we can treat the method as variable by removing the parenthesis, then we need the property decorator.

When you add a property on top of a function or method, this mean the function is only Readable. To be able to access need to use the setter, which I provide above.

--

--

CC(ChenChih)
Chen-Chih’s Portfolio Page

I self study technology, and likes to exchange knowledge with people. I try writing complex tech blog into simple and various ways for people to understand.