Class method và Static method trong Python
Hướng dẫn về các loại phương thức trong lập trình Python hướng đối tượng: Class method và Static method
Phương thức là những thành viên chịu trách nhiệm xử lý dữ liệu trong class Python. Có 3 loại phương thức trong class: instance method (phương thức thành viên), class method (phương thức class), static method (phương thức tĩnh). Ngoài ra trong Python còn có property (thuộc tính) – một dạng kết hợp đặc biệt giúp truy xuất dữ liệu.
Method trong Python
Method trong Python là những hàm được gắn với object hoặc với class. Trong Python, khai báo method có thể thực hiện độc lập với class! Đây là điều mà các bạn xuất phát từ C++/Java/C# rất khó hình dung.
Hãy cùng thực hiện một ví dụ nhỏ:
Ví dụ
class Person:
pass
putin = Person()
def greeting(msg:str):
print(msg)
putin.say_hello = greeting
putin.say_hello('Hello world from Python method') # in ra dòng 'Hello world from Python method'
trump = Person()
trump.say_hello('Welcome to heaven!') # lệnh này sẽ bị lỗi 'không tìm thấy hàm say_hello
Trong ví dụ trên:
- Chúng ta khai báo một class trống rỗng Person. Trong class này không có bất kỳ thành viên nào.
- Tiếp theo chúng ta tạo object
putin
của class Person. - Chúng ta khai báo một hàm độc lập
greeting()
có thể in ra dòng thông báo. - Điểm rất đặc biệt là lệnh
putin.say_hello = greeting
. Đây là lệnh tạo ra một phương thức (method) trong object putin và gán cho nó hàm greeting(). Tức là chúng ta ‘gán ghép’greeting()
vớisay_hello()
củaputin
. - Sau đó, bạn có thể sử dụng hàm/phương thức greeting từ object
putin
nhưng với tên gọi mới say_hello. Hàm say_hello() được gọi là một method của putin. - Tuy nhiên, nếu bạn tạo object trump từ class Person, object này lại không có phương thức say_hello.
Như vậy, giống như attribute, method trong class Python cũng không bắt buộc phải khai báo trong thân class. Method độc lập với class và object.
Tuy nhiên, việc khai báo method trong thân class giúp cho tất cả các object có chung danh sách method. Điều này phù hợp với đặc điểm của lập trình hướng đối tượng.
Tùy thuộc vào việc method truy xuất được dữ liệu của object cụ thể, truy xuất dữ liệu chung của class, hay hoàn toàn không cần dữ liệu gì, Python phân biệt ra instance method, class method và static method.
Phương thức say_hello ở trên có thể được xếp vào loại static method.
Instance method trong Python
Instance method trong Python là những phương thức có khả năng truy xuất trạng thái của object. Nhắc lại: trạng thái của object trong Python được lưu trữ trong các instance attribute (biến thành viên).
Instance method của Python tương đương với phương thức thành viên trong C# hay Java.
Hãy xem ví dụ sau:
Ví dụ
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def print(self, format = True):
"""In thông tin ra console
format: có định dạng thông tin hay không
"""
if not format:
print(self.name, self.age)
else:
print(f'{self.name}, {self.age} years old')
putin = Person('Putin Vladimir', 60)
putin.print()
print() trong class Person là một instance method. Về mặt hình thức khai báo, instance method không có gì khác biệt so với một khai báo hàm thông thường (ngoài class).
Khai báo phương thức thành viên sử dụng từ khóa def
là một lệnh phức hợp gồm một clause. Phần header bao gồm từ khóa def, tên phương tức và danh sách tham số.
Tên phương thức được đặt theo quy tắc đặt định danh chung của Python. Ngoài ra tên phương thức cũng được đặt theo quy ước tương tự như với tên hàm: tất cả chữ cái viết thường; nếu có nhiều từ thì phân tách bằng dấu gạch chân.
Danh sách tham số hoàn toàn tương tự như của hàm. Bạn có thể sử dụng tham số bắt buộc, tham số mặc định, tham số biến động và chỉ báo kiểu tham số như khai báo hàm.
Tuy nhiên, danh sách tham số của phương thức bắt buộc phải có ít nhất một tham số, thường đặt tên là self
. Nếu có nhiều tham số, self phải là tham số đầu tiên trong danh sách.
Sau header là docstring với vai trò tương tự như docstring của hàm. Giống như hàm, bạn nên kết hợp chỉ báo kiểu (type hint) với docstring.
Trong suite ở thân phương thức bạn có thể trả về kết quả với lệnh return.
Nói tóm lại, khai báo một phương thức hoàn toàn giống như khai báo một hàm thông thường. Tuy nhiên, khi khai báo phương thức có hai điều khác biệt cần lưu ý:
- Tham số
self
trong danh sách tham số; - Cách truy xuất biến thành viên.
Tham số self và truy xuất thành viên class
Bạn hẳn đã thấy trong các phương thức của Python, dù là constructor hay instance method đều có biến self
nằm ở đầu danh sách tham số.
Tại sao hàm tạo và các phương thức thành viên của class trong Python lại phải có biến self
?
Nếu bạn đã từng làm việc với class trong các ngôn ngữ như C++, Java hay C#, bạn có thể hình dung class như một ngôi nhà, và các phương thức là những người giúp việc sống trong ngôi nhà đó. Những người giúp việc này thực hiện các công việc được giao. Do sống ở ngay trong nhà, họ có thể truy xuất trực tiếp tất cả các những trong nhà nếu được phép. Do vậy, lệnh truy xuất thành viên của class không cần bao gồm tên object.
Trong Python, bạn cần hình dung những người giúp việc sống ở một nơi khác. Do vậy, khi yêu cầu họ làm việc gì, bạn cũng đồng thời phải chỉ rõ ngôi nhà họ sẽ làm việc. Việc chỉ định ngôi nhà tương tự với truyền chính object qua biến self.
Việc truy xuất thành viên của class (dù là viết trong code của class hay viết bên ngoài class) đều phải bao gồm tên object.
Như trong ví dụ trên, khi bạn tạo object putin
và gọi hàm putin.print()
, object putin
được truyền cho phương thức print
qua biến self
. Tuy nhiên, bạn không cần trực tiếp truyền object putin cho print do Python làm việc này tự động.
Do vậy, biến self
là bắt buộc với mọi phương thức thành viên của Python class.
Python đưa ra quy định là tham số đặc biệt self
phải được viết ở đầu danh sách tham số. Tên self
chỉ là một quy ước. Bạn có thể dùng bất kỳ tên biến hợp lệ nào.
Hãy nhìn cách sử dụng biến name và age:
Ví dụ
if not format:
print(self.name, self.age)
else:
print(f'{self.name}, {self.age} years old')
Bạn không được viết trực tiếp tên attribute mà bắt buộc phải thông qua biến self: self.name
, self.age
. Nó không có gì khác biệt so với khi truy xuất từ tên object bên ngoài class, cả về hình thức và bản chất.
Nếu bạn vẫn còn thấy khó hiểu với self, hãy thử dòng code sau:
Ví dụ
putin = Person('Putin Vladimir', 60)
Person.print(putin) # cho kết quả 'Putin, 60 years old'
Person.print(putin, False) # cho kết quả 'Putin 60'
Giờ đây bạn không gọi hàm print từ object putin nữa mà gọi từ class Person. Kết quả không có gì khác so với bạn gọi từ object. Sự khác biệt ở chỗ bạn phải tự mình truyền object putin cho hàm print.
Nhắc lại, trong Python, bạn có thể hình dung method là một hàm thông thường được bạn “vô tình” viết vào trong khai báo của một class. Để hàm có thể truy xuất attribute của object, bạn phải truyền object cho hàm. Tham số self (hoặc bất kỳ tên gọi nào) ở đầu danh sách tham số chính là để phục vụ yêu cầu này.
Nếu bạn gọi hàm từ một object, Python sẽ hỗ trợ bạn tự động truyền object đó vào tham số self. Nếu bạn gọi theo kiểu khác (như gọi từ class), bạn phải tự mình truyền object cho self.
Class method trong Python
Trong bài học trước bạn đã làm quen với class attribute – loại biến chứa giá trị đặc trưng cho class và gắn với class, thay vì gắn với object. Một ví dụ đã được đưa ra để minh họa là đếm số lượng object của class đã được khởi tạo trong chương trình.
Class method cũng có cùng ý tưởng với class attribute. Class method là những phương thức đặc trưng cho class và gắn liên với class, thay vì gắn với object. Class method có nhiệm vụ xử lý dữ liệu lưu trong class attribute.
Hãy xem ví dụ sau:
Ví dụ
class Person:
count = 0
def __init__(self, name: str, age: int):
self.name = name
self.age = age
Person.count += 1
@classmethod
def show_count(cls):
"""Trả lại số object trong chương trình"""
print(f"Hiện có {cls.count} object Person trong chương trình")
putin = Person('Putin Vladimir', 60)
trump = Person('Trump Donald', 60)
Person.show_count()
Ở đây chúng ta tạo class attribute count và tăng giá trị của count mỗi khi tạo object mới.
Chúng ta cũng xây dựng một phương thức riêng show_count() chuyên để in thông báo về giá trị của count. Hãy để ý cách khai báo phương thức này có điểm đặc thù: @classmethod.
@classmethod
trong Python được gọi là một decorator. Decorator là một loại hàm đặc biệt có thể nhận một hàm khác làm tham số để bổ sung tính năng cho hàm tham số đó. Chúng ta sẽ quay lại học chi tiết về decorator trong một bài khác.
Để tạo ra class method từ một method thông thường, chúng ta sử dụng decorator @classmethod.
Class method cũng bắt buộc phải có một biến đặc biệt trong danh sách tham số: biến cls (viết tắt của class). Biến này có vai trò tương tự như biến self của instance method. Điểm khác biệt nằm ở chỗ biến cls chứa thông tin về chính class.
Trong ví dụ trên, count là một class attribute – chứa thông tin về chính class. Do vậy, có thể truy xuất count qua biến cls. Trên thực tế, bạn có thể hình dung truy xuất qua biến cls cũng chính là truy xuất qua tên class. Tức là cls.count
hoàn toàn tương đương với Person.count
.
Trong code, bạn có thể gọi class method qua tên class hoặc qua tên object đều được. Như trong ví dụ trên, bạn có thể gọi Person.show_count(), putin.show_count(), trump.show_count() đều được và trả về cùng một kết quả.
Để tránh lẫn lộn, bạn nên gọi class method qua tên class. Điều này cũng tương tự như truy xuất class attribute nên qua tên class, mặc dù có thể truy xuất được qua tên object.
Static method trong Python
Trong Python cũng hỗ trợ khái niệm static method. Static method là loại phương thức hoàn toàn tự do, không có gì ràng buộc về dữ liệu với class hay object. Chúng được đặt trong class chỉ vì một lí do logic nào đó.
Để so sánh: instance method có ràng buộc về dữ liệu với instance attribute; class method có ràng buộc về dữ liệu với class method.
Cả static method và class method trong Python đều tương đương với static method trong Java hay C#.
Để tạo ra static method trong class Python, chúng ta sử dung decorator @staticmethod
.
Cùng điều chỉnh class Person như sau:
Ví dụ
class Person:
count = 0
def __init__(self, name: str, age: int):
self.name = name
self.age = age
Person.count += 1
def print(self, format = True):
"""In thông tin ra console
format: có định dạng thông tin hay không
"""
if not format:
print(self.name, self.age)
else:
print(f'{self.name}, {self.age} years old')
@classmethod
def show_count(cls):
"""Trả lại số object trong chương trình"""
print(f"Hiện có {cls.count} object Person trong chương trình")
@staticmethod
def calculate_birth_year(age : int) -> int:
import datetime as dt
year = dt.datetime.now().year
return year - age
# sử dụng phương thức calculate_birth_year như sau:
my_birth_year = Person.calculate_birth_year(37) # kết quả 1983 (năm 2020)
Hãy để ý phương thức calculate_birth_year được đánh dấu với @staticmethod decorator. Phương thức này dùng để tính ra năm sinh dựa trên giá trị tuổi. Phương thức này không sử dụng thông tin của class (biến count), cũng không sử dụng thông tin của object (name và age). Nó hoạt động giống như một phương thức độc lập. Khác biệt là nó được khai báo bên trong class Person.
calculate_birth_year khai báo như trên là một static method.
Cách sử dụng của static method giống như một class method. Bạn gọi nó thông qua tên class và cung cấp tham số cần thiết.
Ví dụ
my_birth_year = Person.calculate_birth_year(37)
Decorator @staticmethod
biến một phương thức khai báo trong class trở thành một static method. Phương thức calculate_birth_year
không hề sử dụng bất kỳ thông tin nào của class Person
cũng như của object tạo từ class này. Nó chỉ có liên hệ về logic với Person: giúp tính năm sinh từ giá trị tuổi.
Như vậy, bạn sử dụng static method nếu chúng có mối liên hệ logic nào đó với class nhưng không cần các thông tin của class cũng như của object. Static method cũng giúp nhóm các hàm xử lý vào cùng một class để tiện sử dụng ở client code.
Kết luận
Trong bài học này chúng ta đã làm quen với method – thành phần xử lý dữ liệu trong class. Python có ba loại method: instance method gắn với object, class method gắn với class, và static method.
Nếu nhớ lại attribute – thành phần lưu trữ dữ liệu trong class – bạn sẽ thấy, method và attribute trong Python tạo thành một hệ thống rất dễ nhớ:
- instance attribute chứa dữ liệu (trạng thái) riêng cho từng object và được xử lý bởi instance method.
- class attribute chứa dữ liệu chung cho mọi object và đặc trưng cho class – được xử lý bởi class method.
- static method nằm trong class nhưng không sử dụng thông tin của class hay object.