2009-07-19 30 views
40

Trong Python 2.x khi bạn muốn đánh dấu một phương pháp như trừu tượng, bạn có thể xác định nó như vậy:tương đương của NotImplementedError cho các lĩnh vực trong Python

class Base: 
    def foo(self): 
     raise NotImplementedError("Subclasses should implement this!") 

Sau đó, nếu bạn quên để ghi đè lên nó, bạn sẽ có được một ngoại lệ nhắc nhở tốt đẹp. Có cách nào tương đương để đánh dấu một trường là trừu tượng không? Hoặc là nói nó trong lớp docstring tất cả các bạn có thể làm gì?

Lúc đầu, tôi nghĩ rằng tôi có thể đặt trường thành NotImplemented, nhưng khi tôi tra cứu những gì nó thực sự cho (so sánh phong phú) nó có vẻ lạm dụng.

+0

Nó vẫn hoạt động, ngay cả khi mục đích ban đầu của nó là để so sánh chi tiết. Có gì sai với nó? –

+0

Vấn đề đầu tiên là bạn có thể đọc trường từ đối tượng (myvar = Base.field) và điều tiếp theo mà bạn biết là NotImplementeds ở khắp nơi cho đến khi một số phần khác cố gắng sử dụng nó và nhận được một AttributeError bí ẩn. – Kiv

+0

Vấn đề thứ hai là IMO nó cản trở khả năng đọc ("Đó là điều so sánh phong phú làm gì ở đó? Tôi đã bỏ lỡ điều gì đó?) Giải pháp của Evan thể hiện chính xác những gì đang diễn ra theo cách quen thuộc. – Kiv

Trả lời

42

Có, bạn có thể. Sử dụng trang trí @property. Ví dụ, nếu bạn có một trường gọi là "dụ", sau đó không có thể giúp bạn làm điều gì đó như thế này:

class Base(object): 

    @property 
    def example(self): 
     raise NotImplementedError("Subclasses should implement this!") 

Chạy sau đây tạo ra một NotImplementedError giống như bạn muốn.

b = Base() 
print b.example 
+2

Điều này đơn giản hơn, nhưng tôi thích cách phiên bản của tôi ném ngay lập tức và không chỉ nếu thuộc tính xảy ra được sử dụng –

+4

Nhưng Glenn, nếu thuộc tính 'ví dụ' được đặt ở một điểm nào khác? Nếu nó ném nó ngay lập tức, thì nó có thể không bao giờ có cơ hội được đặt qua các phương tiện khác. Hãy nhớ rằng các trường và phương thức có thể được đặt thành một lớp bất kỳ lúc nào và không chỉ khi lớp được xác định. –

+0

Ah, đây là những gì tôi đã sau. – Kiv

1
def require_abstract_fields(obj, cls): 
    abstract_fields = getattr(cls, "abstract_fields", None) 
    if abstract_fields is None: 
     return 

    for field in abstract_fields: 
     if not hasattr(obj, field): 
      raise RuntimeError, "object %s failed to define %s" % (obj, field) 

class a(object): 
    abstract_fields = ("x",) 
    def __init__(self): 
     require_abstract_fields(self, a) 

class b(a): 
    abstract_fields = ("y",) 
    x = 5 
    def __init__(self): 
     require_abstract_fields(self, b) 
     super(b, self).__init__() 

b() 
a() 

Lưu ý việc thông qua các loại hình lớp thành require_abstract_fields, vì vậy nếu nhiều lớp kế thừa sử dụng này, họ không phải tất cả các lĩnh vực xác nhất có nguồn gốc từ tầng lớp nhân. Bạn có thể tự động hóa điều này với một chiếc metaclass, nhưng tôi đã không đào sâu vào đó. Xác định một trường thành Không được chấp nhận.

28

câu trả lời khác:

@property 
def NotImplementedField(self): 
    raise NotImplementedError 

class a(object): 
    x = NotImplementedField 

class b(a): 
    # x = 5 
    pass 

b().x 
a().x 

này giống như Evan, nhưng ngắn gọn và rẻ tiền - vì bạn sẽ chỉ nhận được một trường hợp duy nhất của NotImplementedField.

+3

Clever, Glenn. :) Nhược điểm duy nhất tôi có thể thấy là bạn không thể chỉ định các thông điệp khác nhau được hiển thị khi NotImplementedError bị ném. –

+1

Bạn có thể định nghĩa NotImplementedField làm chức năng lấy thông báo để hiển thị. Bạn sẽ phải có một chút thông minh để giữ nó bằng cách sử dụng một ví dụ duy nhất của hàm khi không có thông điệp được đính kèm - cache một singleton không có thông điệp - nhưng đó là về nó. –

+1

Có lẽ nhỏ, nhưng tôi không phải là một fan hâm mộ của phương pháp này bởi vì nó hạn chế khả năng của bạn để thiết lập một thông điệp cho trường hợp ngoại lệ. Bằng cách sử dụng @property, bạn có thể đặt thông báo thành bất kỳ điều gì bạn muốn. – umbrae

0

Và đây là giải pháp của tôi:

def not_implemented_method(func): 
    from functools import wraps 
    from inspect import getargspec, formatargspec 

    @wraps(func) 
    def wrapper(self, *args, **kwargs): 
     c = self.__class__.__name__ 
     m = func.__name__ 
     a = formatargspec(*getargspec(func)) 
     raise NotImplementedError('\'%s\' object does not implement the method \'%s%s\'' % (c, m, a)) 

    return wrapper 


def not_implemented_property(func): 
    from functools import wraps 
    from inspect import getargspec, formatargspec 

    @wraps(func) 
    def wrapper(self, *args, **kwargs): 
     c = self.__class__.__name__ 
     m = func.__name__ 
     raise NotImplementedError('\'%s\' object does not implement the property \'%s\'' % (c, m)) 

    return property(wrapper, wrapper, wrapper) 

Nó có thể được sử dụng như

class AbstractBase(object): 
    @not_implemented_method 
    def test(self): 
     pass 

    @not_implemented_property 
    def value(self): 
     pass 

class Implementation(AbstractBase): 
    value = None 

    def __init__(self): 
     self.value = 42 

    def test(self): 
     return True 
0

Một cách tốt hơn để làm điều này là sử dụng Abstract Base Classes:

import abc 

class Foo(abc.ABC): 

    @property 
    @abc.abstractmethod 
    def demo_attribute(self): 
     raise NotImplementedError 

    @abc.abstractmethod 
    def demo_method(self): 
     raise NotImplementedError 

class BadBar(Foo): 
    pass 

class GoodBar(Foo): 

    demo_attribute = 'yes' 

    def demo_method(self): 
     return self.demo_attribute 

bad_bar = BadBar() 
# TypeError: Can't instantiate abstract class BadBar \ 
# with abstract methods demo_attribute, demo_method 

good_bar = GoodBar() 
# OK 

Lưu ý rằng bạn nên vẫn có raise NotImplementedError thay vì một cái gì đó như pass, bởi vì không có gì ngăn cản lớp kế thừa gọi super().demo_method() và nếu trừu tượng demo_method chỉ là pass, điều này sẽ không âm thầm.

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