Python gọi phương thức lớp từ từ điển

Bài báo này là một bản nháp chưa hoàn chỉnh. Tôi đang thu hút phản hồi. Nếu bạn tìm thấy bất kỳ vấn đề nào, vui lòng viết thư cho tôi theo địa chỉ guido@python. tổ chức

Mục lục

  • Nhật ký thay đổi
  • Giới thiệu
  • Phân loại các loại tích hợp
  • Các loại tích hợp dưới dạng chức năng của nhà máy
  • Các trường hợp nội quan của các loại tích hợp
  • Phương thức tĩnh và phương thức lớp
  • Đặc tính. các thuộc tính được xác định bởi các phương thức get/set
  • Thứ tự giải phương pháp
  • Phương pháp hợp tác và "siêu"
  • Ghi đè phương thức __new__
  • siêu dữ liệu
  • tương thích ngược
  • Người giới thiệu

Nhật ký thay đổi

Các thay đổi kể từ Python 2 ban đầu. 2 phiên bản của hướng dẫn này

  • Đừng làm mọi người sợ hãi bằng cách đề xuất classmethod có thể biến mất. [4-Apr-2002]

Giới thiệu

Trăn 2. 2 giới thiệu giai đoạn đầu tiên của "thống nhất loại/lớp". Đây là một loạt các thay đổi đối với Python nhằm loại bỏ hầu hết sự khác biệt giữa các loại dựng sẵn và các lớp do người dùng định nghĩa. Có lẽ điều rõ ràng nhất là hạn chế sử dụng các kiểu dựng sẵn [chẳng hạn như kiểu danh sách và từ điển] làm lớp cơ sở trong câu lệnh lớp

Đây là một trong những thay đổi lớn nhất đối với Python từ trước đến nay, nhưng nó có thể được thực hiện với rất ít lỗi không tương thích ngược. Các thay đổi được mô tả chi tiết từng phút trong một loạt PEP [Đề xuất cải tiến Python]. PEP không được thiết kế để trở thành hướng dẫn và PEP mô tả sự thống nhất loại/lớp đôi khi khó đọc. Họ cũng chưa hoàn thành. Đó là nơi bài báo này xuất hiện. nó giới thiệu các yếu tố chính của sự hợp nhất kiểu/lớp cho lập trình viên Python trung bình

Một chút thuật ngữ. "Python cổ điển" dùng để chỉ Python 2. 1 [và các bản phát hành bản vá của nó như 2. 1. 1] hoặc các phiên bản cũ hơn, trong khi "các lớp cổ điển" đề cập đến các lớp được định nghĩa bằng câu lệnh lớp không có đối tượng dựng sẵn trong số các cơ sở của nó. hoặc bởi vì nó không có cơ sở hoặc bởi vì tất cả các cơ sở của nó đều là các lớp cổ điển - áp dụng định nghĩa một cách đệ quy

Các lớp cổ điển vẫn là một danh mục đặc biệt trong Python 2. 2. Cuối cùng, chúng sẽ hoàn toàn hợp nhất với các loại, nhưng do có thêm sự không tương thích ngược, điều này sẽ được thực hiện sau 2. 2 được phát hành [có thể không trước Python 3. 0]. Tôi sẽ cố gắng nói "loại" khi tôi muốn nói đến loại tích hợp sẵn và "lớp" khi tôi đang đề cập đến một lớp cổ điển hoặc thứ gì đó có thể là một trong hai;

Phân loại các loại tích hợp

Hãy bắt đầu với chút ngon ngọt nhất. bạn có thể phân loại các loại tích hợp sẵn như từ điển và danh sách. Tất cả những gì bạn cần là tên cho một lớp cơ sở là loại tích hợp sẵn và bạn đang kinh doanh

Có một tên tích hợp mới, "dict", cho loại từ điển. [Ở phiên bản 2. 2b1 trở về trước, đây được gọi là "từ điển"; . ]

Đây thực sự chỉ là đường, vì đã có hai cách khác để đặt tên cho loại này. loại [{}] và [sau khi nhập mô-đun loại] loại. DictType [và một phần ba, các loại. loại từ điển]. Nhưng giờ đây, các loại đóng vai trò trung tâm hơn, nên có vẻ thích hợp để đặt tên tích hợp cho các loại mà bạn có thể gặp phải

Đây là một ví dụ về một lớp con dict đơn giản, cung cấp một "giá trị mặc định" được trả về khi một khóa bị thiếu được yêu cầu

    class defaultdict[dict]:

        def __init__[self, default=None]:
            dict.__init__[self]
            self.default = default

        def __getitem__[self, key]:
            try:
                return dict.__getitem__[self, key]
            except KeyError:
                return self.default

Ví dụ này cho thấy một vài điều. Phương thức __init__[] mở rộng dict. phương thức __init__[]. Giống như các phương thức __init__[] sẽ không được thực hiện, nó có một danh sách đối số khác với phương thức __init__[] của lớp cơ sở. Tương tự, phương thức __getitem__[] mở rộng phương thức __getitem__[] của lớp cơ sở

Phương thức __getitem__[] cũng có thể được viết như sau, sử dụng bài kiểm tra "key in dict" mới được giới thiệu trong Python 2. 2

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default

Tôi tin rằng phiên bản này kém hiệu quả hơn vì nó thực hiện tra cứu khóa hai lần. Ngoại lệ sẽ xảy ra khi chúng tôi cho rằng khóa được yêu cầu hầu như không bao giờ có trong từ điển. sau đó thiết lập câu lệnh thử/ngoại trừ đắt hơn so với thử nghiệm "tự khóa" không thành công

Để hoàn thiện, phương thức get[] có lẽ cũng nên được mở rộng, để làm cho nó sử dụng cùng một giá trị mặc định như __getitem__[]

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]

[Mặc dù chức năng này được khai báo với một danh sách đối số có độ dài thay đổi, nhưng nó thực sự chỉ nên được gọi với một hoặc hai đối số; nếu nhiều hơn được thông qua, lệnh gọi phương thức của lớp cơ sở sẽ đưa ra một ngoại lệ TypeError. ]

Chúng tôi không bị hạn chế mở rộng các phương thức được xác định trên lớp cơ sở. Đây là một phương pháp hữu ích tương tự như update[], nhưng giữ nguyên các giá trị hiện có thay vì ghi đè lên chúng bằng các giá trị mới nếu một khóa tồn tại trong cả hai từ điển

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]

Điều này sử dụng thử nghiệm "key not in dict" mới cũng như "for key in dict" mới. " để lặp lại hiệu quả [không tạo bản sao danh sách khóa] trên tất cả các khóa trong từ điển. Nó không yêu cầu đối số khác phải là một defaultdict hoặc thậm chí là một từ điển. bất kỳ đối tượng ánh xạ nào hỗ trợ "for key in other" và other[key] sẽ làm

Đây là loại mới tại nơi làm việc

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>

Chúng ta cũng có thể sử dụng kiểu mới trong ngữ cảnh mà kiểu cổ điển chỉ cho phép từ điển "thực", chẳng hạn như từ điển local/globals cho câu lệnh exec hoặc hàm tích hợp sẵn eval[]

    >>> print a.keys[]
    [1, 2]
    >>> exec "x = 3; print x" in a
    3
    >>> print a.keys[]
    ['__builtins__', 1, 2, 'x']
    >>> print a['x']
    3
    >>> 

Tuy nhiên, phương thức __getitem__[] của chúng tôi không được trình thông dịch sử dụng để truy cập biến

    >>> exec "print foo" in a
    Traceback [most recent call last]:
      File "", line 1, in ?
      File "", line 1, in ?
    NameError: name 'foo' is not defined
    >>> 

Tại sao điều này không in 0. 0? . Tôi thừa nhận rằng đây có thể là một vấn đề [mặc dù nó chỉ là một vấn đề trong ngữ cảnh này, khi một lớp con dict được sử dụng làm từ điển địa phương/toàn cầu];

Bây giờ chúng ta sẽ thấy rằng các thể hiện defaultdict có các biến thể hiện động, giống như các lớp cổ điển

    >>> a.default = -1
    >>> print a["noway"]
    -1
    >>> a.default = -1000
    >>> print a["noway"]
    -1000
    >>> print a.__dict__.keys[]
    ['default']
    >>> a.x1 = 100
    >>> a.x2 = 200
    >>> print a.x1
    100
    >>> print a.__dict__.keys[]
    ['default', 'x2', 'x1']
    >>> print a.__dict__
    {'default': -1000, 'x2': 200, 'x1': 100}
    >>> 

Đây không phải lúc nào cũng là điều bạn muốn; . Có một cách để tránh điều này

________số 8

Khai báo __slots__ lấy một danh sách các biến thể hiện và dự trữ không gian trong thể hiện cho chính xác những biến này trong thể hiện. Khi __slots__ được sử dụng, các biến thể hiện khác không thể được gán cho

    >>> a = defaultdict2[default=0.0]
    >>> a[1]
    0.0
    >>> a.default = -1
    >>> a[1]
    -1
    >>> a.x1 = 1
    Traceback [most recent call last]:
      File "", line 1, in ?
    AttributeError: 'defaultdict2' object has no attribute 'x1'
    >>>

Một số mẩu tin đáng chú ý và cảnh báo về __slots__

  • Một biến vị trí không xác định sẽ tăng AttributeError như mong đợi. [Lưu ý rằng trong Python 2. 2b2 trở về trước, các biến vị trí có giá trị Không theo mặc định và việc "xóa" chúng sẽ khôi phục giá trị mặc định này. ]
  • Bạn không thể sử dụng thuộc tính lớp để xác định giá trị mặc định cho biến thể hiện được xác định bởi __slots__. Khai báo __slots__ tạo thuộc tính lớp chứa bộ mô tả cho mỗi vị trí và đặt thuộc tính lớp thành giá trị mặc định sẽ ghi đè bộ mô tả này
  • Không có kiểm tra nào để ngăn xung đột tên giữa các vị trí được xác định trong một lớp và các vị trí được xác định trong các lớp cơ sở của nó. Nếu một lớp định nghĩa một vị trí cũng được định nghĩa trong một lớp cơ sở, thì biến thể hiện được xác định bởi vị trí lớp cơ sở sẽ không thể truy cập được [ngoại trừ bằng cách truy xuất bộ mô tả của nó trực tiếp từ lớp cơ sở; điều này có thể được sử dụng để đổi tên nó]. Làm điều này làm cho ý nghĩa của chương trình của bạn không được xác định;
  • Thực thể của lớp sử dụng __slots__ không có __dict__ [trừ khi lớp cơ sở định nghĩa __dict__];
  • Bạn có thể định nghĩa một đối tượng không có biến thể hiện và không có __dict__ bằng cách sử dụng __slots__ = []
  • Bạn không thể sử dụng các vị trí có loại dựng sẵn "độ dài thay đổi" làm lớp cơ sở. Các loại tích hợp có độ dài thay đổi là long, str và tuple
  • Một lớp sử dụng __slots__ không hỗ trợ các tham chiếu yếu đến các thể hiện của nó, trừ khi một trong các chuỗi trong danh sách __slots__ bằng "__weakref__". [Hmm, tính năng này có thể được mở rộng thành "__dict__". ]
  • Biến __slots__ không nhất thiết phải là một danh sách; . Đặc biệt có thể dùng từ điển. Bạn cũng có thể sử dụng một chuỗi đơn để khai báo một vị trí. Tuy nhiên, trong tương lai, một ý nghĩa bổ sung có thể được gán cho việc sử dụng từ điển, ví dụ: các giá trị từ điển có thể được sử dụng để hạn chế loại biến đối tượng hoặc cung cấp chuỗi tài liệu;

Lưu ý rằng mặc dù nói chung, nạp chồng toán tử hoạt động giống như đối với các lớp cổ điển, vẫn có một số khác biệt. [Vấn đề lớn nhất là thiếu hỗ trợ cho __coerce__; các lớp kiểu mới phải luôn sử dụng API số kiểu mới, API này chuyển toán hạng khác mà không bị ép buộc tới các phương thức __add__ và __radd__, v.v. ]

Có một cách mới để ghi đè quyền truy cập thuộc tính. Móc __getattr__, nếu được định nghĩa, sẽ hoạt động giống như đối với các lớp cổ điển. nó chỉ được gọi nếu cách tìm kiếm thuộc tính thông thường không tìm thấy nó. Nhưng bây giờ bạn cũng có thể ghi đè __getattribute__, một thao tác mới được gọi cho tất cả các tham chiếu thuộc tính

Khi ghi đè __getattribute__, hãy nhớ rằng nó rất dễ gây ra đệ quy vô hạn. bất cứ khi nào __getattribute__ tham chiếu đến một thuộc tính của self [kể cả self. __dict__. ], nó được gọi một cách đệ quy. [Điều này tương tự với __setattr__, được gọi cho tất cả các phép gán thuộc tính; __getattr__ cũng có thể gặp phải điều này khi nó được viết bất cẩn và tham chiếu đến một thuộc tính không tồn tại của self. ]

Cách chính xác để lấy bất kỳ thuộc tính nào từ self bên trong __getattribute__ là gọi phương thức __getattribute__ của lớp cơ sở, giống như cách mà bất kỳ phương thức nào ghi đè phương thức của lớp cơ sở đều có thể gọi phương thức của lớp cơ sở. Cơ sở. __getattribute__[bản thân, tên]. [Xem thêm phần thảo luận về super[] bên dưới nếu bạn muốn đúng trong thế giới đa thừa kế. ]

Đây là một ví dụ về ghi đè __getattribute__ [thực sự mở rộng nó, vì phương thức ghi đè gọi phương thức lớp cơ sở]

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
0

Ghi chú về __setattr__. đôi khi các thuộc tính không được lưu trữ trong self. __dict__ [ví dụ: khi sử dụng __slots__ hoặc thuộc tính hoặc khi sử dụng lớp cơ sở tích hợp sẵn]. Mô hình tương tự như đối với __getattribute__ được áp dụng, trong đó bạn gọi lớp cơ sở __setattr__ để thực hiện công việc thực tế. Đây là một ví dụ

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
1

Các lập trình viên C++ có thể thấy hữu ích khi nhận ra rằng hình thức phân loại con này trong Python được triển khai rất giống với phân lớp con kế thừa đơn trong C++, với __class__ trong vai trò của vtable

Còn nhiều điều nữa có thể được giải thích [như khai báo __metaclass__ và phương thức __new__], nhưng hầu hết những điều đó đều khá bí truyền. Xem bên dưới nếu bạn quan tâm

Tôi sẽ kết thúc với một danh sách các lưu ý

  • Bạn có thể sử dụng nhiều kế thừa, nhưng bạn không thể nhân kế thừa từ các loại tích hợp sẵn khác nhau [ví dụ: bạn không thể tạo một loại kế thừa từ cả hai loại chính tả và danh sách tích hợp sẵn]. Đây là một hạn chế vĩnh viễn; . Tuy nhiên, bạn có thể tạo các lớp hỗn hợp bằng cách kế thừa từ "đối tượng". Đây là một tích hợp mới, đặt tên cho loại cơ sở đặc biệt của tất cả các loại tích hợp sẵn trong hệ thống mới
  • Khi sử dụng đa kế thừa, bạn có thể kết hợp các lớp cổ điển và các kiểu dựng sẵn [hoặc các kiểu dẫn xuất từ ​​các kiểu dựng sẵn] trong danh sách các lớp cơ sở. [Đây là điểm mới trong Python 2. 2b2; . ]
  • Xem thêm các lỗi chung trong 2. 2 danh sách

Các loại tích hợp dưới dạng chức năng của nhà máy

Phần trước đã chỉ ra rằng có thể tạo một thể hiện của kiểu phụ defaultdict tích hợp bằng cách gọi hàm defaultdict[]. Điều này được mong đợi, bởi vì điều này cũng hoạt động cho các lớp học cổ điển. Nhưng đây là một tính năng mới. bản thân các loại cơ sở tích hợp cũng có thể được khởi tạo bằng cách gọi trực tiếp loại đó

Đối với một số loại tích hợp sẵn, đã có sẵn các hàm xuất xưởng được đặt tên theo loại trong Python cổ điển, ví dụ: str[] và int[]. Tôi đã thay đổi các nội dung tích hợp này để giờ đây chúng là tên của các loại tương ứng. Mặc dù điều này thay đổi loại của những tên này từ chức năng tích hợp thành loại tích hợp sẵn, tôi không cho rằng điều này sẽ tạo ra sự cố tương thích ngược. Tôi đã đảm bảo rằng các kiểu có thể được gọi với danh sách đối số chính xác giống như các hàm trước đây. [Nói chung, chúng cũng có thể được gọi mà không có đối số, tạo ra một đối tượng có giá trị mặc định phù hợp, chẳng hạn như số không hoặc trống; đây là cách mới. ]

Đây là những tích hợp bị ảnh hưởng

  • int[[số_hoặc_chuỗi[, số_cơ_bản]]]
  • dài[[số_hoặc_chuỗi]]
  • float[[số_hoặc_chuỗi]]
  • phức tạp[[number_or_string[, image_number]]]
  • str[[đối tượng]]
  • unicode[[chuỗi[, chuỗi mã hóa]]]
  • bộ dữ liệu [[có thể lặp lại]]
  • danh sách [[có thể lặp lại]]
  • loại [đối tượng] hoặc loại [tên_chuỗi, cơ sở_tuple, phương thức_dict]

Chữ ký của type[] yêu cầu giải thích. theo truyền thống, loại[x] trả về loại đối tượng x và cách sử dụng này vẫn được hỗ trợ. Tuy nhiên, loại [tên, cơ sở, phương pháp] là một cách sử dụng mới tạo ra một đối tượng loại hoàn toàn mới. [Điều này liên quan đến lập trình siêu dữ liệu, và tôi sẽ không đi sâu vào vấn đề này ở đây ngoại trừ lưu ý rằng chữ ký này giống với chữ ký được sử dụng bởi Don Beaudry hook nổi tiếng về siêu dữ liệu. ]

Ngoài ra còn có một số tích hợp mới theo cùng một mẫu. Những điều này đã được mô tả ở trên hoặc sẽ được mô tả bên dưới

  • dict[[mapping_or_iterable]] - trả về một từ điển mới;
  • sự vật[[. ]] - trả về một đối tượng đặc biệt mới;
  • classmethod[function] - xem bên dưới
  • staticmethod[chức năng] - xem bên dưới
  • super[class_or_type[, instance]] - xem bên dưới
  • thuộc tính [[fget[, fset[, fdel[, doc]]]]] - xem bên dưới

Mục đích của sự thay đổi này là gấp đôi. Đầu tiên, điều này giúp thuận tiện khi sử dụng bất kỳ loại nào trong số này làm lớp cơ sở trong câu lệnh lớp. Thứ hai, nó làm cho việc kiểm tra một loại cụ thể dễ dàng hơn một chút. thay vì viết type[x] là type[0], bây giờ bạn có thể viết isinstance[x, int]

Mà làm tôi nhớ. Đối số thứ hai của isinstance[] bây giờ có thể là một bộ lớp hoặc kiểu. Ví dụ: isinstance[x, [int, long]] trả về giá trị true khi x là một int hoặc long [hoặc một thể hiện của một lớp con thuộc một trong hai loại đó] và tương tự như vậy, kiểm tra isinstance[x, [str, unicode]] . Chúng tôi đã không làm điều này với isclass[]

Các trường hợp nội quan của các loại tích hợp

Đối với các thể hiện của các kiểu dựng sẵn [và đối với các lớp kiểu mới nói chung], x. __class__ bây giờ giống như type[x]

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
2

Trong Python cổ điển, tên phương thức của danh sách có sẵn dưới dạng thuộc tính __methods__ của đối tượng danh sách, có tác dụng tương tự như sử dụng hàm dir[] tích hợp

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
3

Theo đề xuất mới, thuộc tính __methods__ không còn tồn tại

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
4

Thay vào đó, bạn có thể lấy thông tin tương tự từ hàm dir[], hàm này cung cấp thêm thông tin

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
5

Dir[] mới cung cấp nhiều thông tin hơn cái cũ. Ngoài tên của các biến đối tượng và các phương thức thông thường, nó cũng hiển thị các phương thức thường được gọi thông qua các ký hiệu đặc biệt, như __iadd__ [+=], __len__ [len], __ne__ [. =]

Tìm hiểu thêm về hàm dir[] mới

  • dir[] trên một thể hiện [kiểu cổ điển hoặc kiểu mới] hiển thị các biến thể hiện cũng như các phương thức và thuộc tính lớp được định nghĩa bởi lớp của thể hiện và tất cả các lớp cơ sở của nó
  • dir[] trên một lớp [kiểu cổ điển hoặc kiểu mới] hiển thị nội dung của __dict__ của lớp và tất cả các lớp cơ sở của nó. Nó không hiển thị các thuộc tính lớp được xác định bởi siêu dữ liệu
  • dir[] trên một mô-đun hiển thị nội dung của __dict__ của mô-đun. [Điều này không thay đổi. ]
  • dir[] không có đối số hiển thị các biến cục bộ của người gọi. [Một lần nữa, không thay đổi. ]
  • Có một API C mới triển khai hàm dir[]. PyObject_Dir[]
  • Có nhiều chi tiết hơn;

Bạn có thể sử dụng một phương thức của loại tích hợp làm "phương thức không liên kết"

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
6

Điều này giống như sử dụng một phương thức không liên kết của một lớp do người dùng định nghĩa - và tương tự, nó hầu như hữu ích từ bên trong một phương thức của lớp con, để gọi phương thức của lớp cơ sở tương ứng

Không giống như các lớp do người dùng định nghĩa, bạn không thể thay đổi các kiểu dựng sẵn. cố gắng gán một thuộc tính của loại tích hợp sẽ làm tăng TypeError và __dict__ của chúng là đối tượng proxy chỉ đọc. Hạn chế về gán thuộc tính được dỡ bỏ đối với các lớp kiểu mới do người dùng định nghĩa, bao gồm các lớp con của các loại tích hợp sẵn; . phiên ví dụ

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
7

Đối với những người tò mò. có hai lý do khiến việc thay đổi các lớp dựng sẵn không được phép. Đầu tiên, sẽ quá dễ dàng để phá vỡ một bất biến của loại tích hợp được dựa vào ở nơi khác, bởi thư viện tiêu chuẩn hoặc bởi mã thời gian chạy. Thứ hai, khi Python được nhúng trong một ứng dụng khác tạo nhiều trình thông dịch Python, các đối tượng lớp tích hợp [là cấu trúc dữ liệu được cấp phát tĩnh] được chia sẻ giữa tất cả các trình thông dịch;

Phương thức tĩnh và phương thức lớp

API mô tả mới cho phép thêm các phương thức tĩnh và phương thức lớp. Các phương thức tĩnh rất dễ mô tả. chúng hoạt động khá giống các phương thức tĩnh trong C++ hoặc Java. Đây là một ví dụ

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
8

Cả hai gọi C. foo[1, 2] và cuộc gọi c. foo[1, 2] gọi foo[] với hai đối số và in "staticmethod 1 2". Không có "bản thân" nào được khai báo trong định nghĩa của foo[] và không yêu cầu phiên bản nào trong cuộc gọi. Nếu một thể hiện được sử dụng, nó chỉ được sử dụng để tìm lớp xác định phương thức tĩnh. Điều này hoạt động cho các lớp học cổ điển và mới

Dòng "foo = staticmethod[foo]" trong câu lệnh lớp là yếu tố quan trọng. điều này làm cho foo[] trở thành một phương thức tĩnh. Phương thức tĩnh tích hợp [] bao bọc đối số chức năng của nó trong một loại bộ mô tả đặc biệt có phương thức __get__ [] trả về chức năng ban đầu không thay đổi

Tìm hiểu thêm về các phương thức __get__. trong Python 2. 2, sự kỳ diệu của các phương thức liên kết với các thể hiện [ngay cả đối với các lớp cổ điển. ] được thực hiện thông qua phương thức __get__ của đối tượng được tìm thấy trong lớp. Phương thức __get__ cho các đối tượng hàm thông thường trả về một đối tượng phương thức bị ràng buộc; . Nếu một thuộc tính lớp không có phương thức __get__, thì nó không bao giờ bị ràng buộc với một thể hiện, hay nói cách khác, có một thao tác __get__ mặc định trả về đối tượng không thay đổi;

Các phương thức lớp sử dụng một mẫu tương tự để khai báo các phương thức nhận đối số đầu tiên ngầm định là lớp mà chúng được gọi. Điều này không tương đương với C++ hoặc Java và không hoàn toàn giống với các phương thức lớp trong Smalltalk, nhưng có thể phục vụ một mục đích tương tự. [Python cũng có siêu dữ liệu thực và có lẽ các phương thức được định nghĩa trong siêu dữ liệu có quyền hơn đối với tên "phương thức lớp"; nhưng tôi hy vọng rằng hầu hết các lập trình viên sẽ không sử dụng siêu dữ liệu. ] Đây là một ví dụ

        def __getitem__[self, key]:
            if key in self:
                return dict.__getitem__[self, key]
            else:
                return self.default
9

Cả hai gọi C. foo[1] và cuộc gọi c. foo[1] kết thúc cuộc gọi foo[] với hai đối số và in "classmethod __main__. C1". Đối số đầu tiên của foo[] được ngụ ý và nó là lớp, ngay cả khi phương thức được gọi thông qua một thể hiện. Bây giờ hãy tiếp tục ví dụ

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
0

Bản in này "classmethod __main__. D 1" cả hai lần; nói cách khác, lớp được truyền làm đối số đầu tiên của foo[] là lớp liên quan đến lệnh gọi, không phải lớp liên quan đến định nghĩa của foo[]

Nhưng chú ý điều này

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
1

Trong ví dụ này, cuộc gọi đến C. foo[] từ E. foo[] sẽ xem lớp C là đối số đầu tiên của nó, không phải lớp E. Điều này được mong đợi, vì cuộc gọi chỉ định lớp C. Nhưng nó nhấn mạnh sự khác biệt giữa các phương thức lớp này và các phương thức được xác định trong siêu dữ liệu, trong đó một cuộc gọi lên siêu dữ liệu sẽ chuyển lớp đích dưới dạng đối số đầu tiên rõ ràng. [Nếu bạn không hiểu điều này, đừng lo lắng, bạn không đơn độc. . -]

Đặc tính. các thuộc tính được quản lý bởi các phương thức get/set

Thuộc tính là một cách gọn gàng để triển khai các thuộc tính có cách sử dụng giống như truy cập thuộc tính, nhưng việc triển khai sử dụng các lệnh gọi phương thức. Chúng đôi khi được gọi là "thuộc tính được quản lý". Trong các phiên bản Python trước, bạn chỉ có thể làm điều này bằng cách ghi đè __getattr__ và __setattr__; . Các thuộc tính cho phép bạn thực hiện việc này một cách dễ dàng mà không cần phải ghi đè __getattr__ hoặc __setattr__

Tôi sẽ đưa ra một ví dụ đầu tiên. Hãy định nghĩa một lớp có thuộc tính x được xác định bởi một cặp phương thức, getx[] và setx[]

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
2

Đây là một cuộc biểu tình nhỏ

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
3

Chữ ký đầy đủ là property[fget=None, fset=None, fdel=None, doc=None]. Các đối số fget, fset và fdel là các phương thức được gọi khi thuộc tính được lấy, đặt hoặc xóa. Nếu bất kỳ cái nào trong số ba cái này không được chỉ định hoặc Không có, thao tác tương ứng sẽ đưa ra một ngoại lệ AttributeError. Đối số thứ tư là chuỗi tài liệu cho thuộc tính;

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
4

Những điều cần lưu ý về thuộc tính [] [tất cả tài liệu nâng cao ngoại trừ tài liệu đầu tiên]

  • Các thuộc tính không hoạt động đối với các lớp cổ điển, nhưng bạn không gặp lỗi rõ ràng khi thử điều này. Phương thức get của bạn sẽ được gọi, vì vậy nó có vẻ hoạt động, nhưng khi gán thuộc tính, một thể hiện của lớp cổ điển sẽ chỉ đặt giá trị trong __dict__ của nó mà không gọi phương thức set của thuộc tính và sau đó, phương thức get của thuộc tính sẽ không được gọi . [Bạn có thể ghi đè __setattr__ để sửa lỗi này, nhưng sẽ rất tốn kém. ]

    Đối với thuộc tính [] có liên quan, các đối số fget, fset và fdel của nó là các hàm, không phải phương thức - chúng được chuyển một tham chiếu rõ ràng tới đối tượng làm đối số đầu tiên của chúng. Vì thuộc tính[] thường được sử dụng trong một câu lệnh lớp nên điều này đúng [các phương thức thực sự là các đối tượng hàm tại thời điểm thuộc tính[] được gọi] nhưng bạn vẫn có thể coi chúng là các phương thức - miễn là bạn không sử dụng

  • Phương thức get sẽ không được gọi khi thuộc tính được truy cập dưới dạng thuộc tính lớp [C. x] thay vì dưới dạng thuộc tính thể hiện [C[]. x]. Nếu bạn muốn ghi đè thao tác __get__ cho các thuộc tính khi được sử dụng làm thuộc tính lớp, bạn có thể phân lớp thuộc tính - bản thân nó là một kiểu kiểu mới - để mở rộng phương thức __get__ của nó hoặc bạn có thể xác định một kiểu mô tả từ đầu bằng cách tạo một kiểu mô tả mới.

Thứ tự giải phương pháp

Với nhiều kế thừa xuất hiện câu hỏi về thứ tự giải quyết phương thức. thứ tự mà một lớp và các cơ sở của nó được tìm kiếm để tìm kiếm một phương thức của một tên đã cho

Trong Python cổ điển, quy tắc được đưa ra bởi hàm đệ quy sau, còn được gọi là quy tắc theo chiều sâu từ trái sang phải

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
5

Trong Python 2. 2, tôi đã quyết định áp dụng quy tắc tra cứu khác cho các lớp kiểu mới. [Quy tắc dành cho các lớp cổ điển không thay đổi để xem xét tính tương thích ngược; cuối cùng tất cả các lớp sẽ là lớp kiểu mới và sau đó sự khác biệt sẽ biến mất. ] Trước tiên, tôi sẽ cố gắng giải thích điều gì sai với quy tắc cổ điển

Vấn đề với quy tắc cổ điển trở nên rõ ràng khi chúng ta xem xét "sơ đồ kim cương"

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
6

Mũi tên chỉ từ một kiểu phụ đến [các] kiểu cơ sở của nó. Sơ đồ cụ thể này có nghĩa là B và C bắt nguồn từ A, và D bắt nguồn từ B và C [và do đó, một cách gián tiếp, từ A]

Giả sử rằng C ghi đè phương thức save[], được định nghĩa trong cơ sở A. [C. save[] có thể gọi A. save[] và sau đó lưu một số trạng thái của chính nó. ] B và D không ghi đè lưu[]. Khi chúng ta gọi save[] trên phiên bản D, phương thức nào được gọi? . save[] được gọi, bỏ qua C. tiết kiệm[]

Điều này không tốt. Nó có thể phá vỡ C [trạng thái của nó không được lưu], đánh bại toàn bộ mục đích kế thừa từ C ngay từ đầu

Tại sao đây không phải là vấn đề trong Python cổ điển? . Hầu hết các hệ thống phân cấp lớp sử dụng kế thừa đơn và đa kế thừa thường được giới hạn ở các lớp kết hợp. Trên thực tế, vấn đề hiển thị ở đây có lẽ là lý do tại sao đa kế thừa không phổ biến trong Python cổ điển

Tại sao đây sẽ là một vấn đề trong hệ thống mới?

[Qua một bên. phương thức __getattr__[] không thực sự là cách triển khai cho thao tác thuộc tính get; . Điều này thường được coi là một thiếu sót - một số thiết kế lớp có nhu cầu chính đáng đối với phương thức thuộc tính get được gọi cho tất cả các tham chiếu thuộc tính và vấn đề này hiện đã được giải quyết bằng cách cung cấp __getattribute__[]. Nhưng sau đó, phương pháp này phải có khả năng gọi triển khai mặc định bằng cách nào đó. Cách tự nhiên nhất là làm cho triển khai mặc định có sẵn dưới dạng đối tượng. __getattribute__[bản thân, tên]. ]

Do đó, một hệ thống phân cấp lớp cổ điển như thế này

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
7

sẽ thay đổi thành sơ đồ kim cương theo hệ thống mới

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
8

và trong khi ở sơ đồ gốc C. __setattr__[] được gọi, trong hệ thống mới với quy tắc tra cứu cổ điển, đối tượng. __setattr__[] sẽ được gọi

May mắn thay, có một quy tắc tra cứu tốt hơn. Hơi khó giải thích, nhưng nó làm đúng trong sơ đồ hình thoi, và nó cũng giống như quy tắc tra cứu cổ điển khi không có hình thoi nào trong đồ thị thừa kế [khi nó là cây]

Quy tắc tra cứu mới xây dựng một danh sách tất cả các lớp trong sơ đồ thừa kế theo thứ tự mà chúng sẽ được tìm kiếm. Việc xây dựng này được thực hiện khi lớp được xác định, để tiết kiệm thời gian. Để giải thích quy tắc tra cứu mới, trước tiên chúng ta hãy xem xét một danh sách như vậy sẽ trông như thế nào đối với quy tắc tra cứu cổ điển. Lưu ý rằng khi có kim cương, tra cứu cổ điển sẽ truy cập một số lớp nhiều lần. Ví dụ, trong sơ đồ hình thoi ABCD ở trên, quy tắc tra cứu cổ điển truy cập các lớp theo thứ tự này

        def get[self, key, *args]:
            if not args:
                args = [self.default,]
            return dict.get[self, key, *args]
9

Lưu ý cách A xuất hiện hai lần trong danh sách. Lần xuất hiện thứ hai là dư thừa, vì bất kỳ thứ gì có thể tìm thấy ở đó đều đã được tìm thấy khi tìm kiếm lần xuất hiện đầu tiên. Tuy nhiên, nó vẫn được truy cập [việc triển khai đệ quy quy tắc cổ điển không nhớ lớp nào nó đã truy cập]

Theo quy định mới, danh sách sẽ được

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
0

Tìm kiếm các phương pháp theo thứ tự này sẽ thực hiện đúng cho sơ đồ kim cương. Do cách xây dựng danh sách, nó không bao giờ thay đổi thứ tự tìm kiếm trong các tình huống không liên quan đến viên kim cương

Quy tắc chính xác được sử dụng sẽ được giải thích trong phần tiếp theo. Tôi chỉ lưu ý ở đây tính chất quan trọng của tính đơn điệu trong quy tắc tra cứu. nếu lớp X đứng trước lớp Y theo thứ tự tra cứu đối với bất kỳ lớp cơ sở nào của lớp D, thì lớp X cũng sẽ đứng trước lớp Y theo thứ tự tra cứu đối với lớp D. Ví dụ: vì B đứng trước A trong danh sách tra cứu của B, nên nó cũng đứng trước A trong danh sách tra cứu của D; . Ngoại lệ. nếu trong số các cơ sở của lớp D, có một cơ sở mà X đứng trước Y và một cơ sở khác mà Y đứng trước X, thì thuật toán phải phá vỡ sự ràng buộc. Trong trường hợp này, tất cả các cược đều bị tắt;

[Một quy tắc được mô tả trước đây tại địa điểm này đã được chứng minh là không có tính chất đơn điệu. Xem một chủ đề trên python-dev bắt đầu bởi Samuele Pedroni. ]

Đây không phải là ngược không tương thích? . Tuy nhiên, trong Python 2. 2, quy tắc tra cứu mới sẽ chỉ được áp dụng cho các loại bắt nguồn từ các loại tích hợp sẵn, đây là một tính năng mới. Các câu lệnh lớp không có lớp cơ sở tạo ra "các lớp cổ điển" và do đó, các câu lệnh lớp có các lớp cơ sở chính là các lớp cổ điển. Đối với các lớp cổ điển, quy tắc tra cứu cổ điển sẽ được sử dụng. Chúng tôi cũng có thể cung cấp một công cụ phân tích hệ thống phân cấp lớp để tìm kiếm các phương thức sẽ bị ảnh hưởng bởi sự thay đổi về thứ tự giải quyết phương thức

Bất đồng về đơn đặt hàng và các bất thường khác

[Phần này chỉ dành cho người đọc nâng cao. ]

Bất kỳ thuật toán nào để quyết định thứ tự giải quyết phương pháp có thể phải đối mặt với các yêu cầu mâu thuẫn. Ví dụ, điều này xuất hiện khi hai lớp cơ sở nhất định xuất hiện theo thứ tự khác nhau trong danh sách kế thừa của hai lớp dẫn xuất khác nhau và các lớp dẫn xuất đó đều được kế thừa bởi một lớp khác. Đây là một ví dụ

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
1

Nếu bạn thử điều này, [sử dụng Z. __mro__, xem bên dưới], bạn nhận được [Z, X, Y, A, B, object], không duy trì yêu cầu đơn điệu được đề cập ở trên. MRO cho Y là [Y, B, A, đối tượng] và đây không phải là phần tiếp theo của danh sách trên. Thực tế không có nghiệm nào thỏa mãn yêu cầu về tính đơn điệu cho cả X và Y ở đây. Đây được gọi là bất đồng về đơn đặt hàng. Trong phiên bản tương lai, chúng tôi có thể quyết định loại bỏ những bất đồng về đơn đặt hàng như vậy trong một số trường hợp nhất định hoặc đưa ra cảnh báo cho họ

Cuốn sách "Đưa siêu lớp vào hoạt động", đã truyền cảm hứng cho tôi thay đổi MRO, xác định thuật toán MRO hiện đang được triển khai, nhưng mô tả thuật toán của nó khá khó nắm bắt - ban đầu tôi đã ghi lại một thuật toán khác, ngây thơ và không' . Gần đây hơn, Samuele Pedroni đã tìm thấy một phản ví dụ cho thấy thuật toán ngây thơ không duy trì được tính đơn điệu, vì vậy tôi thậm chí sẽ không mô tả nó nữa. Samuele đã thuyết phục tôi sử dụng thuật toán MRO mới hơn có tên là C3, được mô tả trong bài báo "A Monotonic Superclass Linearization for Dylan". Thuật toán này sẽ được sử dụng trong Python 2. 3 [khi tôi bắt đầu thực hiện nó]. C3 đơn điệu giống như thuật toán của cuốn sách, nhưng ngoài ra còn duy trì thứ tự của các lớp cơ sở ngay lập tức, điều mà thuật toán của cuốn sách không phải lúc nào cũng làm được

Cuốn sách cấm các lớp có sự bất đồng về thứ tự như vậy, nếu sự bất đồng về thứ tự là "nghiêm trọng". Bất đồng thứ tự giữa hai lớp là nghiêm trọng khi hai lớp định nghĩa ít nhất một phương thức trùng tên. Trong ví dụ trên, sự bất đồng về thứ tự là nghiêm trọng. Trong Python 2. 2, Tôi đã chọn không kiểm tra các bất đồng nghiêm trọng về đơn đặt hàng; . Nhưng kể từ phản ví dụ của Samuele, chúng tôi biết rằng những bất đồng về trật tự ngoài vòng pháp luật là không đủ để tránh các kết quả khác nhau giữa Python 2. 2 [từ cuốn sách] và Python 2. 3 thuật toán [C3, từ bài báo của Dylan]

Phương pháp hợp tác và "siêu"

Một trong những tính năng thú vị nhất, nhưng có lẽ cũng là một trong những tính năng khác thường nhất của các lớp mới là khả năng viết các lớp "hợp tác". Các lớp hợp tác được viết với nhiều kế thừa, sử dụng một mẫu mà tôi gọi là "siêu cuộc gọi hợp tác". Điều này được biết đến trong một số ngôn ngữ đa kế thừa khác là "phương thức gọi tiếp theo" và mạnh hơn siêu gọi được tìm thấy trong các ngôn ngữ kế thừa đơn như Java hoặc Smalltalk. C++ không có hình thức super call, thay vào đó dựa vào một cơ chế rõ ràng tương tự như cơ chế được sử dụng trong Python cổ điển. [Thuật ngữ "phương pháp hợp tác" xuất phát từ "Putting Metaclasses to Work". ]

Để làm mới lại, trước tiên chúng ta hãy xem xét cuộc gọi hưu bổng truyền thống, không hợp tác. Khi một lớp C xuất phát từ một lớp cơ sở B, C thường muốn ghi đè lên một phương thức m được định nghĩa trong B. Một "siêu lời gọi" xảy ra khi định nghĩa m của C gọi định nghĩa m của B để thực hiện một số công việc của nó. Trong Java, phần thân của m trong C có thể viết super[a, b, c] để gọi định nghĩa m của B với danh sách đối số [a, b, c]. Trong Python, C. m viết B. m[self, a, b, c] để đạt được hiệu ứng tương tự. Ví dụ

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
2Chúng tôi nói rằng phương pháp m của C "mở rộng" phương pháp m của B. Mẫu ở đây hoạt động tốt miễn là chúng ta đang sử dụng một kế thừa, nhưng nó bị hỏng với nhiều kế thừa. Hãy xem xét bốn lớp có sơ đồ thừa kế tạo thành một "viên kim cương" [sơ đồ tương tự đã được hiển thị bằng đồ họa trong phần trước].
        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
3

Giả sử A định nghĩa một phương thức m, được mở rộng bởi cả B và C. Bây giờ D phải làm gì? . Theo truyền thống, Python chỉ cần chọn cái đầu tiên được tìm thấy, trong trường hợp này là định nghĩa từ B. Điều này không lý tưởng, vì điều này hoàn toàn bỏ qua định nghĩa của C. Để xem có gì sai khi bỏ qua C's m, giả sử rằng các lớp này đại diện cho một số loại phân cấp vùng chứa liên tục và xem xét một phương thức thực hiện thao tác "lưu dữ liệu của bạn vào đĩa". Có lẽ, một phiên bản D có cả dữ liệu của B và dữ liệu của C, cũng như dữ liệu của A [một bản sao duy nhất của dữ liệu sau]. Bỏ qua định nghĩa của C về phương thức lưu có nghĩa là một thể hiện D, khi được yêu cầu tự lưu, chỉ lưu phần A và B của dữ liệu của nó, nhưng không lưu phần dữ liệu của nó được xác định bởi lớp C

C++ thông báo rằng D kế thừa hai định nghĩa xung đột của phương thức m và đưa ra thông báo lỗi. Tác giả của D sau đó được cho là ghi đè lên m để giải quyết xung đột. Nhưng định nghĩa của D về m phải làm gì? . Tùy thuộc vào các chi tiết của hoạt động, điều này tốt nhất là không hiệu quả [khi m là idempotent], tệ nhất là một lỗi. Python cổ điển có cùng một vấn đề, ngoại trừ nó thậm chí không coi đó là lỗi khi kế thừa hai định nghĩa xung đột của một phương thức. nó chỉ đơn giản là chọn cái đầu tiên

Giải pháp truyền thống cho vấn đề nan giải này là chia mỗi định nghĩa dẫn xuất của m thành hai phần. triển khai một phần _m, chỉ lưu dữ liệu duy nhất cho một lớp và triển khai m đầy đủ, gọi _m của chính nó và dữ liệu của [các] lớp cơ sở. Ví dụ

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
4

Có một số vấn đề với mô hình này. Trước hết, có sự gia tăng của các phương thức và lệnh gọi bổ sung. Nhưng có lẽ quan trọng hơn, nó tạo ra một sự phụ thuộc không mong muốn trong các lớp dẫn xuất vào các chi tiết của biểu đồ phụ thuộc của các lớp cơ sở của chúng. sự tồn tại của A không còn có thể được coi là chi tiết triển khai của B và C, vì lớp D cần biết về nó. Nếu, trong phiên bản tương lai của chương trình, chúng ta muốn loại bỏ sự phụ thuộc vào A khỏi B và C, thì điều này cũng sẽ ảnh hưởng đến các lớp dẫn xuất như D;

Mẫu "phương thức gọi tiếp theo" giải quyết vấn đề này một cách độc đáo, kết hợp với thứ tự giải quyết phương thức mới. Đây là cách

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
5

Lưu ý rằng đối số đầu tiên của super luôn là lớp mà nó xuất hiện; . Cũng lưu ý rằng self không được lặp lại trong danh sách đối số cho m

Bây giờ, để giải thích cách siêu hoạt động, hãy xem xét MRO cho từng lớp này. MRO được cung cấp bởi thuộc tính lớp __mro__

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
6

Biểu thức super[C, self]. m chỉ nên được sử dụng bên trong việc triển khai phương thức m trong lớp C. Hãy nhớ rằng mặc dù self là một thể hiện của C, nhưng self. __class__ có thể không phải là C. nó có thể là một lớp dẫn xuất từ ​​C [ví dụ, D]. Biểu thức super[C, self]. m, sau đó, tự tìm kiếm. __lớp__. __mro__ [MRO của lớp được sử dụng để tự tạo cá thể] cho sự xuất hiện của C, sau đó bắt đầu tìm kiếm triển khai phương thức m sau thời điểm đó

Ví dụ: nếu self là một phiên bản C, super[C, self]. m sẽ tìm thấy triển khai của A đối với m, cũng như super[B, self]. m nếu bản thân là một phiên bản B. Nhưng bây giờ hãy xem xét một trường hợp D. Trong D's m, super[D, self]. m[] sẽ tìm và gọi B. m[self], vì B là lớp cơ sở đầu tiên theo sau D trong D. __mro__ định nghĩa m. Bây giờ ở B. m, siêu[B, tự]. m[] được gọi. Vì bản thân là một thể hiện của D, nên MRO là [D, B, C, A, đối tượng] và lớp theo sau B là C. Đây là nơi tiếp tục tìm kiếm định nghĩa của m. Điều này tìm thấy C. m, được gọi và lần lượt gọi super[C, self]. m[]. Vẫn sử dụng cùng một MRO, chúng ta thấy rằng lớp theo sau C là A, và do đó A. m được gọi là. Đây là định nghĩa ban đầu của m, vì vậy không có cuộc gọi cấp cao nào được thực hiện vào thời điểm này

Lưu ý cách cùng một siêu biểu thức tìm thấy một lớp khác thực hiện một phương thức tùy thuộc vào lớp của bản thân. Đây là mấu chốt của cơ chế siêu hợp tác

Cuộc gọi siêu như hình trên hơi dễ bị lỗi. thật dễ dàng để sao chép và dán một cuộc gọi siêu cấp từ lớp này sang lớp khác trong khi quên thay đổi tên lớp thành tên của lớp mục tiêu và lỗi này sẽ không bị phát hiện nếu cả hai lớp là một phần của cùng một biểu đồ thừa kế. [Bạn thậm chí có thể gây ra đệ quy vô hạn bằng cách nhập nhầm tên của một lớp dẫn xuất của lớp chứa siêu gọi. ] Sẽ thật tuyệt nếu chúng ta không phải đặt tên lớp một cách rõ ràng, nhưng điều này sẽ cần nhiều trợ giúp hơn từ trình phân tích cú pháp của Python so với hiện tại chúng ta có thể nhận được. Tôi hy vọng sẽ khắc phục điều này trong bản phát hành Python trong tương lai bằng cách làm cho trình phân tích cú pháp nhận ra siêu

Trong lúc này, đây là một mẹo bạn có thể áp dụng. Chúng ta có thể tạo một biến lớp có tên __super có hành vi "ràng buộc". [Hành vi ràng buộc là một khái niệm mới trong Python 2. 2, nhưng nó chính thức hóa một khái niệm nổi tiếng từ Python cổ điển. việc chuyển đổi từ một phương thức không liên kết sang một phương thức bị ràng buộc khi nó được truy cập thông qua thao tác getattr trên một cá thể. Nó được thực hiện bằng phương pháp __get__ đã thảo luận ở trên. ] Đây là một ví dụ đơn giản

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
7

Một phần của thủ thuật nằm ở việc sử dụng tên __super, mà [thông qua phép biến đổi xáo trộn tên] chứa tên lớp. Điều này đảm bảo rằng bản thân. __super có nghĩa là một cái gì đó khác nhau trong mỗi lớp [miễn là tên lớp khác nhau; thật không may, trong Python có thể sử dụng lại tên của lớp cơ sở cho lớp dẫn xuất]. Một phần khác của thủ thuật là siêu tích hợp có thể được gọi bằng một đối số duy nhất, sau đó tạo một phiên bản không liên kết có thể bị ràng buộc bởi thao tác getattr phiên bản sau

Thật không may, ví dụ này vẫn còn khá xấu vì một số lý do. super yêu cầu lớp được truyền vào, nhưng lớp không khả dụng cho đến khi hoàn thành việc thực thi câu lệnh lớp, vì vậy thuộc tính lớp __super phải được gán bên ngoài lớp. Bên ngoài lớp, việc xáo trộn tên không hoạt động [xét cho cùng, nó được dự định là một tính năng riêng tư], vì vậy bài tập phải sử dụng tên chưa được xáo trộn. May mắn thay, có thể viết một siêu lớp tự động thêm thuộc tính __super vào các lớp của nó;

Lưu ý rằng super[class, subclass] cũng hoạt động;

Thí dụ. mã hóa siêu trong Python

Như một minh họa về sức mạnh của hệ thống mới, đây là một triển khai đầy đủ chức năng của lớp tích hợp super[] trong Python thuần túy. Điều này cũng có thể giúp làm rõ ngữ nghĩa của super[] bằng cách đánh vần tìm kiếm một cách chi tiết. Câu lệnh in ở cuối đoạn mã sau in "DCBA"

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
8

Ghi đè phương thức __new__

Khi phân lớp các loại tích hợp không thể thay đổi như số và chuỗi, và đôi khi trong các tình huống khác, phương thức tĩnh __new__ có ích. __new__ là bước đầu tiên trong quá trình xây dựng cá thể, được gọi trước __init__. Phương thức __new__ được gọi với lớp là đối số đầu tiên của nó; . So sánh điều này với __init__. __init__ được gọi với một thể hiện là đối số đầu tiên của nó và nó không trả về bất kỳ thứ gì; . Có những trường hợp một phiên bản mới được tạo mà không cần gọi __init__ [ví dụ: khi phiên bản được tải từ dưa chua]. Không có cách nào để tạo một thể hiện mới mà không gọi __new__ [mặc dù trong một số trường hợp, bạn có thể thoát khỏi việc gọi __new__ của lớp cơ sở]

Nhớ lại rằng bạn tạo các thể hiện của lớp bằng cách gọi lớp. Khi lớp là một lớp kiểu mới, điều sau xảy ra khi nó được gọi. Đầu tiên, phương thức __new__ của lớp được gọi, chuyển chính lớp đó làm đối số đầu tiên, tiếp theo là bất kỳ đối số [vị trí cũng như từ khóa] nào nhận được bởi lệnh gọi ban đầu. Điều này trả về một phiên bản mới. Sau đó, phương thức __init__ của cá thể đó được gọi để tiếp tục khởi tạo nó. [Nhân tiện, tất cả điều này được kiểm soát bởi phương thức __call__ của siêu dữ liệu. ]

Đây là một ví dụ về một lớp con ghi đè __new__ - đây là cách bạn thường sử dụng nó

        def merge[self, other]:
            for key in other:
                if key not in self:
                    self[key] = other[key]
9

Lớp này không hữu ích lắm [thậm chí nó không phải là cách đúng đắn để chuyển đổi đơn vị] nhưng nó chỉ ra cách mở rộng hàm tạo của một loại không thay đổi. Nếu thay vì __new__, chúng tôi đã cố gắng ghi đè __init__, nó sẽ không hoạt động

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
0

Phiên bản ghi đè __init__ không hoạt động vì __init__ của kiểu float là không hoạt động. nó trả về ngay lập tức, bỏ qua các đối số của nó

Tất cả điều này được thực hiện để các loại bất biến có thể duy trì tính bất biến của chúng trong khi cho phép phân lớp. Nếu giá trị của đối tượng float được khởi tạo bằng phương thức __init__ của nó, bạn có thể thay đổi giá trị của đối tượng float hiện có. Ví dụ, điều này sẽ làm việc

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
1

Tôi có thể đã khắc phục vấn đề này theo những cách khác, chẳng hạn bằng cách thêm cờ "đã được khởi tạo" hoặc chỉ cho phép gọi __init__ trên các cá thể của lớp con, nhưng những giải pháp đó không phù hợp. Thay vào đó, tôi đã thêm __new__, đây là một cơ chế tổng quát hoàn hảo có thể được sử dụng bởi các lớp tích hợp và do người dùng định nghĩa, cho các đối tượng bất biến và có thể thay đổi

Dưới đây là một số quy tắc cho __new__

  • __new__ là một phương thức tĩnh. Khi xác định nó, bạn không cần [nhưng có thể. ] sử dụng cụm từ "__new__ = staticmethod[__new__]", bởi vì điều này được ngụ ý bởi tên của nó [nó được đặt biệt bởi hàm tạo của lớp]
  • Đối số đầu tiên của __new__ phải là một lớp;
  • Phương thức __new__ ghi đè phương thức __new__ của lớp cơ sở có thể gọi phương thức __new__ của lớp cơ sở đó. Đối số đầu tiên cho lệnh gọi phương thức __new__ của lớp cơ sở phải là đối số lớp cho phương thức __new__ ghi đè, không phải lớp cơ sở;
  • Trừ khi bạn muốn chơi các trò chơi như được mô tả trong hai gạch đầu dòng tiếp theo, phương thức __new__ phải gọi phương thức __new__ của lớp cơ sở; . Lớp con __new__ có thể thực hiện hai việc để ảnh hưởng đến đối tượng kết quả. chuyển các đối số khác nhau cho lớp cơ sở __new__ và sửa đổi đối tượng kết quả sau khi nó được tạo [ví dụ: để khởi tạo các biến đối tượng thiết yếu]
  • __new__ phải trả về một đối tượng. Không có gì yêu cầu nó trả về một đối tượng mới là một thể hiện của đối số lớp của nó, mặc dù đó là quy ước. Nếu bạn trả về một đối tượng hiện có, lệnh gọi hàm tạo vẫn sẽ gọi phương thức __init__ của nó. Nếu bạn trả về một đối tượng của một lớp khác, phương thức __init__ của nó sẽ được gọi. Nếu bạn quên trả lại thứ gì đó, Python sẽ trả về Không một cách vô ích và người gọi của bạn có thể sẽ rất bối rối
  • Đối với các lớp bất biến, __new__ của bạn có thể trả về một tham chiếu được lưu trong bộ nhớ cache cho một đối tượng hiện có với cùng một giá trị; . Đây là một trong những lý do tại sao __init__ của họ không làm gì cả. các đối tượng được lưu trong bộ nhớ cache sẽ được khởi tạo lại nhiều lần. [Lý do khác là không còn gì để __init__ khởi tạo. __new__ trả về một đối tượng được khởi tạo đầy đủ. ]
  • Nếu bạn phân lớp một loại bất biến tích hợp sẵn và muốn thêm một số trạng thái có thể thay đổi [có thể bạn thêm chuyển đổi mặc định thành loại chuỗi], thì tốt nhất bạn nên khởi tạo trạng thái có thể thay đổi trong phương thức __init__ và để nguyên __new__
  • Nếu bạn muốn thay đổi chữ ký của hàm tạo, bạn thường phải ghi đè cả __new__ và __init__ để chấp nhận chữ ký mới. Tuy nhiên, hầu hết các kiểu dựng sẵn đều bỏ qua các đối số của phương thức mà chúng không sử dụng; . Loại 'đối tượng' tích hợp có một hình nộm __new__ và một hình nộm __init__ [mà các đối tượng khác kế thừa]. Loại 'loại' tích hợp là đặc biệt ở nhiều khía cạnh;
  • [Điều này không liên quan gì đến __new__, nhưng dù sao cũng rất tiện để biết. ] Nếu bạn phân lớp một loại tích hợp sẵn, thì không gian bổ sung sẽ tự động được thêm vào các phiên bản để chứa __dict__ và __weakrefs__. [Mặc dù vậy, __dict__ không được khởi chạy cho đến khi bạn sử dụng nó, vì vậy bạn không nên lo lắng về dung lượng bị chiếm dụng bởi một từ điển trống cho mỗi phiên bản bạn tạo. ] Nếu bạn không cần thêm khoảng trống này, bạn có thể thêm cụm từ "__slots__ = []" vào lớp học của mình. [Xem bên trên để biết thêm về __slots__. ]
  • thực tế. __new__ là một phương thức tĩnh, không phải là một phương thức lớp. Ban đầu tôi nghĩ rằng nó sẽ phải là một phương thức lớp, và đó là lý do tại sao tôi đã thêm nguyên hàm phương thức lớp. Thật không may, với các phương thức lớp, các cuộc gọi lên không hoạt động đúng trong trường hợp này, vì vậy tôi phải biến nó thành một phương thức tĩnh với một lớp rõ ràng làm đối số đầu tiên của nó. Trớ trêu thay, hiện tại không có cách sử dụng nào được biết đến cho các phương thức lớp trong bản phân phối Python [ngoại trừ trong bộ thử nghiệm]. Tuy nhiên, các phương thức lớp vẫn hữu ích ở những nơi khác, ví dụ, để lập trình các hàm tạo thay thế kế thừa

Như một ví dụ khác về __new__, đây là một cách để triển khai mẫu đơn

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
2

Để tạo một lớp đơn, bạn phân lớp từ Singleton; . Để tiếp tục khởi tạo thể hiện của lớp con, các lớp con nên ghi đè 'init' thay vì __init__ - phương thức __init__ được gọi mỗi khi hàm tạo được gọi. Ví dụ

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
3

siêu dữ liệu

Trước đây, chủ đề về siêu dữ liệu trong Python đã khiến người ta dựng tóc gáy và thậm chí là nổ tung não [xem, ví dụ: Siêu dữ liệu trong Python 1. 5]. May mắn thay, trong Python 2. 2, siêu dữ liệu dễ tiếp cận hơn và ít nguy hiểm hơn

Về mặt thuật ngữ, một siêu dữ liệu chỉ đơn giản là "lớp của một lớp". Bất kỳ lớp nào có thể hiện chính là lớp, là siêu dữ liệu. Khi chúng ta nói về một thể hiện không phải là một lớp, siêu dữ liệu của thể hiện là lớp của lớp của nó. theo định nghĩa, siêu dữ liệu của x là x. __lớp__. __lớp__. Nhưng khi chúng ta nói về một lớp C, chúng ta thường đề cập đến siêu lớp của nó khi chúng ta muốn nói đến C. __class__ [không phải C. __lớp__. __class__, đó sẽ là một meta-metaclass;

'Loại' tích hợp là siêu dữ liệu phổ biến nhất; . Các lớp cổ điển sử dụng một siêu dữ liệu khác. loại được gọi là loại. Loại lớp. Cái sau tương đối không thú vị; . Bạn không thể truy cập siêu dữ liệu của phiên bản cổ điển bằng cách sử dụng x. __lớp__. __lớp__; . __class__], bởi vì các lớp cổ điển không hỗ trợ thuộc tính __class__ trên các lớp [chỉ trong các trường hợp]

Khi một câu lệnh lớp được thực thi, trước tiên trình thông dịch sẽ xác định siêu dữ liệu M thích hợp, sau đó gọi M[name, base, dict]. Tất cả điều này xảy ra ở cuối câu lệnh lớp, sau khi phần thân của lớp [nơi các phương thức và biến lớp được định nghĩa] đã được thực thi. Các đối số cho M là tên lớp [một chuỗi lấy từ câu lệnh lớp], bộ các lớp cơ sở [các biểu thức được đánh giá ở đầu câu lệnh lớp; đây là [] nếu không có cơ sở nào được chỉ định trong câu lệnh lớp] và . Dù cuộc gọi này M[name,base,dict] trả về sau đó được gán cho biến tương ứng với tên lớp và đó là tất cả những gì có trong câu lệnh lớp

M được xác định như thế nào?

  • Nếu dict['__metaclass__'] tồn tại, nó được sử dụng
  • Mặt khác, nếu có ít nhất một lớp cơ sở, thì siêu dữ liệu của nó sẽ được sử dụng [cái này tìm thuộc tính __class__ trước và nếu không tìm thấy, hãy sử dụng loại của nó]. [Trong Python cổ điển, bước này cũng tồn tại, nhưng chỉ được thực thi khi siêu dữ liệu có thể gọi được. Cái này được gọi là cái móc Don Beaudry - mong nó yên nghỉ. ]
  • Mặt khác, nếu có một biến toàn cục có tên __metaclass__, thì nó được sử dụng
  • Mặt khác, siêu dữ liệu cổ điển [các loại. ClassType] được sử dụng

Các kết quả phổ biến nhất ở đây là M là một trong hai loại. ClassType [tạo lớp cổ điển] hoặc 'type' [tạo lớp kiểu mới]. Các kết quả phổ biến khác là loại tiện ích mở rộng tùy chỉnh [như Jim Fulton's ExtensionClass] hoặc loại phụ của 'loại' [khi chúng tôi đang sử dụng siêu dữ liệu kiểu mới]. Nhưng có thể có một cái gì đó hoàn toàn kỳ lạ ở đây. nếu chúng tôi chỉ định một lớp cơ sở có thuộc tính __class__ tùy chỉnh, chúng tôi có thể sử dụng bất kỳ thứ gì làm "siêu dữ liệu". Đó là chủ đề nổ não trong bài báo siêu lớp ban đầu của tôi và tôi sẽ không nhắc lại ở đây

Luôn luôn có thêm một nếp nhăn. Khi bạn kết hợp các lớp cổ điển và các lớp kiểu mới trong danh sách cơ sở, siêu dữ liệu của lớp cơ sở kiểu mới đầu tiên được sử dụng thay vì các loại. ClassType [giả sử dict['__metaclass__'] không được xác định]. Hiệu quả là khi bạn vượt qua một lớp cổ điển và một lớp kiểu mới, lớp con là một lớp kiểu mới

Và một số khác [Tôi hứa đây là nếp nhăn cuối cùng trong quá trình xác định siêu dữ liệu]. Đối với các siêu dữ liệu kiểu mới, có một ràng buộc là siêu dữ liệu được chọn phải bằng hoặc một phân lớp của mỗi siêu dữ liệu của các cơ sở. Hãy xem xét một lớp C với hai lớp cơ sở, B1 và ​​B2. Giả sử M = C. __lớp__, M1 = B1. __lớp__, M2 = B2. __lớp__. Sau đó, chúng tôi yêu cầu issubclass[M, M1] và issubclass[M, M2]. [Điều này là do một phương thức của B1 có thể tự gọi một siêu phương thức được xác định trong M1. __class__, ngay cả khi self là một thể hiện của một lớp con của B1. ]

Cuốn sách siêu dữ liệu mô tả một cơ chế theo đó một siêu dữ liệu phù hợp được tạo tự động, khi cần thiết, thông qua nhiều kế thừa từ M1 và M2. Trong Python 2. 2, tôi đã chọn một cách tiếp cận đơn giản hơn, đưa ra một ngoại lệ nếu ràng buộc siêu dữ liệu không được thỏa mãn; . Tuy nhiên, nếu một trong các siêu dữ liệu cơ sở thỏa mãn ràng buộc [bao gồm cả __metaclass__ được cung cấp rõ ràng, nếu có], thì siêu dữ liệu cơ sở đầu tiên được tìm thấy thỏa mãn ràng buộc sẽ được sử dụng làm siêu dữ liệu

Trong thực tế, điều này có nghĩa là nếu bạn có một hệ thống phân cấp siêu dữ liệu suy biến có hình dạng của một tòa tháp [có nghĩa là đối với hai siêu dữ liệu M1 và M2, thì ít nhất một trong số issubclass[M1, M2] hoặc issubclass[M2, M1] luôn đúng . Ví dụ

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
4

Đối với lớp C2, ràng buộc được thỏa mãn vì M2 là lớp con của M1. Đối với lớp C3 thỏa mãn vì M3 là lớp con của cả M1 và M2. Đối với lớp D, siêu dữ liệu rõ ràng M1 không phải là lớp con của siêu dữ liệu cơ sở [M2, M3], nhưng việc chọn M3 thỏa mãn ràng buộc, vì vậy D. __class__ là M3. Tuy nhiên, lớp E là một lỗi. hai siêu dữ liệu liên quan là M3 và M4 và không phải là lớp con của lớp kia. Chúng ta có thể sửa trường hợp sau này như sau

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
5

[Cách tiếp cận từ sách siêu dữ liệu sẽ tự động cung cấp định nghĩa lớp cho M5 dựa trên định nghĩa ban đầu của lớp E. ]

Ví dụ siêu dữ liệu

Trước tiên hãy làm mới một số lý thuyết. Hãy nhớ rằng một câu lệnh lớp gây ra một cuộc gọi đến M[name, base, dict] trong đó M là siêu dữ liệu. Bây giờ, siêu dữ liệu là một lớp và chúng tôi đã thiết lập rằng khi một lớp được gọi, các phương thức __new__ và __init__ của nó được gọi theo thứ tự. Vì vậy, một cái gì đó như thế này sẽ xảy ra

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
6

Tôi đang viết cuộc gọi __init__ dưới dạng cuộc gọi phương thức không liên kết tại đây. Điều này làm rõ rằng chúng tôi đang gọi __init__ được xác định bởi M, không phải __init__ được xác định trong cls [sẽ là phần khởi tạo cho các phiên bản của cls]. Nhưng nó thực sự gọi phương thức __init__ của đối tượng cls;

Our first example is a metaclass that looks through the methods of a class for methods named _get_ and _set_, and automatically adds property descriptors named . It turns out that it's sufficient to override __init__ to do what we want. The algorithm makes two passes: first it collects names of properties, then it adds them to the class. The collection pass looks through dict, which is the dictionary representing the class variables and methods [excluding base class variables and methods]. But the second pass, the property construction pass, looks up _get_ and _set_ as class attributes. This means that if a base class defines _get_x and a subclass defines _set_x, the subclass will have a property x created from both methods, even though only _set_x occurs in the subclass's dictionary. Thus, you can extend properties in a subclass. Note that we use the three-argument form of getattr[], so a missing _get_x or _set_x will be translated into None, not raise an AttributeError. We also call the base class __init__ method, in cooperative fashion using super[].

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
7

Hãy kiểm tra autoprop với một ví dụ ngớ ngẩn. Đây là một lớp lưu trữ một thuộc tính x dưới dạng giá trị đảo ngược của nó trong self. __x

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
8

Ví dụ thứ hai của chúng tôi tạo một lớp, 'autosuper', lớp này sẽ thêm một biến lớp riêng có tên __super, được đặt thành giá trị super[cls]. [Nhắc lại phần thảo luận về bản thân. __siêu trên. ] Bây giờ, __super là tên riêng [bắt đầu bằng dấu gạch dưới kép] nhưng chúng tôi muốn nó là tên riêng của lớp được tạo, không phải tên riêng của autosuper. Vì vậy, chúng ta phải tự xáo trộn tên và sử dụng setattr[] để đặt biến lớp. Với mục đích của ví dụ này, tôi đang đơn giản hóa việc xáo trộn tên thành "thêm dấu gạch dưới và tên lớp". Một lần nữa, chỉ cần ghi đè __init__ để làm những gì chúng ta muốn, và một lần nữa, chúng ta gọi lớp cơ sở __init__ một cách hợp tác

    >>> print defaultdict               # show our type
    
    >>> print type[defaultdict]         # its metatype
    
    >>> a = defaultdict[default=0.0]    # create an instance
    >>> print a                         # show the instance
    {}
    >>> print type[a]                   # show its type
    
    >>> print a.__class__               # show its class
    
    >>> print type[a] is a.__class__    # its type is its class
    1
    >>> a[1] = 3.25                     # modify the instance
    >>> print a                         # show the new value
    {1: 3.25}
    >>> print a[1]                      # show the new item
    3.25
    >>> print a[0]                      # a non-existant item
    0.0
    >>> a.merge[{1:100, 2:200}]         # use a dictionary method
    >>> print a                         # show the result
    {1: 3.25, 2: 200}
    >>>
9

Bây giờ hãy kiểm tra autosuper với sơ đồ kim cương cổ điển

    >>> print a.keys[]
    [1, 2]
    >>> exec "x = 3; print x" in a
    3
    >>> print a.keys[]
    ['__builtins__', 1, 2, 'x']
    >>> print a['x']
    3
    >>> 
0

[Siêu dữ liệu autosuper của chúng ta dễ dàng bị đánh lừa nếu bạn định nghĩa một lớp con có cùng tên với một lớp cơ sở; nó thực sự nên kiểm tra tình trạng đó và đưa ra lỗi nếu nó xảy ra. Nhưng đó là một ví dụ nhiều mã hơn là cảm thấy đúng, vì vậy tôi sẽ để nó như một bài tập cho người đọc. ]

Bây giờ chúng tôi có hai siêu dữ liệu được phát triển độc lập, chúng tôi có thể kết hợp cả hai thành siêu dữ liệu thứ ba kế thừa từ cả hai

    >>> print a.keys[]
    [1, 2]
    >>> exec "x = 3; print x" in a
    3
    >>> print a.keys[]
    ['__builtins__', 1, 2, 'x']
    >>> print a['x']
    3
    >>> 
1

Đơn giản nhỉ? . Hãy kiểm tra nó

    >>> print a.keys[]
    [1, 2]
    >>> exec "x = 3; print x" in a
    3
    >>> print a.keys[]
    ['__builtins__', 1, 2, 'x']
    >>> print a['x']
    3
    >>> 
2

Đó là tất cả cho ngày hôm nay. Tôi hy vọng bộ não của bạn không bị tổn thương quá nhiều

tương thích ngược

Thư giãn. Hầu hết các tính năng được mô tả ở trên chỉ được gọi khi bạn sử dụng câu lệnh lớp có đối tượng tích hợp làm lớp cơ sở [hoặc khi bạn sử dụng phép gán __metaclass__ rõ ràng]

Bạn có thể chuyển từ điển cho một lớp bằng Python không?

Trong Python, mọi thứ đều là đối tượng, vì vậy từ điển có thể được truyền dưới dạng đối số cho hàm giống như các biến khác được truyền.

Phương pháp __ dict __ là gì?

Tất cả đối tượng trong Python đều có thuộc tính __dict__, là đối tượng từ điển chứa tất cả các thuộc tính được xác định cho chính đối tượng đó . Việc ánh xạ các thuộc tính với các giá trị của nó được thực hiện để tạo từ điển.

Chức năng Len có hoạt động với từ điển không?

Để tính độ dài của từ điển, chúng ta có thể sử dụng phương thức len[] tích hợp sẵn của Python . Phương thức len[] trả về số lượng khóa trong từ điển Python.

Chủ Đề