Cập nhật lệnhddict python

Nếu bạn không ấn tượng với nội trang super[] của Python, rất có thể bạn không thực sự biết nó có khả năng làm gì hoặc cách sử dụng nó một cách hiệu quả.

Phần lớn đã được viết về super[] và phần lớn bài viết đó đã thất bại. Bài viết này tìm cách cải thiện tình hình bằng cách

  • cung cấp các trường hợp sử dụng thực tế
  • đưa ra một mô hình tinh thần rõ ràng về cách thức hoạt động của nó
  • hiển thị thủ công để làm cho nó hoạt động mọi lúc
  • lời khuyên cụ thể để xây dựng các lớp sử dụng super[]
  • ủng hộ các ví dụ thực tế hơn các sơ đồ hình thoi trừu tượng ABCD

Các ví dụ cho bài đăng này có sẵn ở cả cú pháp Python 2 và cú pháp Python 3

Sử dụng cú pháp Python 3, hãy bắt đầu với một trường hợp sử dụng cơ bản, một lớp con để mở rộng một phương thức từ một trong các lớp dựng sẵn

class LoggingDict[dict]:
    def __setitem__[self, key, value]:
        logging.info['Setting %r to %r' % [key, value]]
        super[].__setitem__[key, value]

Lớp này có tất cả các khả năng giống như lớp cha của nó, dict, nhưng nó mở rộng phương thức __setitem__ để tạo các mục nhật ký bất cứ khi nào một khóa được cập nhật. Sau khi thực hiện một mục nhập nhật ký, phương thức này sử dụng super[] để ủy thác công việc thực sự cập nhật từ điển bằng cặp khóa/giá trị

Trước khi super[] được giới thiệu, chúng tôi đã thiết lập sẵn cuộc gọi bằng dict. __setitem__[bản thân, khóa, giá trị]. Tuy nhiên, super[] tốt hơn vì nó là một tham chiếu gián tiếp được tính toán

Một lợi ích của sự gián tiếp là chúng ta không phải chỉ định lớp đại biểu theo tên. Nếu bạn chỉnh sửa mã nguồn để chuyển lớp cơ sở sang một số ánh xạ khác, tham chiếu super[] sẽ tự động theo sau. Bạn có một nguồn sự thật duy nhất

class LoggingDict[SomeOtherMapping]:            # new base class
    def __setitem__[self, key, value]:
        logging.info['Setting %r to %r' % [key, value]]
        super[].__setitem__[key, value]         # no change needed

Ngoài việc cô lập các thay đổi, còn có một lợi ích lớn khác đối với tính năng gián tiếp được tính toán, một lợi ích có thể không quen thuộc với những người đến từ các ngôn ngữ tĩnh. Vì sự gián tiếp được tính toán trong thời gian chạy, chúng tôi có quyền tự do tác động đến phép tính để sự gián tiếp sẽ trỏ đến một số lớp khác

Phép tính phụ thuộc vào cả lớp gọi super và cây tổ tiên của cá thể. Thành phần đầu tiên, lớp mà super được gọi, được xác định bởi mã nguồn của lớp đó. Trong ví dụ của chúng ta, super[] được gọi trong LoggingDict. phương thức __setitem__. Thành phần đó được cố định. Thành phần thứ hai và thú vị hơn là biến [chúng ta có thể tạo các lớp con mới với cây tổ tiên phong phú]

Hãy tận dụng lợi thế này để xây dựng một từ điển ghi nhật ký theo thứ tự mà không sửa đổi các lớp hiện có của chúng ta

class LoggingOD[LoggingDict, collections.OrderedDict]:
    pass

Cây tổ tiên cho lớp mới của chúng ta là. LoggingOD, LoggingDict, OrderedDict, dict, đối tượng. Đối với mục đích của chúng tôi, kết quả quan trọng là OrderedDict đã được chèn sau LoggingDict và trước dict. Điều này có nghĩa là cuộc gọi super[] trong LoggingDict. __setitem__ hiện gửi cập nhật khóa/giá trị tới OrderedDict thay vì dict

Hãy suy nghĩ về điều đó trong một thời điểm. Chúng tôi không thay đổi mã nguồn của LoggingDict. Thay vào đó, chúng tôi đã xây dựng một lớp con có logic duy nhất là kết hợp hai lớp hiện có và kiểm soát thứ tự tìm kiếm của chúng

________________________________________________________________________________________________________________________________________

Đơn hàng tìm kiếm

Cái mà tôi đã gọi là thứ tự tìm kiếm hoặc cây tổ tiên được chính thức gọi là Thứ tự giải quyết phương pháp hoặc MRO. Thật dễ dàng để xem MRO bằng cách in thuộc tính __mro__

>>> pprint[LoggingOD.__mro__]
[,
 ,
 ,
 ,
 ]

Nếu mục tiêu của chúng tôi là tạo một lớp con có MRO theo ý thích của chúng tôi, thì chúng tôi cần biết cách tính toán nó. Những điều cơ bản là đơn giản. Trình tự bao gồm lớp, các lớp cơ sở của nó và các lớp cơ sở của các cơ sở đó, v.v. cho đến khi đạt đến đối tượng là lớp gốc của tất cả các lớp. Trình tự được sắp xếp sao cho một lớp luôn xuất hiện trước cha của nó và nếu có nhiều cha, chúng sẽ giữ nguyên thứ tự như bộ của các lớp cơ sở

MRO được hiển thị ở trên là một thứ tự tuân theo các ràng buộc đó

  • LoggingOD đi trước cha mẹ của nó, LoggingDict và OrderedDict
  • LoggingDict đi trước OrderedDict vì LoggingOD. __bases__ là [LoggingDict, OrderedDict]
  • LoggingDict đứng trước cha mẹ của nó là dict
  • OrderedDict đứng trước cha mẹ của nó là dict
  • dict đứng trước cha mẹ của nó là đối tượng

Quá trình giải quyết các ràng buộc đó được gọi là tuyến tính hóa. Có một số bài báo hay về chủ đề này, nhưng để tạo các lớp con với MRO theo ý thích của chúng tôi, chúng tôi chỉ cần biết hai ràng buộc. trẻ em đứng trước cha mẹ và thứ tự xuất hiện trong __bases__ được tôn trọng

________________________________________________________________________________________________________________________________________

Lời khuyên thiết thực

super[] đang kinh doanh ủy quyền các lệnh gọi phương thức cho một số lớp trong cây tổ tiên của cá thể. Để các lệnh gọi phương thức có thể sắp xếp lại hoạt động, các lớp cần được thiết kế hợp tác. Điều này trình bày ba vấn đề thực tế dễ giải quyết

  • phương thức được gọi bởi super[] cần phải tồn tại
  • người gọi và người được gọi cần phải có chữ ký đối số phù hợp
  • và mọi lần xuất hiện của phương thức đều cần sử dụng super[]

1] Trước tiên hãy xem xét các chiến lược để nhận các đối số của người gọi khớp với chữ ký của phương thức được gọi. Điều này khó khăn hơn một chút so với các cuộc gọi phương thức truyền thống trong đó callee được biết trước. Với super[], callee không được biết tại thời điểm một lớp được viết [vì một lớp con được viết sau đó có thể đưa các lớp mới vào MRO]

Một cách tiếp cận là gắn với một chữ ký cố định bằng cách sử dụng các đối số vị trí. Điều này hoạt động tốt với các phương thức như __setitem__ có chữ ký cố định của hai đối số, khóa và giá trị. Kỹ thuật này được hiển thị trong ví dụ LoggingDict trong đó __setitem__ có cùng chữ ký trong cả LoggingDict và dict

Một cách tiếp cận linh hoạt hơn là có mọi phương thức trong cây tổ tiên được thiết kế hợp tác để chấp nhận các đối số từ khóa và từ điển đối số từ khóa, để xóa mọi đối số mà nó cần và chuyển tiếp các đối số còn lại bằng cách sử dụng **kwds, cuối cùng để trống từ điển

Mỗi cấp loại bỏ các đối số từ khóa mà nó cần để lệnh trống cuối cùng có thể được gửi tới một phương thức không mong đợi gì cả [ví dụ: đối tượng. __init__ mong đợi không có đối số]

class Shape:
    def __init__[self, shapename, **kwds]:
        self.shapename = shapename
        super[].__init__[**kwds]        

class ColoredShape[Shape]:
    def __init__[self, color, **kwds]:
        self.color = color
        super[].__init__[**kwds]

cs = ColoredShape[color='red', shapename='circle']

2] Sau khi xem xét các chiến lược để khớp các mẫu đối số người gọi/callee, bây giờ chúng ta hãy xem cách đảm bảo phương thức đích tồn tại

Ví dụ trên cho thấy trường hợp đơn giản nhất. Chúng tôi biết rằng đối tượng có phương thức __init__ và đối tượng đó luôn là lớp cuối cùng trong chuỗi MRO, do đó, bất kỳ chuỗi cuộc gọi nào đến super[]. __init__ được đảm bảo kết thúc bằng lệnh gọi đối tượng. phương thức __init__. Nói cách khác, chúng tôi đảm bảo rằng mục tiêu của cuộc gọi super[] được đảm bảo tồn tại và sẽ không bị lỗi với AttributeError

Đối với trường hợp đối tượng không có phương thức quan tâm [ví dụ như phương thức draw[]], chúng ta cần viết một lớp gốc đảm bảo được gọi trước đối tượng. Trách nhiệm của lớp gốc chỉ đơn giản là thực hiện cuộc gọi phương thức mà không thực hiện cuộc gọi chuyển tiếp bằng cách sử dụng super[]

Nguồn gốc. draw cũng có thể sử dụng lập trình phòng thủ bằng cách sử dụng một xác nhận để đảm bảo rằng nó không che giấu một số phương thức draw[] khác sau này trong chuỗi. Điều này có thể xảy ra nếu một lớp con kết hợp nhầm một lớp có phương thức draw[] nhưng không kế thừa từ Root

class Root:
    def draw[self]:
        # the delegation chain stops here
        assert not hasattr[super[], 'draw']

class Shape[Root]:
    def __init__[self, shapename, **kwds]:
        self.shapename = shapename
        super[].__init__[**kwds]
    def draw[self]:
        print['Drawing.  Setting shape to:', self.shapename]
        super[].draw[]

class ColoredShape[Shape]:
    def __init__[self, color, **kwds]:
        self.color = color
        super[].__init__[**kwds]
    def draw[self]:
        print['Drawing.  Setting color to:', self.color]
        super[].draw[]

cs = ColoredShape[color='blue', shapename='square']
cs.draw[]

Nếu các lớp con muốn đưa các lớp khác vào MRO, thì các lớp khác đó cũng cần kế thừa từ Root để không có đường dẫn nào gọi hàm draw[] có thể đến đối tượng mà không bị Root chặn lại. vẽ tranh. Điều này cần được ghi lại rõ ràng để ai đó viết các lớp hợp tác mới sẽ biết phân lớp từ Root. Hạn chế này không khác nhiều so với yêu cầu riêng của Python là tất cả các ngoại lệ mới phải kế thừa từ BaseException

3] Các kỹ thuật hiển thị ở trên đảm bảo rằng super[] gọi một phương thức đã biết là tồn tại và chữ ký sẽ chính xác; . Điều này rất dễ đạt được nếu chúng ta thiết kế các lớp một cách hợp tác – chỉ cần thêm lệnh gọi super[] vào mọi phương thức trong chuỗi

Ba kỹ thuật được liệt kê ở trên cung cấp phương tiện để thiết kế các lớp hợp tác có thể được tạo thành hoặc sắp xếp lại bởi các lớp con

________________________________________________________________________________________________________________________________________

Làm thế nào để kết hợp một lớp không hợp tác

Đôi khi, một lớp con có thể muốn sử dụng các kỹ thuật đa kế thừa hợp tác với lớp bên thứ ba không được thiết kế cho nó [có lẽ phương thức quan tâm của nó không sử dụng super[] hoặc có lẽ lớp không kế thừa từ lớp gốc . Tình huống này có thể dễ dàng khắc phục bằng cách tạo một lớp bộ điều hợp hoạt động theo quy tắc

Ví dụ: lớp Moveable sau đây không thực hiện cuộc gọi super[] và nó có chữ ký __init__[] không tương thích với đối tượng. __init__ và nó không kế thừa từ Root

class Moveable:
    def __init__[self, x, y]:
        self.x = x
        self.y = y
    def draw[self]:
        print['Drawing at position:', self.x, self.y]

Nếu chúng tôi muốn sử dụng lớp này với hệ thống phân cấp ColoredShape được thiết kế hợp tác của chúng tôi, chúng tôi cần tạo một bộ điều hợp với các cuộc gọi super[] cần thiết

class MoveableAdapter[Root]:
    def __init__[self, x, y, **kwds]:
        self.movable = Moveable[x, y]
        super[].__init__[**kwds]
    def draw[self]:
        self.movable.draw[]
        super[].draw[]

class MovableColoredShape[ColoredShape, MoveableAdapter]:
    pass

MovableColoredShape[color='red', shapename='triangle',
                    x=10, y=20].draw[]

________________________________________________________________________________________________________________________________________

Ví dụ hoàn chỉnh – Chỉ để giải trí

Trong Python 2. 7 và 3. 2, mô-đun bộ sưu tập có cả lớp Counter và lớp OrderedDict. Các lớp đó dễ dàng được soạn thảo để tạo một OrderedCounter

________số 8

________________________________________________________________________________________________________________________________________

Ghi chú và Tài liệu tham khảo

* Khi phân lớp một nội trang như dict[], thường cần phải ghi đè hoặc mở rộng nhiều phương thức cùng một lúc. Trong các ví dụ trên, phần mở rộng __setitem__ không được sử dụng bởi các phương pháp khác như dict. cập nhật, vì vậy có thể cần phải mở rộng chúng. Yêu cầu này không phải là duy nhất đối với super[];

* Nếu một lớp phụ thuộc vào một lớp cha trước một lớp khác [ví dụ: LoggingOD phụ thuộc vào LoggingDict xuất hiện trước OrderedDict xuất hiện trước dict], bạn có thể dễ dàng thêm các xác nhận để xác thực và ghi lại thứ tự giải quyết phương pháp dự kiến

position = LoggingOD.__mro__.index
assert position[LoggingDict] < position[OrderedDict]
assert position[OrderedDict] < position[dict]

* Có thể tìm thấy các bài viết tốt về thuật toán tuyến tính hóa tại tài liệu Python MRO và tại mục nhập Wikipedia về Tuyến tính hóa C3

* Ngôn ngữ lập trình Dylan có cấu trúc phương thức tiếp theo hoạt động giống như super[] của Python. Xem tài liệu lớp học của Dylan để biết cách viết ngắn gọn về cách nó hoạt động

* Phiên bản Python 3 của super[] được sử dụng trong bài viết này. Mã nguồn làm việc đầy đủ có thể được tìm thấy tại. Công thức 577720. Cú pháp Python 2 khác ở chỗ các đối số kiểu và đối tượng của super[] rõ ràng hơn là ẩn. Ngoài ra, phiên bản Python 2 của super[] chỉ hoạt động với các lớp kiểu mới [những lớp kế thừa rõ ràng từ đối tượng hoặc loại dựng sẵn khác]. Toàn bộ mã nguồn đang hoạt động sử dụng cú pháp Python 2 có tại Công thức 577721.
_____________________________________________________________________________________________________________________________

Sự nhìn nhận

Một số Pythonistas đã thực hiện đánh giá trước khi xuất bản bài viết này. Nhận xét của họ đã giúp cải thiện nó khá nhiều

họ đang. Laura Creighton, Alex Gaynor, Philip Jenvey, Brian Curtin, David Beazley, Chris Angelico, Jim Baker, Ethan Furman và Michael Foord. Cảm ơn một và tất cả

Quảng cáo

Chia sẻ cái này

  • Twitter
  • Facebook
  • reddit

Như thế này

Thích Đang tải.

Khám phá các bài viết trong cùng chuyên mục. Thuật toán, Tài liệu, Kế thừa, Mã nguồn mở, Python

Mục nhập này đã được đăng vào ngày 26 tháng 5 năm 2011 lúc 9. 15 giờ sáng và được lưu trong Thuật toán, Tài liệu, Kế thừa, Nguồn mở, Python. Bạn có thể đăng ký qua RSS 2. 0 nguồn cấp dữ liệu cho bình luận của bài đăng này. Bạn có thể nhận xét bên dưới hoặc liên kết tới URL vĩnh viễn này từ trang web của riêng bạn

Chủ Đề