2009-02-23 8 views
5

Trong Python, tôi đã thấy đề xuất sử dụng giữ hoặc gói để mở rộng chức năng của đối tượng hoặc lớp, thay vì thừa kế. Đặc biệt, tôi nghĩ rằng Alex Martelli đã nói về điều này trong bài nói chuyện Python Design Patterns của mình. Tôi đã thấy mẫu này được sử dụng trong các thư viện để tiêm phụ thuộc, như pycontainer.Đóng gói các đối tượng để mở rộng/thêm chức năng trong khi làm việc xung quanh isinstance

Một vấn đề mà tôi đã gặp phải là khi tôi phải giao diện với mã sử dụng isinstance anti-pattern, mẫu này không thành công vì đối tượng giữ/gói không thực hiện kiểm tra isinstance. Làm thế nào tôi có thể thiết lập đối tượng giữ/gói để kiểm tra loại không cần thiết? Điều này có thể được thực hiện chung? Theo một nghĩa nào đó, tôi cần một thứ gì đó cho các cá thể lớp tương tự với các trang trí chức năng bảo quản chữ ký (ví dụ: simple_decorator hoặc decorator của Michele Simionato).

Chứng chỉ: Tôi không khẳng định rằng tất cả việc sử dụng isinstance là không phù hợp; một số câu trả lời làm cho các điểm tốt về điều này. Điều đó nói rằng, cần phải nhận ra rằng việc sử dụng isinstance đặt ra những hạn chế đáng kể về tương tác đối tượng --- nó buộc thừa kế là nguồn đa hình, thay vì hành vi.

Dường như có sự nhầm lẫn về chính xác/tại sao đây là vấn đề, vì vậy hãy để tôi cung cấp một ví dụ đơn giản (được nâng cấp rộng rãi từ pycontainer). Giả sử chúng ta có một lớp Foo, cũng như một FooFactory. Vì lợi ích của ví dụ này, giả sử chúng ta muốn có thể khởi tạo các đối tượng Foo để ghi lại mọi cuộc gọi hàm, hoặc không nghĩ là AOP. Hơn nữa, chúng tôi muốn thực hiện điều này mà không sửa đổi lớp/nguồn Foo theo bất kỳ cách nào (ví dụ: chúng tôi có thể triển khai một nhà máy chung có thể thêm khả năng ghi nhật ký cho bất kỳ cá thể lớp nào đang chạy). Một đâm đầu tiên tại này có thể là:

class Foo(object): 
    def bar(): 
     print 'We\'re out of Red Leicester.' 

class LogWrapped(object): 
    def __init__(self, wrapped): 
     self.wrapped = wrapped 
    def __getattr__(self, name): 
     attr = getattr(self.wrapped, name) 
     if not callable(attr): 
      return attr 
     else: 
      def fun(*args, **kwargs): 
       print 'Calling ', name 
       attr(*args, **kwargs) 
       print 'Called ', name 
      return fun 

class FooFactory(object): 
    def get_foo(with_logging = False): 
     if not with_logging: 
      return Foo() 
     else: 
      return LogWrapped(Foo()) 

foo_fact = FooFactory() 
my_foo = foo_fact.get_foo(True) 
isinstance(my_foo, Foo) # False! 

Lý do có nhiều lý do tại sao bạn có thể muốn làm những việc chính xác theo cách này (sử dụng trang trí thay vào đó, vv), nhưng cần lưu ý:

  • Chúng tôi don không muốn chạm vào lớp Foo. Giả sử chúng tôi đang viết mã khung công tác có thể được sử dụng bởi khách hàng mà chúng tôi chưa biết.
  • Vấn đề là trả về một đối tượng chủ yếu là Foo, nhưng với chức năng bổ sung. Nó dường như là một Foo --- nhiều nhất có thể --- với bất kỳ mã khách hàng nào khác mong đợi một Foo. Do đó mong muốn làm việc xung quanh isinstance.
  • Có, tôi biết rằng tôi không cần lớp nhà máy (bảo vệ trước bản thân mình ở đây).
+0

Lắc nắm tay trong cơn giận dữ bất lực lúc đang cố gắng. Bạn không thể sửa mã với isinstance? http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/423857#423857 –

+0

Fury bất lực :) Trong một số trường hợp, tôi có quyền truy cập vào isinstance. Tuy nhiên, một trường hợp sử dụng khác sẽ muốn viết mã "khung" (ví dụ: người bảo vệ ở trên) có vai trò tốt đẹp với những người khác. – zweiterlinde

Trả lời

2

Nếu mã thư viện bạn phụ thuộc vào việc sử dụng isinstance và dựa vào thừa kế tại sao không đi theo tuyến đường này? Nếu bạn không thể thay đổi thư viện thì có lẽ tốt nhất là nên kiên trì với nó.

Tôi cũng nghĩ rằng có sử dụng hợp pháp cho isinstance và với việc giới thiệu abstract base classes trong 2.6, điều này đã được chính thức công nhận. Có những tình huống trong đó isinstance thực sự là giải pháp đúng, trái ngược với việc gõ vịt với hasattr hoặc sử dụng ngoại lệ.

Một số tùy chọn bẩn nếu vì một lý do nào bạn thực sự không muốn sử dụng thừa kế:

  • Bạn có thể thay đổi chỉ các trường hợp lớp bằng cách sử dụng phương pháp dụ. Với new.instancemethod bạn tạo các phương thức trình bao bọc cho cá thể của bạn, sau đó gọi phương thức gốc được định nghĩa trong lớp gốc. Điều này có vẻ là lựa chọn duy nhất không sửa đổi lớp gốc cũng như không định nghĩa các lớp mới.

Nếu bạn có thể sửa đổi các lớp trong thời gian chạy có rất nhiều lựa chọn:

  • Sử dụng một mixin chạy, ví dụ: chỉ cần thêm một lớp học để các __base__ thuộc tính của lớp học của bạn. Nhưng điều này được sử dụng nhiều hơn để thêm chức năng cụ thể, không phải cho gói bừa bãi mà bạn không biết cần phải bọc gì.

  • Các tùy chọn trong câu trả lời của Dave (trang trí lớp bằng Python> = 2.6 hoặc Metaclasses).

Chỉnh sửa: Ví dụ cụ thể của bạn, tôi đoán chỉ có tùy chọn đầu tiên hoạt động. Nhưng tôi vẫn sẽ xem xét việc thay thế việc tạo một LogFoo hoặc chọn một giải pháp hoàn toàn khác cho một cái gì đó cụ thể như ghi nhật ký.

0

Xung đầu tiên của tôi là cố gắng sửa mã vi phạm sử dụng isinstance. Nếu không, bạn chỉ cần truyền bá những sai lầm thiết kế của nó vào thiết kế của riêng bạn. Bất kỳ lý do bạn không thể sửa đổi nó?

Edit: Vì vậy, biện minh của bạn rằng bạn đang viết code khuôn khổ/thư viện mà bạn muốn mọi người có thể sử dụng trong mọi trường hợp, ngay cả khi họ muốn sử dụng isinstance?

Tôi nghĩ rằng có nhiều điều sai trái với điều này:

  • Bạn đang cố gắng hỗ trợ một mô hình bị hỏng
  • Em là người định nghĩa thư viện và giao diện của nó, nó tùy thuộc vào những người dùng sử dụng nó đúng.
  • Không có cách nào bạn có thể lường trước tất cả các lập trình xấu người sử dụng thư viện của bạn sẽ làm, vì vậy nó khá nhiều nỗ lực vô ích để cố gắng hỗ trợ thực hành lập trình xấu

Tôi nghĩ rằng bạn tốt nhất nên viết thành ngữ, cũng được thiết kế mã. Mã tốt (và mã xấu) có khuynh hướng lan rộng, do đó hãy biến bạn thành ví dụ. Hy vọng rằng nó sẽ dẫn đến sự gia tăng tổng thể về chất lượng mã. Đi theo một cách khác sẽ chỉ tiếp tục giảm chất lượng.

+0

Giả định là việc khôi phục luôn là một ý tưởng tồi. Có thời gian hợp lệ để sử dụng isinstance. –

1

Một điều cần lưu ý là bạn không nhất thiết phải sử dụng sử dụng mọi thứ trong một lớp cơ sở nếu bạn đi tuyến kế thừa. Bạn có thể tạo một lớp sơ khai để kế thừa từ đó không thêm bất kỳ triển khai cụ thể nào.Tôi đã thực hiện một số việc như thế này nhiều lần:

Nếu bạn cần thừa kế từ những thứ khác, tôi cho rằng bạn có thể gặp phải một số vấn đề với đa thừa kế, nhưng điều này sẽ khá tối thiểu nếu bạn không cung cấp bất kỳ triển khai cụ thể nào.

Một vấn đề mà tôi đã chạy vào là khi tôi phải giao tiếp với mã mà sử dụng isinstance chống mẫu

Tôi đồng ý rằng isinstance là để thể tránh được nếu có thể, nhưng tôi m không chắc chắn tôi sẽ gọi nó là một antipattern. Có một số lý do hợp lệ để sử dụng isinstance. Ví dụ, có một số khung công tác truyền thông điệp sử dụng điều này để định nghĩa các thông điệp. Ví dụ, nếu bạn nhận được một lớp kế thừa từ Shutdown, đó là thời gian cho một hệ thống con để tắt.

0

Nếu bạn đang viết một khung cần chấp nhận một số loại đầu vào từ người dùng API của mình, thì không có lý do nào tôi có thể nghĩ đến việc sử dụng isinstance. Xấu xí như nó có thể là, tôi luôn luôn chỉ cần kiểm tra để xem nếu nó thực sự cung cấp giao diện Ý tôi là để sử dụng:

def foo(bar): 
    if hasattr(bar, "baz") and hasattr(bar, "quux"): 
     twiddle(bar.baz, bar.quux()) 
    elif hasattr(bar, "quuux"): 
     etc... 

Và tôi cũng thường cung cấp một lớp tốt đẹp để kế thừa chức năng mặc định, nếu người dùng API muốn sử dụng nó:

class Bar: 
    def baz(self): 
     return self.quux("glu") 

    def quux(self): 
     raise NotImplemented