2009-07-19 13 views
40

w Pythonie 2.x, gdy chcesz oznaczyć metodę jako abstrakcyjne, można określić ją tak:Odpowiednik NotImplementedError na polach w Pythonie

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

Następnie, jeśli zapomni się go zastąpić, dostaniesz miły wyjątek przypomnienia. Czy istnieje równoważny sposób oznaczania pola jako abstrakcyjnego? Czy jest to stwierdzenie w klasie docstring, co możesz zrobić?

Początkowo myślałem, że mogę ustawić pole Nieemplementowane, ale kiedy spojrzałem w górę, do czego służy (porównanie bogate), wydawało się to nadużyciem.

+0

Nadal działa, nawet jeśli pierwotnym zamiarem było bogate porównywanie. Co jest z tym nie tak? –

+0

Pierwszy problem polega na tym, że możesz odczytać pole z obiektu (myvar = Base.field), a następnie dowiedzieć się, że nie są one wdrażane w dowolnym miejscu, dopóki inna część nie spróbuje go użyć i otrzyma tajemniczy AttributeError. – Kiv

+0

Drugi problem polega na tym, że IMO utrudnia czytelność ("Co tam robi to porównanie? Czy coś mi umknęło?) Rozwiązanie Evan dokładnie wyjaśnia, co się dzieje w znajomym stylu. – Kiv

Odpowiedz

42

Tak, możesz. Użyj dekoratora @property. Na przykład, jeśli masz pole o nazwie „przykład”, a następnie nie można zrobić coś takiego:

class Base(object): 

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

uruchamiając następujące produkuje NotImplementedError tak jak chcesz.

b = Base() 
print b.example 
+2

To jest prostsze, ale podoba mi się, jak moja wersja natychmiast rzuca i nie tylko wtedy, gdy atrybut zostanie użyty: –

+4

Ale Glenn, a jeśli właściwość 'example' zostanie ustawiona w innym punkcie? Jeśli natychmiast ją wyrzuci, może nigdy nie uzyskać szansy na ustawienie innymi metodami. Pamiętaj, że pola i metody można ustawić w dowolnej klasie, a nie tylko wtedy, gdy klasa jest zdefiniowana. –

+0

Ach, tego właśnie szukałem. – 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() 

Uwaga przemijanie typu klasy w require_abstract_fields, więc jeśli kilka odziedziczone klasy korzystać z tego, że nie wszystko walidacji pól najbardziej pochodzący swojej klasie jest. Możesz zautomatyzować to za pomocą metaklasu, ale nie zagłębiłem się w to. Definiowanie pola na Brak jest akceptowane.

28

odpowiedź Alternatywny:

@property 
def NotImplementedField(self): 
    raise NotImplementedError 

class a(object): 
    x = NotImplementedField 

class b(a): 
    # x = 5 
    pass 

b().x 
a().x 

To jak Evan, ale zwięzły i tanio - you'll dostać tylko jedno wystąpienie NotImplementedField.

+3

Sprytny, Glenn. :) Jedyną wadą, jaką widzę, jest to, że nie można określić różnych komunikatów, które będą wyświetlane, gdy zostanie zgłoszony NotImplementedError. –

+1

Można zdefiniować NotImplementedField jako funkcję, która wyświetla komunikat. Trzeba by było trochę sprytny, aby zachować go za pomocą pojedynczego wystąpienia funkcji, gdy nie jest dołączona wiadomość - cache pojedyncza dla żadnej wiadomości - ale to wszystko. –

+1

Być może niewielki, ale nie jestem tak wielkim fanem tego podejścia, ponieważ ogranicza możliwość ustawiania komunikatu dla wyjątku. Korzystając z @property, możesz ustawić wiadomość tak, jak chcesz. – umbrae

0

I tu jest moje rozwiązanie:

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) 

Może być używany jako

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

lepszy sposób to zrobić jest użycie 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 

pamiętać, że powinien nadal masz raise NotImplementedError zamiast czegoś podobnego do pass, ponieważ nic nie stoi na przeszkodzie, aby klasa dziedzicząca dzwoniła pod numer super().demo_method(), a jeśli abstrakcyjne demo_method jest tylko pass, nie powiedzie się to po cichu.

Powiązane problemy