2015-01-12 18 views
18

Trong Python, khi triển khai một loại chuỗi, tôi thường (tương đối nói) thấy mình viết code như thế này:Làm thế nào để viết __getitem__ sạch?

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      # Get a single item 
     elif isinstance(key, slice): 
      # Get a whole slice 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

Mã kiểm tra các loại đối số của nó một cách rõ ràng với isinstance(). Đây là regarded as an antipattern trong cộng đồng Python. Làm thế nào để tránh nó?

  • tôi không thể sử dụng functools.singledispatch, bởi vì đó là quite deliberately không tương thích với các phương pháp (nó sẽ cố gắng để gửi về self, đó là hoàn toàn vô dụng vì chúng ta đang đã cử trên self qua OOP polymorphism). Nó hoạt động với @staticmethod, nhưng nếu tôi cần lấy đồ ra khỏi số self thì sao?
  • Truyền tới int() và sau đó bắt các TypeError, kiểm tra một lát và có thể nâng cấp lại vẫn xấu xí, mặc dù có lẽ hơi kém hơn.
  • Nó có thể sạch hơn để chuyển đổi số nguyên thành lát một phần tử và xử lý cả hai tình huống với cùng mã, nhưng có vấn đề riêng (trả lại 0 hoặc [0]?).

Trả lời

13

Dường như có vẻ lạ, tôi nghi ngờ rằng cách bạn có nó là cách tốt nhất để thực hiện mọi thứ. Các mẫu thường tồn tại để bao gồm các trường hợp sử dụng phổ biến, nhưng điều đó không có nghĩa là chúng nên được coi là phúc âm khi theo dõi chúng làm cho cuộc sống trở nên khó khăn hơn. Lý do chính mà PEP 443 cho balking tại typechecking rõ ràng là nó là "giòn và đóng cửa để mở rộng". Tuy nhiên, chủ yếu áp dụng cho các chức năng tùy chỉnh có một số loại khác nhau bất kỳ lúc nào. Từ số Python docs on __getitem__:

Đối với loại trình tự, khóa được chấp nhận phải là số nguyên và đối tượng lát. Lưu ý rằng cách diễn giải đặc biệt của các chỉ số âm (nếu lớp muốn mô phỏng một kiểu chuỗi) là tùy thuộc vào phương thức __getitem __(). Nếu khóa là một loại không thích hợp, TypeError có thể được nâng lên; nếu giá trị nằm ngoài tập hợp các chỉ mục cho chuỗi (sau khi giải thích đặc biệt các giá trị âm), thì IndexError sẽ được nâng lên. Đối với các loại ánh xạ, nếu thiếu khóa (không phải trong vùng chứa), nên sử dụng KeyError.

Tài liệu Python nêu rõ hai loại cần được chấp nhận và phải làm gì nếu một mục không thuộc hai loại đó được cung cấp. Do các loại được cung cấp bởi chính tài liệu, nó không có khả năng thay đổi (làm như vậy sẽ phá vỡ nhiều triển khai hơn là của bạn), do đó, có thể không đáng để bạn tìm cách viết mã với Python có khả năng thay đổi.

Nếu bạn đang đặt tránh việc đánh máy rõ ràng, tôi sẽ hướng bạn đến this SO answer. Nó chứa một thực hiện ngắn gọn của một trang trí @methdispatch (không phải tên của tôi, nhưng tôi sẽ cuộn với nó) cho phép @singledispatch làm việc với các phương pháp bằng cách buộc nó kiểm tra args[1] (arg) thay vì args[0] (tự). Sử dụng điều đó sẽ cho phép bạn sử dụng công văn đơn tùy chỉnh với phương thức __getitem__ của bạn.

Có hay không bạn xem xét một trong các "pythonic" tùy thuộc vào bạn, nhưng hãy nhớ rằng trong khi Zen of Python lưu ý rằng "Các trường hợp đặc biệt không đủ đặc biệt để phá vỡ các quy tắc", thì ngay lập tức ghi chú rằng " thực tế đánh bại sự tinh khiết ". Trong trường hợp này, chỉ cần kiểm tra hai loại tài liệu nêu rõ là những điều duy nhất __getitem__ nên hỗ trợ có vẻ như là cách thực tế đối với tôi.

0

Tôi không biết cách để tránh làm việc đó sau khi. Đó chỉ là sự cân bằng của việc sử dụng ngôn ngữ được gõ động theo cách này. Tuy nhiên, điều đó không có nghĩa là bạn phải lặp đi lặp lại. Tôi sẽ giải quyết nó một lần bằng cách tạo ra một lớp trừu tượng với tách ra tên phương pháp, sau đó kế thừa từ lớp đó thay vì trực tiếp từ Sequence, như:

class UnannoyingSequence(collections.abc.Sequence): 

    def __getitem__(self, key): 
     if isinstance(key, int): 
      return self.getitem(key) 
     elif isinstance(key, slice): 
      return self.getslice(key) 
     else: 
      raise TypeError('Index must be int, not {}'.format(type(key).__name__)) 

    # default implementation in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

class FooSequence(UnannoyingSequence): 
    def getitem(self, key): 
     # Get a single item 

    # optional efficient, type-specific implementation not in terms of getitem 
    def getslice(self, key): 
     # Get a whole slice 

này dọn dẹp FooSequence đủ mà tôi thậm chí có thể làm theo cách này nếu Tôi chỉ có một lớp có nguồn gốc. Tôi ngạc nhiên vì thư viện chuẩn đã không hoạt động theo cách đó.

+1

Tất nhiên, ngôn ngữ * đã sử dụng * để làm điều này cho chúng tôi với '__getslice__', nhưng điều đó không được chấp nhận, điều này khiến tôi băn khoăn về tính chính xác của toàn bộ kỹ thuật. – Kevin

+0

@Kevin, nó không được chấp nhận vì các lý do khác nhau. Họ muốn tạo một đối tượng 'slice', nhưng' __getslice__' nhận các đối số 'i' và' j'. Nó sẽ có khả năng tương thích ngược. –

-1

Để duy trì pythonic, bạn đã làm việc với ngữ nghĩa hơn là loại đối tượng. Vì vậy, nếu bạn có một số tham số như accessor cho một chuỗi, chỉ cần sử dụng nó như thế. Sử dụng trừu tượng cho một tham số càng lâu càng tốt. Nếu bạn mong đợi một tập hợp số nhận dạng người dùng, đừng mong đợi một tập hợp, mà đúng hơn là một số cấu trúc dữ liệu có phương thức add. Nếu bạn mong đợi một số văn bản, đừng mong đợi đối tượng unicode, mà là một số vùng chứa cho các ký tự có các phương pháp encodedecode.

Tôi giả sử nói chung bạn muốn làm điều gì đó như "Sử dụng hành vi của việc triển khai cơ sở trừ khi một số giá trị đặc biệt được cung cấp. Nếu bạn muốn thực hiện __getitem__, bạn có thể sử dụng phân biệt chữ hoa chữ thường xảy ra nếu một giá trị đặc biệt . là cung cấp tôi muốn sử dụng mẫu sau:

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if key == SPECIAL_VALUE: 
       return SOMETHING_SPECIAL 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

Nếu bạn muốn phân biệt giữa một giá trị duy nhất (trong thuật ngữ perl "vô hướng") và một chuỗi (trong Java thuật ngữ "bộ sưu tập"), sau đó nó là bạn có thể sử dụng mẫu thử bắt hoặc hasattr như tôi làm bây giờ:

>>> a = 42 
>>> b = [1, 3, 5, 7] 
>>> c = slice(1, 42) 
>>> hasattr(a, "__iter__") 
False 
>>> hasattr(b, "__iter__") 
True 
>>> hasattr(c, "__iter__") 
False 
>>> 

Áp dụng cho ví dụ:

class FooSequence(collections.abc.Sequence): 
    # Snip other methods 

    def __getitem__(self, key): 
     try: 
      if hasattr(key, "__iter__"): 
       return map(lambda x: WHATEVER(x), key) 
      else: 
       return self.our_baseclass_instance[key] 
     except AttributeError: 
      raise TypeError('Wrong type: {}'.format(type(key).__name__)) 

ngôn ngữ lập trình động như gõ python và sử dụng ruby ​​vịt. Và một con vịt là một con vật, đi như một con vịt, bơi như một con vịt và quẳng như một con vịt. Không phải vì ai đó gọi nó là "vịt".

+2

Tôi đồng ý, nhưng tài liệu cho getitem tuyên bố rõ ràng "chỉ cho phép các đối tượng kiểu int và slice". Không có lý do nào để cho phép hoặc chấp nhận các vòng lặp vì lý do đơn giản là getitem không được phép chấp nhận các vòng lặp, và cho các mục đích của toán tử '[]', một danh sách sẽ không bao giờ được cung cấp. Đánh máy vịt là tất cả tốt và tốt, nhưng tài liệu là vô cùng rõ ràng về chính xác những gì các mặt hàng để chấp nhận. Xem [tài liệu python cho getitem] (https://docs.python.org/3.4/reference/datamodel.html#object.__getitem__) để biết thêm thông tin. –

+0

Tôi không có cơ sở triển khai. Tôi đang làm mọi thứ bằng tay. Mã đã được đơn giản hóa cho mục đích giải thích; mã thực tế của tôi là 3D-sliceable, không sử dụng NumPy, và phải làm một số tiền hợp lý của tiền xử lý chỉ mục mà nếu không sẽ khá mất tập trung. – Kevin

+0

Trong trường hợp đó, tôi xem xét câu trả lời của @ SevenDeadlySins là câu trả lời hữu ích nhất. – meisterluk

Các vấn đề liên quan