Instance, class, static method trong Python
Trong bài hướng dẫn này, mình sẽ giúp làm sáng tỏ về 3 loại phương thức trong Python: Instance, Class và Static method.
Nếu bạn thực sự hiểu được sự khác biệt giữa chúng, bạn sẽ có thể HỌC PYTHON tốt hơn, truyền đạt ý định của nó rõ ràng hơn và sẽ dễ dàng bảo trì trong thời gian dài.
Tổng quan về Instance, Class và Static Method trong Python
Hãy bắt đầu tìm hiểu về instance method, class method và static method bằng cách viết một (Python 3) class mà có ví dụ đơn giản cho cả 3 loại phương thức này:
class MyClass:
def method(self):
return 'instance method', self
@classmethod
def classmethod(cls):
return 'class method', cls
@staticmethod
def staticmethod():
return 'static method'
Lưu ý cho Python 2: Decorator @staticmethod
và @classmethod
có sẵn kể từ Python 2.4 và ví dụ này sẽ hoạt động như bình thường. Thay vì sử dụng một khai báo class MyClass:
, bạn có thể chọn khai báo một class kiểu mới kế thừa từ đối tượng với cú pháp class MyClass (object):
.
Instance Method trong Python là gì?
Phương thức đầu tiên trên MyClass
, được gọi là method
, là một phương thức instance method (phương thức thể hiện) thông thường.
Đó là loại phương thức cơ bản, không rườm rà thường được sử dụng nhất.
Bạn có thể thấy phương thức lấy một tham số, self
, nó trỏ đến một thể hiện của MyClass
khi phương thức được gọi (nhưng tất nhiên các phương thức thể hiện có thể chấp nhận nhiều hơn một tham số).
Thông qua tham số self
, các phương thức thể hiện có thể tự do truy cập các thuộc tính và các phương thức khác trên cùng một đối tượng.
Điều này mang lại cho chúng rất nhiều sức mạnh khi sửa đổi trạng thái đối tượng.
Chúng không chỉ có thể sửa đổi trạng thái đối tượng, các instance method cũng có thể truy cập vào chính class đó thông qua thuộc tính self.__ class__
.
Điều này có nghĩa là các instance method cũng có thể sửa đổi trạng thái của class.
Class Method trong Python là gì?
Hãy so sánh với phương thứ thứ hai, MyClass.classmethod
. Mình đã đánh dấu phương thức này bằng một Decorator là @classmethod
để gắn cờ nó là một class method.
Thay vì chấp nhận một tham số self
, các class method lấy tham số cls
trỏ đến class, và không phải là đối tượng thể hiện, khi phương thức được gọi.
Bởi vì class method chỉ có quyền truy cập vào đối số cls
này, nên nó có thể sửa đổi trạng thái đối tượng.
Điều đó sẽ yêu cầu quyền truy cập vào self
. Tuy nhiên, các class method vẫn có thể sửa đổi trạng thái class áp dụng trên tất cả các phiên bản của class.
Static Method trong Python là gì?
Phương thức thứ ba, MyClass.staticmethod
được đánh dấu bằng Decorator là @staticmethod
để gắn cờ chó biết nó là một Static method.
Kiểu phương thức này không có tham số self
hay tham số cls
(nhưng tất nhiên, nó có thể nhận số lượng tham số khác tùy ý).
Do đó, một Static method không thể sửa đổi trạng thái đối tượng cũng như trạng thái class.
Các Static method bị hạn chế truy cập một số dữ liệu - và chúng chủ yếu là một cách để thiết lập namespace các phương thức của bạn.
Hoạt động của instance, class và static method
Tôi biết cho đến đoạn nãy, đã khá đủ lý thuyết.
Mà quan trọng nhất để hiểu 3 loại phương thức này chúng ta phải đi vào trực quan. Dưới đây sẽ là một số ví dụ cụ thể để bạn thấy cách chúng hoạt động.
Hãy xem cách các phương thức này hoạt động khi chúng ta gọi chúng. Chúng ta sẽ bắt đầu bằng cách tạo một thể hiện của class và sau đó gọi ba phương thức khác nhau trên đó.
MyClass
được thiết lập theo cách mà mỗi khai triển của từng phương thức lại trả về một tuple chứa thông tin để chúng ta theo dõi những gì diễn ra - và phần nào của class hoặc đối tượng mà phương thức có thể truy cập.
Ở đây, những gì xảy ra khi chúng ta gọi một instance method:
obj = MyClass()
print(obj.method())
#Kết quả ('instance method', <__main__.MyClass object at 0x0194F8D0>)
Điều này đã xác nhận rằng method
(instance method
) có quyền truy cập vào instance object (được in dưới dạng <__main__.MyClass object at 0x013FF8D0>
) thông qua đối số slef
.
Khi phương thức được gọi, Python thay thế đối số self
bằng instance object là obj
.
Chúng ta có thể bỏ qua cú pháp (obj.method()
) và truyền instance object theo cách thủ công để có được kết quả tương tự:
obj = MyClass()
print(MyClass.method(obj))
#Kết quả ('instance method', <__main__.MyClass object at 0x0150F8D0>)
Bạn có thể đoán điều gì sẽ xảy ra nếu bạn cố gọi phương thức mà không tạo instance trước không?
Nhân tiện, các instance method cũng có thể truy cập vào chính class đó thông qua thuộc tính self.__ class__
.
Điều này làm cho các instance method trở nên mạnh mẽ về các hạn chế truy cập - chúng có thể sửa đổi trạng thái trên instance object và trên chính class đó.
Hãy thử gọi phương thức tiếp theo xem nào:
obj = MyClass()
print(obj.classmethod())
#Kết quả ('class method', <class '__main__.MyClass'>)
Việc gọi classmethod()
cho chúng ta thấy nó không có quyền truy cập vào đối tượng <MyClass instance>
, nhưng chỉ với đối tượng <class MyClass>
, đại diện cho chính class đó (mọi thứ trong Python là một đối tượng, ngay cả chính các class).
Lưu ý cách Python tự động chuyển class làm đối số đầu tiên cho hàm khi chúng ta gọi MyClass.classmethod()
.
Gọi một phương thức trong Python thông qua cú pháp dấu chấm kích hoạt hành vi này. Tham số self
trên các instance method hoạt động theo cùng một cách.
Xin lưu ý rằng việc tự đặt tên cho các tham số này là self
và cls
cũng chỉ là quy ước.
Bạn có thể dễ dàng đặt tên cho chúng là the_object
và the_group
và nhận được kết quả tương tự. Bởi vì Python đã định vị tham số này là tham số đầu tiên trong phương thức.
Bây giờ chúng ta thử gọi static method:
obj = MyClass()
print(obj.staticmethod())
#Kết quả static method
Bạn có thấy cách chúng ta gọi staticmethod()
trên đối tượng và có thể thực hiện thành công không?
Một số lập trình viên ngạc nhiên khi họ biết rằng nó có thể gọi một phương thức tĩnh trên một instance object.
Thực ra, Python chỉ thực thi các hạn chế truy cập bằng cách không truyền vào đối số self
hoặc cls
khi một static method được gọi bằng cú pháp dấu chấm.
Bây giờ, hãy xem điều gì xảy ra khi chúng ta cố gắng gọi các phương thức này trên chính class đó - mà không tạo ra một instance object trước đó:
print(MyClass.classmethod())
#Kết quả: ('class method', <class '__main__.MyClass'>)
print(MyClass.staticmethod())
#Kết quả: static method
print(MyClass.method())
#Kết quả: TypeError: method() missing 1 required positional argument: 'self'
Chúng ta có thể gọi classmethod()
và staticmethod()
, nhưng cố gắng gọi instance medthod method()
sẽ gây ra lỗi.
Lần này chúng ta đã không tạo ra một instance object và đã thử gọi một instance function trực tiếp trên bản thiết kế của class.
Điều này có nghĩa là không có cách nào để Python đưa ra đối số self
và do đó nó thất bại.
Điều này sẽ làm cho bạn thấy sự khác biệt giữa ba loại phương thức này rõ ràng hơn một chút. Nhưng mình sẽ không dừng tại đây.
Trong hai phần tiếp theo, chúng ta sẽ đi qua hai ví dụ thực tế hơn một chút để sử dụng các loại phương thức đặc biệt này.
Mình sẽ dựa trên các ví dụ của mình xung quanh class Pizza này:
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
Pizza(['cheese', 'tomatoes'])
Pizza(['cheese', 'tomatoes'])
Lưu ý: Ví dụ này và các ví dụ khác trong hướng dẫn sử dụng trên phiên bản 3.6 trở đi để xây dựng chuỗi được trả về bởi __repr__. Trên Python 2 và các phiên bản Python 3 trước 3.6, bạn sử dụng một biểu thức định dạng chuỗi khác nhau, ví dụ:
def __repr__(self):
return 'Pizza(%r)' % self.ingredients
Ví dụ thực tế về class method trong Python
Nếu bạn đã từng tiếp ăn pizza trong thế giới thực, bạn sẽ biết rằng có rất nhiều biến thể ngon có sẵn:
Pizza(['mozzarella', 'tomatoes'])
Pizza(['mozzarella', 'tomatoes', 'ham', 'mushrooms'])
Pizza(['mozzarella'] * 4)
Người Ý đã tìm ra cách phân loại pizza của họ từ nhiều thế kỷ trước, và vì vậy những loại pizza ngon này đều có tên riêng.
Chúng ta làm rất tốt để tận dụng lợi thế đó và cung cấp cho người dùng class Pizza của chúng ta một interface tốt hơn để tạo các đối tượng Pizza
mà họ muốn.
Một cách hay và gọn gàng để làm điều đó là sử dụng các class method:
class Pizza:
def __init__(self, ingredients):
self.ingredients = ingredients
def __repr__(self):
return f'Pizza({self.ingredients!r})'
@classmethod
def margherita(cls):
return cls(['mozzarella', 'tomatoes'])
@classmethod
def prosciutto(cls):
return cls(['mozzarella', 'tomatoes', 'ham'])
Lưu ý cách mình sử dụng đối số cls
trong các phương thức margherita
và prosciutto
thay vì gọi trực tiếp Pizza
constructor.
Đây là một mẹo bạn có thể sử dụng để tuân theo nguyên tắc Don't Repeat Yourself (DRY).
Nếu chúng ta quyết định đổi tên class này tại một số điểm, chúng ta sẽ phải nhớ cập nhật tên constructor trong tất cả các classmethod.
Bây giờ, chúng ta có thể làm gì với các phương thức? Hãy để thử dùng chúng:
print(Pizza.margherita())
#Kết quả: Pizza(['mozzarella', 'tomatoes'])
print(Pizza.prosciutto())
#Kết quả: Pizza(['mozzarella', 'tomatoes', 'ham'])
Như bạn có thể thấy, chúng ta có thể sử dụng các phương thức này để tạo các đối tượng Pizza mới được cấu hình theo cách chúng ta muốn.
Tất cả đều sử dụng cùng một hàm tạo __init__
trong nội bộ và chỉ cần cung cấp một lối tắt để ghi nhớ tất cả các thành phần khác nhau.
Một cách khác để xem xét việc sử dụng các class method này là chúng cho phép bạn xác định các constructor thay thế cho các class của mình.
Python chỉ cho phép một phương thức __init__
mỗi class. Sử dụng các class method, nó có thể thêm nhiều constructor thay thế nếu cần.
Điều này có thể làm interface cho các class của bạn tự ghi lại tài liệu (ở một mức độ nhất định) và đơn giản hóa việc sử dụng chúng.
Khi nào sử dụng Static Method trong Python?
Hơi khó một chút để đưa ra một ví dụ tốt ở đây.
Đây là những gì mà mình nghĩ ra:
import math
class Pizza:
def __init__(self, radius, ingredients):
self.radius = radius
self.ingredients = ingredients
def __repr__(self):
return (f'Pizza({self.radius!r}, '
f'{self.ingredients!r})')
def area(self):
return self.circle_area(self.radius)
@staticmethod
def circle_area(r):
return r ** 2 * math.pi
Mình đã thay đổi gì ở đây? Đầu tiên, mình đã sửa đổi constructor và __repr__
để chấp nhận thêm đối số radius
.
Mình cũng đã thêm một phương thức ví dụ area()
để tính toán và trả về diện tích pizza.
Thay vì tính diện tích trực tiếp trong area()
, bằng cách sử dụng công thức diện tích hình tròn, mình đã thực hiện điều đó theo một static method circle_area()
riêng biệt.
Hãy để thử xem nào!
p = Pizza(4, ['mozzarella', 'tomatoes'])
print(p)
#Kết quả: Pizza(4, ['mozzarella', 'tomatoes'])
print(p.area())
#Kết quả: 50.26548245743669
print(Pizza.circle_area(4))
#Kết quả: 50.26548245743669
Chắc chắn, đây là một ví dụ đơn giản, nhưng nó sẽ giúp giải thích một số lợi ích mà các static cung cấp.
Như chúng ta đã học được, các static method có thể truy cập vào class hoặc instance state bởi vì chúng không có một đối số cls
hoặc self
.
Đó là một hạn chế lớn - nhưng nó cũng là một tín hiệu tốt để chỉ ra rằng một phương thức cụ thể là độc lập với mọi thứ khác xung quanh nó.
Trong ví dụ trên, có một điều rõ ràng là circle_area()
có thể sửa đổi class hoặc thể hiện của class theo bất kỳ cách nào.
Bây giờ, tại sao phương thức static lại hữu ích?
Việc gắn cờ một phương thức như một static method không chỉ là một gợi ý rằng một phương thức không được thay đổi trạng thái của class hoặc thể hiện của class - hạn chế này cũng được thi hành bởi Python Runtime.
Các kỹ thuật như thế cho phép bạn giao tiếp rõ ràng về các phần của kiến trúc class của bạn để công việc lập trình mới được hướng dẫn một cách tự nhiên trong các ranh giới đã đặt ra.
Tất nhiên, nó có thể vượt qua hạn chế này. Nhưng trong thực tế, chúng thường giúp tránh các sửa đổi ngẫu nhiên đi ngược lại với thiết kế ban đầu.
Nói cách khác, sử dụng các static method và class method là cách để truyền đạt ý định của lập trình viên, đồng thời thực thi ý định đó đủ để tránh hầu hết các lỗi các lỗi có thể phá vỡ thiết kế.
Áp dụng một cách hợp lý, viết một số phương thức của bạn theo cách đó có thể mang lại lợi ích bảo trì và làm cho khả năng các lập trình viên khác sử dụng các class của bạn không chính xác thấp hơn.
Các static method cũng có lợi ích khi viết code kiểm thử.
Bởi vì phương thức circle_area()
hoàn toàn độc lập với phần còn lại của class nên nó dễ kiểm tra hơn nhiều.
Chúng ta không phải lo lắng về việc thiết lập một thể hiện của class hoàn chỉnh trước khi chúng ta có thể kiểm tra phương thức này trong một bài kiểm tra unit test.
Một lần nữa, cách làm này đảm bảo cho tương lai code dễ bảo trì hơn.
Lời kết
Các instance method cần một thể thiện của class và có thể truy cập thể hiện này thông qua self.
Các class method không cần một thể hiện của class. Chúng không thể truy cập vào thể hiện (self) nhưng chúng có quyền truy cập vào chính class của nó thông qua cls
.
Các static method không có quyền truy cập vào cls
hoặc self
. Chúng hoạt động như các hàm thông thường nhưng thuộc về namespace của class.
Các static method và class method thực thi ý định của lập trình viên về thiết kế class. Điều này có thể có lợi cho việc bảo trì sau này.
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp #python #java #php