Bản chất của việc sử dụng lớp là gọi các phương thức nội tại của nó.
Phương thức `__getitem__`, `__setitem__`, `__delitem__`, `__delattr__`
Các phương thức này được kích hoạt khi thuộc tính của đối tượng được truy cập, sửa đổi hoặc xóa.
class ExampleClass:
def __init__(self, initial_value):
self.attribute = initial_value
def __getitem__(self, key):
# Truy cập thuộc tính bằng cú pháp []
print(f"Truy cập thuộc tính '{key}' bằng []:")
if key in self.__dict__:
return self.__dict__[key]
else:
raise KeyError(f"Thuộc tính '{key}' không tồn tại.")
def __setitem__(self, key, value):
# Thiết lập thuộc tính bằng cú pháp []
print(f"Thiết lập thuộc tính '{key}' thành '{value}' bằng []:")
self.__dict__[key] = value
def __delitem__(self, key):
# Xóa thuộc tính bằng cú pháp []
print(f"Xóa thuộc tính '{key}' bằng [] được kích hoạt.")
if key in self.__dict__:
del self.__dict__[key]
else:
print(f"Thuộc tính '{key}' không tồn tại để xóa.")
def __delattr__(self, name):
# Xóa thuộc tính bằng cú pháp del obj.attribute
print(f"Xóa thuộc tính '{name}' bằng del obj.attribute được kích hoạt.")
if name in self.__dict__:
del self.__dict__[name]
else:
print(f"Thuộc tính '{name}' không tồn tại để xóa.")
# Sử dụng lớp
obj = ExampleClass("Giá trị ban đầu")
# Truy cập thuộc tính thông thường
print(f"Truy cập thông thường: {obj.attribute}")
# Sử dụng __getitem__
print(f"Truy cập bằng []: {obj['attribute']}")
# Sử dụng __setitem__
obj['new_attribute'] = "Giá trị mới"
print(f"Truy cập thuộc tính mới tạo bằng []: {obj['new_attribute']}")
# Sử dụng __getattr__ (mặc định)
obj.another_attribute = "Thuộc tính khác"
print(f"Truy cập thuộc tính khác thông thường: {obj.another_attribute}")
# Sử dụng __setitem__ để sửa đổi thuộc tính
obj['attribute'] = "Giá trị đã sửa đổi"
print(f"Truy cập thuộc tính đã sửa đổi bằng []: {obj['attribute']}")
# Sử dụng __delitem__
del obj['new_attribute']
try:
print(obj['new_attribute'])
except KeyError as e:
print(e)
# Sử dụng __delattr__
del obj.attribute
try:
print(obj.attribute)
except AttributeError as e:
print(e)
print(f"Trạng thái __dict__ của đối tượng: {obj.__dict__}")
Phương thức Nội tại `__slots__`
Theo mặc định, các thuộc tính của một đối tượng được lưu trữ trong một từ điển gọi là `__dict__`. Từ điển này có thể tốn bộ nhớ, đặc biệt khi có nhiều đối tượng với ít thuộc tính. `__slots__` cho phép định nghĩa một tập hợp các thuộc tính được phép cho một lớp, giúp tiết kiệm bộ nhớ bằng cách không tạo `__dict__` cho mỗi đối tượng.
Lưu ý: Khi sử dụng `__slots__`, bạn sẽ không thể thêm thuộc tính mới vào đối tượng sau khi nó đã được tạo, trừ khi thuộc tính đó nằm trong `__slots__`.
class LimitedProperties:
__slots__ = ['prop_a', 'prop_b', 'prop_c']
def __init__(self, val_a, val_b):
self.prop_a = val_a
self.prop_b = val_b
# Sử dụng lớp
limited_obj = LimitedProperties(10, 20)
limited_obj.prop_c = 30 # Hợp lệ vì 'prop_c' nằm trong __slots__
print(f"Prop A: {limited_obj.prop_a}, Prop B: {limited_obj.prop_b}, Prop C: {limited_obj.prop_c}")
# limited_obj.new_prop = 40 # Điều này sẽ gây ra lỗi AttributeError vì 'new_prop' không có trong __slots__
# print(limited_obj.new_prop)
Triển khai Giao thức Iterator: Phương thức Nội tại `__iter__` và `__next__`
Các phương thức này cho phép một đối tượng trở thành một iterator, có nghĩa là bạn có thể lặp qua nó bằng các vòng lặp như for hoặc sử dụng hàm next().
from collections.abc import Iterable, Iterator
class SimpleCounter:
def __init__(self, start_value):
self.current = start_value
def __iter__(self):
# Trả về chính đối tượng vì nó cũng là một iterator
return self
def __next__(self):
# Trả về giá trị tiếp theo và tăng biến đếm
value_to_return = self.current
self.current += 1
return value_to_return
# Sử dụng lớp
counter = SimpleCounter(0)
print(next(counter)) # Xuất ra 0
print(next(counter)) # Xuất ra 1
print(next(counter)) # Xuất ra 2
# Vòng lặp for sẽ tự động gọi __iter__ và __next__
print("Sử dụng vòng lặp for:")
for i in counter:
if i >= 5: # Đặt một điều kiện dừng để tránh lặp vô hạn
break
print(i)
Triển khai Lớp Tương tự range với Xử lý StopIteration trong `__next__`
Ví dụ này minh họa cách tạo một lớp tùy chỉnh có thể lặp lại, tương tự như hàm range của Python, bằng cách sử dụng __iter__ và __next__, đồng thời xử lý việc kết thúc lặp bằng cách ném ra ngoại lệ StopIteration.
class CustomRange:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration("Đã đạt đến điểm kết thúc của phạm vi.")
value_to_return = self.current
self.current += 1
return value_to_return
# Sử dụng lớp CustomRange
print("Sử dụng CustomRange:")
for num in CustomRange(3, 8):
print(num)
# Ví dụ với next() và xử lý StopIteration
print("\nSử dụng next() với CustomRange:")
range_iterator = CustomRange(10, 13)
print(next(range_iterator)) # 10
print(next(range_iterator)) # 11
print(next(range_iterator)) # 12
try:
print(next(range_iterator)) # Sẽ ném StopIteration
except StopIteration as e:
print(f"Đã bắt ngoại lệ: {e}")
Phương thức Huỷ (Destructor) `__del__`
Phương thức __del__ được gọi khi một đối tượng sắp bị dọn dẹp bộ nhớ (garbage collection). Tuy nhiên, trong hầu hết các trường hợp, bạn không cần định nghĩa phương thức này vì Python quản lý bộ nhớ tự động. Nó thường chỉ hữu ích trong các tình huống quản lý tài nguyên cấp thấp hoặc khi cần giải phóng tài nguyên một cách rõ ràng.
Trong ví dụ sau, __del__ được sử dụng trong một lớp quản lý tệp, nhưng việc sử dụng context manager (with statement) với __enter__ và __exit__ là cách tiếp cận được khuyến nghị hơn để quản lý tài nguyên tệp.
import time
class ResourceManager:
def __init__(self, name):
self.name = name
print(f"Tài nguyên '{self.name}' được khởi tạo.")
def __enter__(self):
print(f"Vào khối 'with' cho '{self.name}'.")
# Trả về chính đối tượng để có thể truy cập qua biến 'as'
return self
def do_something(self, data):
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
print(f"{timestamp}: '{self.name}' đang xử lý dữ liệu: {data}")
def __getattr__(self, item):
# Chuyển tiếp các truy cập thuộc tính không xác định tới một đối tượng nội bộ
# Ví dụ: nếu có đối tượng tệp nội bộ, bạn có thể chuyển tiếp các phương thức của tệp
print(f"Truy cập thuộc tính không xác định '{item}', chuyển tiếp...")
# Giả định có một thuộc tính nội bộ 'internal_resource'
# return getattr(self.internal_resource, item)
# Trong ví dụ này, chúng ta chỉ đơn giản là trả về một hàm giả
return lambda *args, **kwargs: print(f"Gọi phương thức '{item}' với {args}, {kwargs}")
def __del__(self):
# Phương thức __del__ sẽ được gọi khi đối tượng bị thu gom rác
print(f"--- Phương thức __del__ được gọi để dọn dẹp tài nguyên '{self.name}'. ---")
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Thoát khối 'with' cho '{self.name}'.")
if exc_type:
print(f"Xảy ra lỗi trong khối 'with': Loại={exc_type}, Giá trị={exc_val}")
# Thực hiện dọn dẹp tài nguyên ở đây
print(f"Tài nguyên '{self.name}' đã được giải phóng.")
# Trả về False để nổi lại ngoại lệ nếu có, hoặc True để chặn ngoại lệ
return False
# Sử dụng context manager
print("Bắt đầu ví dụ với context manager:")
with ResourceManager("Tệp cấu hình") as resource_manager:
resource_manager.do_something("Dữ liệu quan trọng")
# Giả lập truy cập một phương thức không tồn tại trực tiếp trên ResourceManager
resource_manager.close()
print("Kết thúc ví dụ với context manager.")
# Garbage collection có thể không xảy ra ngay lập tức sau khi khối 'with' kết thúc.
# Lời gọi __del__ sẽ xảy ra khi Python quyết định dọn dẹp đối tượng.
# Để thấy __del__ rõ ràng hơn, đôi khi cần để đối tượng ra khỏi phạm vi hoặc gọi gc.collect() (không khuyến khích dùng thường xuyên).
# Ví dụ khác cho thấy __del__ khi đối tượng không còn được tham chiếu
print("\nVí dụ về __del__ khi đối tượng không còn tham chiếu:")
def create_and_lose_reference():
temp_resource = ResourceManager("Tài nguyên tạm thời")
temp_resource.do_something("Dữ liệu tạm")
# Khi hàm kết thúc, 'temp_resource' không còn được tham chiếu bên ngoài,
# có thể dẫn đến việc gọi __del__ nếu Python tiến hành thu gom rác.
create_and_lose_reference()
print("Hàm create_and_lose_reference đã thực thi.")
# Lưu ý: Việc gọi __del__ không được đảm bảo thực hiện ngay lập tức.
Phương thức Nội tại `__call__` - Gọi Đối tượng như một Hàm
Phương thức __call__ cho phép bạn gọi một đối tượng như thể nó là một hàm. Nếu một lớp có định nghĩa phương thức __call__, thì các đối tượng được tạo từ lớp đó sẽ trở thành callable.
class CallableObject:
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
# Phương thức này được gọi khi đối tượng được gọi như một hàm
print(f"Đối tượng '{self.name}' đã được gọi!")
print(f"Các tham số vị trí: {args}")
print(f"Các tham số từ khóa: {kwargs}")
return f"Kết quả từ {self.name}"
# Tạo đối tượng
my_callable = CallableObject("MyProcessor")
# Kiểm tra xem đối tượng có thể gọi được không
print(f"Đối tượng 'my_callable' có thể gọi được không? {callable(my_callable)}") # Sẽ in ra True
# Gọi đối tượng như một hàm
result = my_callable("dữ liệu 1", thamso2="giá trị 2")
print(f"Kết quả của lệnh gọi: {result}")
# Nếu không có __call__, đối tượng sẽ không thể gọi được
class NonCallableObject:
def __init__(self, name):
self.name = name
non_callable_instance = NonCallableObject("MyNonProcessor")
print(f"Đối tượng 'non_callable_instance' có thể gọi được không? {callable(non_callable_instance)}") # Sẽ in ra False
# non_callable_instance() # Điều này sẽ gây ra lỗi TypeError