__base__ trong trăn

Trong các bản phát hành Python trước [và vẫn còn trong 1. 5], có một thứ được gọi là ``Móc Don Beaudry'', theo tên nhà phát minh và nhà vô địch của nó. Điều này cho phép các phần mở rộng C cung cấp hành vi lớp thay thế, do đó cho phép cú pháp lớp Python được sử dụng để xác định các thực thể giống như lớp khác. Don Beaudry đã sử dụng điều này trong gói MESS khét tiếng của mình; . [Nó cũng được gọi là `` Vụ hack Don Beaudry, '' nhưng đó là cách gọi sai. Nó không có gì là phá cách -- thực tế, nó khá tao nhã và sâu sắc, mặc dù có gì đó đen tối trong đó. ]

[Khi đọc lần đầu, bạn có thể muốn bỏ qua trực tiếp các ví dụ trong phần "Viết siêu dữ liệu trong Python" bên dưới, trừ khi bạn muốn nổ tung đầu. ]

Tài liệu về lưỡi câu Don Beaudry đã cố tình giữ ở mức tối thiểu, vì nó là một tính năng có sức mạnh đáng kinh ngạc và dễ bị lạm dụng. Về cơ bản, nó kiểm tra xem loại của lớp cơ sở có thể gọi được hay không và nếu có, nó được gọi để tạo lớp mới

Lưu ý hai cấp độ gián tiếp. Lấy một ví dụ đơn giản

class B:
    pass

class C[B]:
    pass
Hãy xem định nghĩa lớp thứ hai và cố gắng hiểu ``loại lớp cơ sở có thể gọi được. ''

[Nhân tiện, các loại không phải là các lớp. Xem câu hỏi 4. 2, 4. 19 và đặc biệt là 6. 22 trong Câu hỏi thường gặp về Python để biết thêm về chủ đề này. ]

  • Lớp cơ sở là B;
  • Vì B là một lớp, nên kiểu của nó là ``lớp''; . Đây còn được gọi là các loại. ClassType, giả sử mô-đun tiêu chuẩn
    class C[B]:
        a = 1
        b = 2
    
    3 đã được nhập
  • Bây giờ loại ``class'' có thể gọi được không? . Các lớp có thể gọi được [gọi một lớp sẽ tạo một thể hiện mới] nhưng các loại thì không

Vì vậy, kết luận của chúng tôi là trong ví dụ của chúng tôi, loại lớp cơ sở [của C] không thể gọi được. Vì vậy, hook Don Beaudry không được áp dụng và cơ chế tạo lớp mặc định được sử dụng [cũng được sử dụng khi không có lớp cơ sở]. Trên thực tế, móc Don Beaudry không bao giờ áp dụng khi chỉ sử dụng Python lõi, vì loại đối tượng lõi không bao giờ có thể gọi được

Vậy Don và Jim làm gì để sử dụng cái móc của Don? . Đầu tiên sẽ là kiểu cho các đối tượng ``class-like'' có thể sử dụng như một lớp cơ sở, để kích hoạt Don's hook. Loại này phải được thực hiện có thể gọi được. Đó là lý do tại sao chúng ta cần một loại thứ hai. Một đối tượng có thể gọi được hay không tùy thuộc vào loại của nó. Vì vậy, một đối tượng loại có thể gọi được hay không tùy thuộc vào loại của nó, đó là loại meta. [Trong Python lõi chỉ có một loại meta, loại ``type'' [các loại. TypeType], là kiểu của tất cả các đối tượng kiểu, kể cả chính nó. ] Một loại meta mới phải được xác định để làm cho loại đối tượng giống như lớp có thể gọi được. [Thông thường, cũng cần có loại thứ ba, loại ``thực thể'' mới, nhưng đây không phải là một yêu cầu tuyệt đối -- loại lớp mới có thể trả về một đối tượng thuộc một số loại hiện có khi được gọi để tạo một thực thể. ]

Vẫn còn bối rối? . Lấy một định nghĩa lớp đơn giản;

class C[B]:
    a = 1
    b = 2
Điều này có thể được coi là tương đương với.
C = type[B]['C', [B,], {'a': 1, 'b': 2}]
Nếu điều đó quá dày đặc đối với bạn, thì đây là điều tương tự được viết ra bằng cách sử dụng các biến tạm thời.
creator = type[B]               # The type of the base class
name = 'C'                      # The name of the new class
bases = [B,]                    # A tuple containing the base class[es]
namespace = {'a': 1, 'b': 2}    # The namespace of the class statement
C = creator[name, bases, namespace]
Điều này tương tự với những gì xảy ra khi không có hook Don Beaudry, ngoại trừ trong trường hợp đó, hàm tạo được đặt thành trình tạo lớp mặc định

Trong cả hai trường hợp, người tạo được gọi với ba đối số. Cái đầu tiên, tên, là tên của lớp mới [như được đưa ra ở đầu câu lệnh lớp]. Đối số cơ sở là một bộ các lớp cơ sở [một bộ đơn lẻ nếu chỉ có một lớp cơ sở, như ví dụ]. Cuối cùng, không gian tên là một từ điển chứa các biến cục bộ được thu thập trong quá trình thực thi câu lệnh lớp

Lưu ý rằng nội dung của từ điển không gian tên chỉ đơn giản là bất kỳ tên nào được định nghĩa trong câu lệnh lớp. Một sự thật ít được biết đến là khi Python thực thi một câu lệnh lớp, nó sẽ đi vào một không gian tên cục bộ mới và tất cả các phép gán cũng như định nghĩa hàm diễn ra trong không gian tên này. Vì vậy, sau khi thực hiện câu lệnh lớp sau

class C:
    a = 1
    def f[s]: pass
the class namespace's contents would be {'a': 1, 'f': }.

Nhưng đã đủ về việc viết siêu dữ liệu Python trong C;

Viết Metaclass bằng Python

Trong Trăn 1. 5, yêu cầu viết phần mở rộng C để viết siêu dữ liệu đã bị loại bỏ [mặc dù tất nhiên bạn vẫn có thể làm điều đó]. Ngoài kiểm tra ``là loại của lớp cơ sở có thể gọi được'', còn có kiểm tra `` lớp cơ sở có thuộc tính __class__ không. '' Nếu vậy, giả định rằng thuộc tính __class__ đề cập đến một lớp

Hãy lặp lại ví dụ đơn giản của chúng tôi từ trên

class C[B]:
    a = 1
    b = 2
Giả sử B có thuộc tính __class__, điều này có nghĩa là.
C = B.__class__['C', [B,], {'a': 1, 'b': 2}]
Điều này hoàn toàn giống như trước ngoại trừ thay vì loại [B], B. __class__ được gọi. Nếu bạn đã đọc Câu hỏi thường gặp 6. 22 bạn sẽ hiểu rằng mặc dù có sự khác biệt lớn về mặt kỹ thuật giữa loại [B] và B. __class__, chúng đóng vai trò giống nhau ở các mức độ trừu tượng khác nhau. Và có lẽ tại một thời điểm nào đó trong tương lai, chúng sẽ thực sự giống nhau [tại thời điểm đó, bạn sẽ có thể lấy được các lớp con từ các loại tích hợp sẵn]

Tại thời điểm này, có thể đáng nói rằng C. __class__ là cùng một đối tượng với B. __lớp__, tôi. e. , siêu dữ liệu của C giống với siêu dữ liệu của B. Nói cách khác, việc phân lớp một lớp hiện có sẽ tạo ra một thể hiện [siêu dữ liệu] mới của siêu dữ liệu của lớp cơ sở

Quay trở lại ví dụ, lớp B. __class__ được khởi tạo, truyền cho hàm tạo của nó ba đối số giống như được truyền cho hàm tạo của lớp mặc định hoặc cho siêu dữ liệu của tiện ích mở rộng. tên, cơ sở và không gian tên

Rất dễ bị nhầm lẫn bởi chính xác những gì xảy ra khi sử dụng siêu dữ liệu, bởi vì chúng ta mất đi sự phân biệt tuyệt đối giữa các lớp và các thể hiện. một lớp là một thể hiện của một siêu lớp [một ``metainstance''], nhưng về mặt kỹ thuật [i. e. trong mắt hệ thống thời gian chạy python], siêu dữ liệu chỉ là một lớp và siêu dữ liệu chỉ là một thể hiện. Ở cuối câu lệnh lớp, siêu dữ liệu có siêu dữ liệu được sử dụng làm lớp cơ sở được khởi tạo, tạo ra siêu dữ liệu thứ hai [của cùng một siêu dữ liệu]. Metanstance này sau đó được sử dụng như một lớp [bình thường, không phải meta]; . Và đó là thể hiện của lớp nào?

Hy vọng rằng một ví dụ sẽ làm cho mọi thứ rõ ràng hơn. Giả sử chúng ta có một siêu dữ liệu MetaClass1. Đó là lớp trợ giúp [đối với các trường hợp phi kim loại] được gọi là Lớp trợ giúp 1. Bây giờ chúng tôi [thủ công] khởi tạo MetaClass1 một lần để có được một lớp cơ sở đặc biệt trống

BaseClass1 = MetaClass1["BaseClass1", [], {}]
Bây giờ chúng ta có thể sử dụng BaseClass1 làm lớp cơ sở trong câu lệnh lớp.
class MySpecialClass[BaseClass1]:
    i = 1
    def f[s]: pass
Tại thời điểm này, MySpecialClass được xác định; . __class__ == MySpecialClass. __class__ == MetaClass1'' mang lại kết quả đúng

Bây giờ chúng tôi đã sẵn sàng để tạo các phiên bản của MySpecialClass. Giả sử rằng không có đối số hàm tạo nào được yêu cầu

class C[B]:
    a = 1
    b = 2
0Câu lệnh in cho thấy x và y là các thể hiện của HelperClass1. Làm sao chuyện này lại xảy ra?

Bây giờ hãy xem cách chúng ta có thể sử dụng siêu dữ liệu -- chúng ta có thể làm gì với siêu dữ liệu mà chúng ta không thể dễ dàng thực hiện nếu không có chúng? . một siêu dữ liệu có thể tự động chèn các lệnh gọi theo dõi cho tất cả các lệnh gọi phương thức. Trước tiên, hãy phát triển một ví dụ đơn giản hóa, không hỗ trợ kế thừa hoặc các tính năng ``nâng cao'' khác của Python [chúng tôi sẽ thêm các tính năng đó sau]

class C[B]:
    a = 1
    b = 2
0Bối rối chưa? . Lớp Tracing là siêu dữ liệu mà chúng tôi đang xác định. Cấu trúc của nó thực sự đơn giản
  • Phương thức __init__ được gọi khi một phiên bản Truy tìm mới được tạo, e. g. định nghĩa của lớp MyTracedClass sau trong ví dụ. Nó chỉ lưu tên lớp, các lớp cơ sở và không gian tên dưới dạng các biến thể hiện
  • Phương thức __call__ được gọi khi một phiên bản Truy tìm được gọi, e. g. việc tạo ra một phiên bản sau trong ví dụ. Nó trả về một thể hiện của Instance của lớp, được định nghĩa tiếp theo

Thể hiện của lớp là lớp được sử dụng cho tất cả các thể hiện của các lớp được xây dựng bằng cách sử dụng siêu dữ liệu Truy tìm, e. g. một ví dụ. Nó có hai phương pháp

  • Phương thức __init__ được gọi từ Tracing. __call__ phương thức trên để khởi tạo một thể hiện mới. Nó lưu tham chiếu lớp dưới dạng một biến thể hiện. Nó sử dụng một cái tên ngộ nghĩnh vì các biến đối tượng của người dùng [e. g. bản thân. một ví dụ sau] sống trong cùng một không gian tên
  • Phương thức __getattr__ được gọi bất cứ khi nào mã người dùng tham chiếu đến một thuộc tính của thể hiện không phải là biến thể hiện [cũng không phải là biến lớp; nhưng ngoại trừ __init__ và __getattr__ không có biến lớp]. Nó sẽ được gọi, ví dụ, khi aninstance. method1 được tham chiếu trong ví dụ, với self được đặt thành aninstance và tên được đặt thành chuỗi "method1"

Phương thức __getattr__ tra cứu tên trong từ điển __namespace__. Nếu không tìm thấy, nó sẽ tạo ra một ngoại lệ AttributeError. [Trong một ví dụ thực tế hơn, trước tiên nó cũng phải xem qua các lớp cơ sở. ] Nếu nó được tìm thấy, có hai khả năng. đó là một chức năng hoặc nó không phải là. Nếu nó không phải là một hàm, thì nó được coi là một biến lớp và giá trị của nó được trả về. Nếu nó là một hàm, chúng ta phải ``bọc'' nó trong trường hợp của một lớp trợ giúp khác, BoundMethod

Lớp BoundMethod là cần thiết để triển khai một tính năng quen thuộc. khi một phương thức được xác định, nó có một đối số ban đầu, bản thân, đối số này sẽ tự động được liên kết với thể hiện có liên quan khi nó được gọi. Ví dụ, một trường hợp. method1[10] tương đương với method1[aninstance, 10]. Trong ví dụ nếu lệnh gọi này, trước tiên, một cá thể BoundMethod tạm thời được tạo bằng lệnh gọi hàm tạo sau. temp = BoundMethod[method1, aninstance]; . Sau cuộc gọi, phiên bản tạm thời bị loại bỏ

  • Phương thức __init__ được gọi cho lệnh gọi hàm tạo BoundMethod[method1, aninstance]. Nó chỉ đơn giản là lưu đi các đối số của nó
  • Phương thức __call__ được gọi khi thể hiện của phương thức ràng buộc được gọi, như trong temp[10]. Nó cần gọi method1[aninstance, 10]. Tuy nhiên, dù bản thân. chức năng bây giờ là method1 và self. dụ là aninstance, nó không thể tự gọi. chức năng [tự. instance, args] một cách trực tiếp, bởi vì nó sẽ hoạt động bất kể số lượng đối số được truyền vào. [Để đơn giản, hỗ trợ cho các đối số từ khóa đã được bỏ qua. ]

Để có thể hỗ trợ các danh sách đối số tùy ý, trước tiên, phương thức __call__ sẽ xây dựng một bộ đối số mới. Thuận tiện, vì ký hiệu *args trong danh sách đối số riêng của __call__, các đối số cho __call__ [ngoại trừ self] được đặt trong bộ đối số. Để xây dựng danh sách đối số mong muốn, chúng ta nối một bộ đơn lẻ chứa thể hiện với bộ args. [bản thân. ví dụ,] + args. [Lưu ý dấu phẩy ở cuối được sử dụng để xây dựng bộ dữ liệu đơn lẻ. ] Trong ví dụ của chúng tôi, bộ đối số kết quả là [aninstance, 10]

Hàm nội tại apply[] nhận một hàm và một bộ đối số và gọi hàm cho nó. Trong ví dụ của chúng tôi, chúng tôi đang gọi apply[method1, [aninstance, 10]] tương đương với gọi method[aninstance, 10]

Từ đây trở đi, mọi thứ sẽ đến với nhau khá dễ dàng. Đầu ra của mã ví dụ là như thế này

class C[B]:
    a = 1
    b = 2
1

Đó là về ví dụ có ý nghĩa ngắn nhất mà tôi có thể nghĩ ra. Một siêu dữ liệu theo dõi thực [ví dụ, được thảo luận bên dưới] cần phải phức tạp hơn theo hai chiều

Đầu tiên, nó cần hỗ trợ các tính năng nâng cao hơn của Python như biến lớp, thừa kế, phương thức __init__ và đối số từ khóa

Thứ hai, nó cần cung cấp một cách linh hoạt hơn để xử lý thông tin theo dõi thực tế; . Ngay cả dấu vết. ví dụ py chưa hỗ trợ tất cả các tính năng này

Ví dụ thực tế

Hãy xem một số ví dụ rất sơ bộ mà tôi đã mã hóa để dạy bản thân cách viết siêu dữ liệu

liệt kê. pyThis [ab] sử dụng cú pháp lớp như một cách tao nhã để xác định các kiểu liệt kê. Các lớp kết quả không bao giờ được khởi tạo -- thay vào đó, các thuộc tính lớp của chúng là các giá trị được liệt kê. Ví dụ.
class C[B]:
    a = 1
    b = 2
2sẽ in chuỗi ``Color. đỏ'', trong khi ``Màu. red==1'' là true, và ``Color. đỏ + 1'' tăng ngoại lệ TypeError. Dấu vết. pyCác lớp kết quả hoạt động giống như các lớp tiêu chuẩn, nhưng bằng cách thiết lập một thuộc tính thể hiện hoặc lớp đặc biệt __trace_output__ để trỏ đến một tệp, tất cả các cuộc gọi đến các phương thức của lớp đều được theo dõi. Đó là một chút đấu tranh để có được điều này đúng. Điều này có lẽ nên làm lại bằng cách sử dụng siêu dữ liệu chung bên dưới. meta. siêu dữ liệu chung pyA. Đây là một nỗ lực nhằm tìm hiểu xem siêu dữ liệu có thể bắt chước bao nhiêu hành vi của lớp tiêu chuẩn. Câu trả lời sơ bộ dường như là mọi thứ đều ổn miễn là lớp [hoặc máy khách của nó] không nhìn vào thuộc tính __class__ của cá thể, cũng như thuộc tính __dict__ của lớp. Việc sử dụng __getattr__ trong nội bộ làm cho việc triển khai cổ điển của __getattr__ hook trở nên khó khăn; . [__setattr__ và __delattr__ không bị ảnh hưởng. ] [XXX tiếng. Có thể phát hiện sự hiện diện của __getattr__ và đổi tên nó. ]Eiffel. pySử dụng siêu dữ liệu chung ở trên để triển khai tiền điều kiện và hậu điều kiện kiểu Eiffel. đồng bộ. pySử dụng siêu dữ liệu chung ở trên để triển khai các phương thức được đồng bộ hóa. Giản dị. pyMô-đun ví dụ được sử dụng ở trên

Một mô hình dường như đang nổi lên. hầu hết tất cả những cách sử dụng siêu dữ liệu này [ngoại trừ Enum, có lẽ dễ thương hơn là hữu ích] chủ yếu hoạt động bằng cách đặt các trình bao bọc xung quanh các lệnh gọi phương thức. Một vấn đề rõ ràng với điều đó là không dễ để kết hợp các tính năng của các siêu dữ liệu khác nhau, trong khi điều này thực sự sẽ khá hữu ích. ví dụ: tôi không ngại nhận được dấu vết từ quá trình chạy thử mô-đun Đồng bộ hóa và sẽ rất thú vị nếu thêm các điều kiện tiên quyết vào mô-đun đó. Cái này cần nghiên cứu thêm. Có lẽ một siêu dữ liệu có thể được cung cấp cho phép các hàm bao có thể xếp chồng lên nhau

Những điều bạn có thể làm với siêu dữ liệu

Có rất nhiều điều bạn có thể làm với siêu dữ liệu. Hầu hết những điều này cũng có thể được thực hiện bằng cách sử dụng sáng tạo __getattr__, nhưng siêu dữ liệu giúp dễ dàng sửa đổi hành vi tra cứu thuộc tính của các lớp. Đây là một phần danh sách

  • Thực thi ngữ nghĩa kế thừa khác nhau, e. g. tự động gọi các phương thức của lớp cơ sở khi một lớp dẫn xuất ghi đè
  • Thực hiện các phương thức lớp [e. g. nếu đối số đầu tiên không được đặt tên là 'self']
  • Thực hiện rằng mỗi phiên bản được khởi tạo với các bản sao của tất cả các biến lớp
  • Thực hiện một cách khác để lưu trữ các biến đối tượng [e. g. trong một danh sách được giữ bên ngoài cá thể nhưng được lập chỉ mục bởi id của cá thể[]]
  • Tự động bọc hoặc bẫy tất cả hoặc một số phương pháp nhất định
    • để truy tìm
    • để kiểm tra tiền điều kiện và hậu điều kiện
    • cho các phương pháp được đồng bộ hóa
    • cho bộ nhớ đệm giá trị tự động
  • Khi một thuộc tính là một hàm không tham số, hãy gọi nó theo tham chiếu [để bắt chước nó là một biến thể hiện];
  • Thiết bị đo đạc. xem có bao nhiêu lần các thuộc tính khác nhau được sử dụng
  • Các ngữ nghĩa khác nhau cho __setattr__ và __getattr__ [e. g. vô hiệu hóa chúng khi chúng đang được sử dụng đệ quy]
  • Lạm dụng cú pháp lớp cho những thứ khác
  • Thử nghiệm với kiểm tra loại tự động
  • Ủy quyền [hoặc mua lại]
  • Các mẫu kế thừa động
  • Tự động lưu trữ các phương thức

Tín dụng

Xin chân thành cảm ơn David Ascher và Donald Beaudry vì những nhận xét của họ đối với bản thảo trước đó của bài viết này. Cũng xin cảm ơn Matt Conway và Tommy Burnette vì đã gieo mầm cho ý tưởng về siêu lớp trong tâm trí tôi, gần ba năm trước, mặc dù lúc đó câu trả lời của tôi là ``bạn có thể làm điều đó với các móc __getattr__. ''. -]

__ gọi __ Python là gì?

Phương thức __call__ cho phép các lập trình viên Python viết các lớp trong đó các thể hiện hoạt động giống như các hàm và có thể được gọi giống như một hàm . Khi thể hiện được gọi là một hàm; . ] là viết tắt của x. __gọi__[arg1, arg2,. ].

__ phụ __ trong Python là gì?

Tôi hiểu rằng __sub__ là trình nạp chồng toán tử trong python và chặn cuộc gọi p1-p2.

_ trong lớp Python là gì?

Python tự động lưu giá trị của biểu thức cuối cùng trong trình thông dịch vào một biến cụ thể được gọi là "_. " Bạn cũng có thể gán các giá trị này cho một biến khác nếu muốn.

_init _ trong Python là gì?

Phương thức __init__ là tương đương Python với hàm tạo C++ theo cách tiếp cận hướng đối tượng . Hàm __init__ được gọi mỗi khi một đối tượng được tạo từ một lớp. Phương thức __init__ cho phép lớp khởi tạo các thuộc tính của đối tượng và không phục vụ mục đích nào khác. Nó chỉ được sử dụng trong các lớp học.

Chủ Đề