2009-10-12 22 views
11

Từ những gì tôi nhớ từ lớp C++, giáo sư nói rằng quá tải toán tử là tuyệt vời, nhưng phải mất khá nhiều suy nghĩ và mã để trang trải tất cả các trường hợp cuối (ví dụ: khi quá tải + bạn cũng có thể muốn quá tải +++= và cũng đảm bảo xử lý các trường hợp cuối như thêm đối tượng vào chính nó, v.v.), bạn chỉ nên xem xét nó trong những trường hợp mà tính năng này sẽ có tác động lớn đến mã, như quá tải các toán tử cho lớp ma trận trong một ứng dụng toán học.Quy tắc chung khi sử dụng quá tải toán tử trong python

Điều tương tự cũng áp dụng với python? Bạn có đề xuất ghi đè hành vi của nhà điều hành trong python không? Và bạn có thể đưa ra những quy tắc nào?

Trả lời

23

điều hành quá tải chủ yếu là hữu ích khi bạn đang thực hiện một lớp mới mà rơi vào một "Tóm tắt Base Class" hiện có (ABC) - thực tế, nhiều ABC trong mô-đun thư viện chuẩn collections dựa vào sự hiện diện của một số phương thức đặc biệt (và các phương thức đặc biệt, một phương pháp có tên bắt đầu và kết thúc với dấu gạch ngang kép AKA "dunders", chính xác là cách bạn thực hiện quá tải toán tử trong Python). Điều này cung cấp hướng dẫn khởi đầu tốt.

Ví dụ, một Container lớp phải ghi đè phương pháp đặc biệt __contains__, tức là, các nhà điều hành kiểm tra thành viên item in container (như trong, if item in container: - đừng nhầm lẫn với tuyên bố for, for item in container:, mà dựa vào __iter__ -!) . Tương tự, một số Hashable phải ghi đè __hash__, Sized phải ghi đè __len__, một Sequence hoặc Mapping phải ghi đè __getitem__, v.v. (Ngoài ra, ABC có thể cung cấp lớp học của bạn với chức năng mixin - ví dụ: cả hai SequenceMapping có thể cung cấp __contains__ trên cơ sở cung cấp số ghi __getitem__ được cung cấp của bạn và do đó tự động làm cho lớp của bạn là Container).

Ngoài collections, bạn sẽ muốn ghi đè các phương pháp đặc biệt (tức là cung cấp cho quá tải nhà điều hành) chủ yếu nếu lớp mới của bạn "là số". Các trường hợp đặc biệt khác tồn tại, nhưng chống lại sự cám dỗ của các toán tử quá tải "chỉ để làm mát", không có kết nối ngữ nghĩa với ý nghĩa "bình thường", như luồng của C++ làm cho các đường dây <<>> và Python (trong Python 2.*, may mắn là không ở 3.* nhiều hơn ;-) làm cho % - khi các nhà khai thác như vậy không có nghĩa là "bit-shifting" hoặc "phần còn lại phân chia", bạn chỉ gây ra sự nhầm lẫn. Thư viện chuẩn của một ngôn ngữ có thể thoát khỏi nó (mặc dù nó không nên ;-), nhưng trừ khi thư viện của bạn được phổ biến rộng rãi như ngôn ngữ chuẩn của ngôn ngữ, sự nhầm lẫn sẽ bị tổn hại! -)

+4

BTW, cho những người tuyệt vọng tại nghĩ rằng không có% để định dạng chuỗi: mặc dù tài liệu Python 3 mô tả% là lỗi thời, nó vẫn được ghi lại và dường như không có tính năng nào thực sự biến mất cho đến Python 4, dựa trên các cuộc thảo luận gần đây trong python-dev. Điều đó để lại rất nhiều thời gian để tìm hiểu và yêu thích phương thức định dạng chuỗi mới đã có sẵn trong 2.6. –

+0

Chức năng định dạng tốt hơn nhiều so với% – Casebash

12

Tôi đã viết phần mềm với số lượng đáng kể quá tải và gần đây tôi rất tiếc chính sách đó. Tôi sẽ nói điều này:

Chỉ các nhà khai thác quá tải nếu đó là điều tự nhiên, dự kiến ​​sẽ làm và không có bất kỳ tác dụng phụ nào.

Vì vậy, nếu bạn thực hiện một lớp mới RomanNumeral, nó làm cho tinh thần để quá tải cộng và trừ vv Nhưng không quá tải nó trừ khi nó là điều tự nhiên: nó làm cho không có ý nghĩa để xác định cộng và trừ cho một Car hoặc một đối tượng Vehicle .

Quy tắc chung khác: không quá tải ==. Nó làm cho nó rất khó (mặc dù không phải là không thể) để thực sự kiểm tra nếu hai đối tượng là như nhau. Tôi đã phạm sai lầm này và trả tiền cho nó trong một thời gian dài.

Đối với khi quá tải +=, ++ vv, tôi thực sự nói: chỉ quá tải các toán tử bổ sung nếu bạn có rất nhiều nhu cầu cho chức năng đó. Thật dễ dàng để có một cách để làm điều gì đó hơn năm. Chắc chắn, nó có nghĩa là đôi khi bạn sẽ phải viết x = x + 1 thay vì x += 1, nhưng nhiều mã hơn là ok nếu nó rõ ràng hơn. Nói chung, giống như với nhiều tính năng 'ưa thích', thật dễ dàng để nghĩ rằng bạn muốn một cái gì đó khi bạn không thực sự, thực hiện một loạt các công cụ, không nhận thấy các tác dụng phụ, và sau đó con số nó ra sau đó. Err ở phe bảo thủ.

EDIT: Tôi muốn thêm ghi chú giải thích về quá tải ==, bởi vì có vẻ như nhiều người nhận xét khác hiểu nhầm điều này và nó đã khiến tôi hiểu. Có, is tồn tại, nhưng đó là một hoạt động khác. Giả sử tôi có một đối tượng x, là từ lớp tùy chỉnh của tôi hoặc là số nguyên. Tôi muốn xem nếu x là số 500. Nhưng nếu bạn đặt x = 500, sau đó kiểm tra x is 500, bạn sẽ nhận được False, do cách Python lưu trữ số. Với 50, nó sẽ trả về True. Nhưng bạn không thể sử dụng is, vì bạn có thể muốn x == 500 trả lại True nếu x là một phiên bản của lớp học của bạn. Gây nhầm lẫn? Chắc chắn rồi. Nhưng đây là loại chi tiết bạn cần phải hiểu cho các nhà khai thác quá tải thành công.

+2

Quá tải '++' không đặc biệt áp dụng, vì Python không có toán tử '++'. –

+0

chắc chắn, tôi sẽ thay đổi ví dụ cho Python. (mặc dù tôi có nghĩa là để giải thích nguyên tắc chung). – Peter

+2

Bạn không thể kiểm tra xem hai đối tượng có giống nhau hay không bởi 'if a is b: ...', ngay cả khi '==' bị quá tải?Hay tôi hiểu lầm điểm bạn đang làm? – sth

3

Quá tải của Python "an toàn hơn" nói chung so với C++ - ví dụ, toán tử gán không thể bị quá tải và += có triển khai mặc định hợp lý.

Tuy nhiên, theo một số cách, quá tải trong Python vẫn bị "hỏng" như trong C++. Các lập trình viên nên hạn chế mong muốn "tái sử dụng" một toán tử cho các mục đích không liên quan, chẳng hạn như C++ tái sử dụng các bithifts để thực hiện định dạng chuỗi và phân tích cú pháp.Đừng quá tải một toán tử với các ngữ nghĩa khác nhau từ việc thực hiện của bạn chỉ để có được cú pháp đẹp hơn.

Kiểu Python hiện đại không khuyến khích quá tải "rogue", nhưng nhiều khía cạnh của ngôn ngữ và thư viện chuẩn giữ lại các toán tử có tên kém để tương thích ngược. Ví dụ:

  • %: mô đun và chuỗi định dạng
  • +: bổ sung và chuỗi nối
  • *: nhân và trình tự lặp lại

Vì vậy, quy tắc của ngón tay cái? Nếu việc thực thi nhà điều hành của bạn sẽ làm mọi người ngạc nhiên, đừng làm điều đó.

+0

Tôi nghĩ rằng việc sử dụng kép của '+' và '*' được đánh giá cao - cả hai đều sử dụng ít nhất là làm cùng một khái niệm, thậm chí nếu họ làm điều đó khác đi. –

+1

Chúng không giống nhau. '(1 + 2) == (2 + 1)', nhưng '(" a "+" b ")! = (" B "+" a ")'. '(1 + 2-1) == 2', nhưng' ("a" + "b" - "a") 'là vô nghĩa. Cùng một loại vấn đề tồn tại cho phép nhân. –

+3

Tôi không có ý nói rằng chúng giống nhau, nhưng tôi đã nói rất tệ. Tôi có nghĩa là khái niệm họ thực hiện các hành động tương tự. Tôi nghĩ rằng hầu hết mọi người sẽ nói rằng tham gia hai chuỗi và thêm hai con số là cả hai "phụ gia" hoạt động, và nhân con số và lặp đi lặp lại một chuỗi nhiều lần là cả hai "nhân" hoạt động. –

5

Dưới đây là một ví dụ sử dụng bitwise hoặc hoạt động để mô phỏng một đường ống unix. Điều này được dự định như là một ví dụ truy cập cho hầu hết các quy tắc của ngón tay cái.

Tôi chỉ tìm thấy Lumberjack trong đó sử dụng cú pháp này trong mã thực



class pipely(object): 
    def __init__(self, *args, **kw): 
     self._args = args 
     self.__dict__.update(kw) 

    def __ror__(self, other): 
     return (self.map(x) for x in other if self.filter(x)) 

    def map(self, x): 
     return x 

    def filter(self, x): 
     return True 

class sieve(pipely): 
    def filter(self, x): 
     n = self._args[0] 
     return x==n or x%n 

class strify(pipely): 
    def map(self, x): 
     return str(x) 

class startswith(pipely): 
    def filter(self, x): 
     n=str(self._args[0]) 
     if x.startswith(n): 
      return x 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | strify() | startswith(5): 
    print i 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | startswith(5): 
    print i 

print"*"*80 
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | pipely(filter=lambda x: x.startswith('5')): 
    print i 

+0

+1 vì điều này rất thú vị, mặc dù tôi không biết liệu tôi có chấp thuận sử dụng mã này trong mã thực hay không. –

+2

:) Tôi quản trị Tôi đã không sử dụng nó trong mã thực sự, nhưng nó là một cách thuận tiện để tạo chuỗi với nhau. Bạn có thể làm một cái gì đó tương tự với đồng-thói quen, nhưng cú pháp trở nên giống như ' sàng (2, sàng (3, sàng (5, sàng (7))))' mà tôi không thích hơn –