Vì các lớp là các thể hiện của siêu dữ liệu, không có gì bất ngờ khi một "phương thức cá thể" trên siêu dữ liệu sẽ hoạt động giống như một lớp đối xứng.
Tuy nhiên, vâng, có những khác biệt - và một số trong số chúng còn hơn cả ngữ nghĩa:
- Sự khác biệt quan trọng nhất là một phương thức trong siêu dữ liệu không "hiển thị" từ một thể hiện của lớp . Điều đó xảy ra bởi vì tra cứu thuộc tính trong Python (theo cách đơn giản hóa - mô tả có thể được ưu tiên) tìm kiếm một thuộc tính trong thể hiện - nếu nó không có trong cá thể, thì Python sẽ tìm trong lớp của cá thể đó, và sau đó tìm kiếm tiếp tục các siêu lớp của lớp, nhưng không phải trên các lớp của lớp. Stdlib Python sử dụng tính năng này trong
abc.ABCMeta.register
phương thức. Tính năng đó có thể được sử dụng tốt, vì các phương thức liên quan đến chính lớp được tự do sử dụng lại làm thuộc tính cá thể mà không có bất kỳ xung đột nào (nhưng một phương thức vẫn sẽ xung đột).
- Tuy nhiên, một sự khác biệt khác là một phương thức được khai báo trong siêu dữ liệu có thể có sẵn trong một số lớp, không liên quan đến nhau - nếu bạn có hệ thống phân cấp lớp khác nhau, hoàn toàn không liên quan đến cái gì chúng xử lý, nhưng muốn có một số chức năng chung cho tất cả các lớp , bạn phải đưa ra một lớp mixin, sẽ phải được đưa vào làm cơ sở trong cả hai cấu trúc phân cấp (giả sử bao gồm tất cả các lớp trong sổ đăng ký ứng dụng). (NB. Mixin đôi khi có thể là một cuộc gọi tốt hơn so với siêu dữ liệu)
- Classmethod là một đối tượng "classmethod" chuyên biệt, trong khi một phương thức trong metaclass là một hàm thông thường.
Vì vậy, điều xảy ra là cơ chế mà classmethod sử dụng là " giao thức mô tả ". Trong khi các hàm bình thường có một __get__
phương thức sẽ chèn self
đối số khi chúng được truy xuất từ một thể hiện và để trống đối số đó khi được truy xuất từ một lớp, một classmethod
đối tượng có một đối tượng khác__get__
, nó sẽ chèn chính lớp đó ("chủ sở hữu") làm tham số đầu tiên trong cả hai tình huống.
Điều này không tạo ra sự khác biệt thực tế trong hầu hết thời gian, nhưng nếu bạn muốn truy cập vào phương thức như một hàm, với mục đích thêm động trang trí vào nó hoặc bất kỳ phương thức nào khác, cho một phương thức trong siêu dữ liệu meta.method
lấy ra hàm, sẵn sàng sử dụng , trong khi bạn phải sử dụng cls.my_classmethod.__func__
để truy xuất nó từ một classmethod (và sau đó bạn phải tạo một classmethod
đối tượng khác và gán lại cho nó, nếu bạn thực hiện một số gói).
Về cơ bản, đây là 2 ví dụ:
class M1(type):
def clsmethod1(cls):
pass
class CLS1(metaclass=M1):
pass
def runtime_wrap(cls, method_name, wrapper):
mcls = type(cls)
setattr(mcls, method_name, wrapper(getatttr(mcls, method_name)))
def wrapper(classmethod):
def new_method(cls):
print("wrapper called")
return classmethod(cls)
return new_method
runtime_wrap(cls1, "clsmethod1", wrapper)
class CLS2:
@classmethod
def classmethod2(cls):
pass
def runtime_wrap2(cls, method_name, wrapper):
setattr(cls, method_name, classmethod(
wrapper(getatttr(cls, method_name).__func__)
)
)
runtime_wrap2(cls1, "clsmethod1", wrapper)
Nói cách khác: ngoài sự khác biệt quan trọng mà một phương thức được định nghĩa trong siêu dữ liệu có thể nhìn thấy từ thể hiện và một classmethod
đối tượng thì không, sự khác biệt khác, trong thời gian chạy sẽ có vẻ mơ hồ và vô nghĩa - nhưng điều đó xảy ra vì ngôn ngữ không cần phải đi theo cách của nó với các quy tắc đặc biệt cho phân loại: Cả hai cách khai báo phân loại đều có thể, do hậu quả của thiết kế ngôn ngữ - một, vì thực tế là một lớp tự nó là một đối tượng, và một khả năng khác, như một khả năng của nhiều người, việc sử dụng giao thức mô tả cho phép một người chuyên truy cập thuộc tính trong một thể hiện và trong một lớp:
Nội dung classmethod
được xác định trong mã gốc, nhưng nó chỉ có thể được mã hóa bằng python thuần và sẽ hoạt động theo cùng một cách chính xác. Dưới đây là lớp 5 dòng có thể được sử dụng như một công cụ classmethod
trang trí không có sự khác biệt về thời gian chạy so với repr` @classmethod" at all (though distinguishable through introspection such as calls to
isinstance , and even
repr` tích hợp):
class myclassmethod:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, owner):
return lambda *args, **kw: self.__func__(owner, *args, **kw)
Và, ngoài các phương thức, điều thú vị là hãy nhớ rằng các thuộc tính chuyên biệt như @property
trên siêu dữ liệu sẽ hoạt động như các thuộc tính lớp chuyên biệt, giống nhau, không có hành vi đáng ngạc nhiên nào cả.