@ Câu trả lời Oddthinking là không sai, nhưng tôi nghĩ rằng nó bỏ lỡ thực , thực tế nguyên nhân Python có kiến thức cơ bản trong một thế giới của vịt-gõ.
Các phương pháp trừu tượng là gọn gàng, nhưng theo tôi, chúng không thực sự lấp đầy bất kỳ trường hợp sử dụng nào chưa được bao phủ bởi cách gõ vịt. Sức mạnh thực sự của các lớp cơ sở trừu tượng nằm ở cách chúng cho phép bạn tùy chỉnh hành vi của isinstance
vàissubclass
. ( __subclasshook__
về cơ bản là một API thân thiện hơn so với Python __instancecheck__
và__subclasscheck__
hook.) Việc điều chỉnh các cấu trúc dựng sẵn để hoạt động trên các loại tùy chỉnh là một phần rất lớn trong triết lý của Python.
Mã nguồn của Python là mẫu mực. Đây là cách collections.Container
định nghĩa trong thư viện chuẩn (tại thời điểm viết):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Định nghĩa này __subclasshook__
nói rằng bất kỳ lớp nào có __contains__
thuộc tính đều được coi là một lớp con của Container, ngay cả khi nó không phân lớp trực tiếp. Vì vậy, tôi có thể viết này:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
Nói cách khác, nếu bạn thực hiện đúng giao diện, bạn là một lớp con! ABC cung cấp một cách chính thức để xác định các giao diện trong Python, trong khi vẫn đúng với tinh thần gõ vịt. Bên cạnh đó, điều này hoạt động theo cách tôn vinh Nguyên tắc đóng mở .
Mô hình đối tượng của Python trông bề ngoài tương tự như hệ thống OO "truyền thống" hơn (ý tôi là Java *) - chúng ta có các lớp yer, đối tượng yer, phương thức yer - nhưng khi bạn nhìn vào bề mặt, bạn sẽ thấy thứ gì đó phong phú hơn và Linh hoạt hơn. Tương tự như vậy, khái niệm của Python về các lớp cơ sở trừu tượng có thể được nhà phát triển Java nhận ra, nhưng trong thực tế, chúng được dành cho một mục đích rất khác.
Đôi khi tôi thấy mình viết các hàm đa hình có thể hoạt động trên một vật phẩm hoặc một bộ sưu tập các vật phẩm và tôi thấy isinstance(x, collections.Iterable)
nó dễ đọc hơn nhiều so với hasattr(x, '__iter__')
hoặc một try...except
khối tương đương . (Nếu bạn không biết Python, cái nào trong ba cái đó sẽ làm cho ý định của mã rõ ràng nhất?)
Điều đó nói rằng, tôi thấy rằng tôi hiếm khi cần phải viết ABC của riêng mình và tôi thường phát hiện ra sự cần thiết thông qua tái cấu trúc. Nếu tôi thấy một hàm đa hình thực hiện nhiều kiểm tra thuộc tính hoặc nhiều hàm thực hiện kiểm tra thuộc tính tương tự, thì mùi đó cho thấy sự tồn tại của ABC đang chờ được trích xuất.
* mà không cần phải tranh luận về việc liệu Java có phải là một hệ thống OO "truyền thống" không ...
Phụ lục : Mặc dù một lớp cơ sở trừu tượng có thể ghi đè hành vi của isinstance
và issubclass
, nhưng nó vẫn không nhập MRO của lớp con ảo. Đây là một cạm bẫy tiềm tàng cho khách hàng: không phải mọi đối tượng isinstance(x, MyABC) == True
có các phương thức được xác định trên MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Thật không may, đây là một trong những cái bẫy "chỉ không làm như vậy" (trong đó Python có khá ít!): Tránh xác định ABC bằng cả hai __subclasshook__
phương thức và không trừu tượng. Hơn nữa, bạn nên làm cho định nghĩa của bạn __subclasshook__
phù hợp với tập hợp các phương thức trừu tượng mà ABC định nghĩa.
__contains__
và một lớp kế thừa làcollections.Container
gì? Trong ví dụ của bạn, trong Python luôn có sự hiểu biết chung__str__
. Việc thực hiện__str__
đưa ra những lời hứa giống như kế thừa từ một số ABC và sau đó thực hiện__str__
. Trong cả hai trường hợp, bạn có thể phá vỡ hợp đồng; không có ngữ nghĩa có thể chứng minh được như những gì chúng ta có trong gõ tĩnh.