Różnica między klasą abstrakcyjną a interfejsem w Pythonie

Odpowiedzi:

618

To, co czasem zobaczysz, jest następujące:

class Abstract1( object ):
    """Some description that tells you it's abstract,
    often listing the methods you're expected to supply."""
    def aMethod( self ):
        raise NotImplementedError( "Should have implemented this" )

Ponieważ Python nie ma (i nie potrzebuje) formalnej umowy interfejsu, nie ma w tym języku rozróżnienia między abstrakcją a interfejsem. Jeśli ktoś przejdzie wysiłek zdefiniowania formalnego interfejsu, będzie to również klasa abstrakcyjna. Jedynymi różnicami byłyby określone zamiary w dokumentacji.

Różnica między abstrakcją a interfejsem polega na rozczesywaniu włosów, gdy piszesz kaczkę.

Java używa interfejsów, ponieważ nie ma wielokrotnego dziedziczenia.

Ponieważ Python ma wiele elementów dziedziczenia, możesz również zobaczyć coś takiego

class SomeAbstraction( object ):
    pass # lots of stuff - but missing something

class Mixin1( object ):
    def something( self ):
        pass # one implementation

class Mixin2( object ):
    def something( self ):
        pass # another

class Concrete1( SomeAbstraction, Mixin1 ):
    pass

class Concrete2( SomeAbstraction, Mixin2 ):
    pass

Wykorzystuje to rodzaj abstrakcyjnej nadklasy z mixinami, aby utworzyć konkretne podklasy, które są rozłączne.

S.Lott
źródło
5
S. Lott, czy masz na myśli to, że z powodu wpisywania kaczki rozróżnienie między has-a (interfejs) a is-a (dziedziczenie) nie jest znaczące?
Lorenzo,
3
Różnica między streszczeniem a interfejsem powoduje rozczesywanie włosów, gdy piszesz kaczkę. Nie wiem, co znaczy „znaczący”. To „prawdziwe” - ma treść - z perspektywy projektowania. Ale z perspektywy językowej może nie być wsparcia. Można zastosować konwencje rozróżniające klasę abstrakcyjną od definicji klasy interfejsu w Pythonie.
S.Lott
26
@ L.DeLeo - czy jesteś pewien, że twoje pojęcie has-a vs. is-a jest prawidłowe? Zasadniczo uważam to rozróżnienie za zmienne has-a = członek vs. is-a = dziedziczenie (klasa nadrzędna lub interfejs). Pomyśl o porównywalnej lub liście w Javie; są to relacje, niezależnie od tego, czy są to interfejsy czy abstrakcyjne klasy.
dimo414
43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))jest bardziej informacyjny komunikat o błędzie :)
naught101
9
@Lorenzo relacja has-a nie ma nic wspólnego z dziedziczeniem, pisaniem kaczek, interfejsami i klasami abstrakcyjnymi (wszystkie cztery odnoszą się do relacji is-a).
Karl Richter,
196

Jaka jest różnica między klasą abstrakcyjną a interfejsem w Pythonie?

Interfejs dla obiektu to zestaw metod i atrybutów tego obiektu.

W Pythonie możemy użyć abstrakcyjnej klasy bazowej do zdefiniowania i wymuszenia interfejsu.

Korzystanie z abstrakcyjnej klasy bazowej

Powiedzmy, że chcemy użyć jednej z abstrakcyjnych klas podstawowych z collectionsmodułu:

import collections
class MySet(collections.Set):
    pass

Jeśli spróbujemy go użyć, otrzymamy, TypeErrorponieważ utworzona przez nas klasa nie obsługuje oczekiwanego zachowania zestawów:

>>> MySet()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MySet with abstract methods
__contains__, __iter__, __len__

Więc jesteśmy zobowiązani do wdrożenia w najmniej __contains__ , __iter__i __len__. Skorzystajmy z tego przykładu implementacji z dokumentacji :

class ListBasedSet(collections.Set):
    """Alternate set implementation favoring space over speed
    and not requiring the set elements to be hashable. 
    """
    def __init__(self, iterable):
        self.elements = lst = []
        for value in iterable:
            if value not in lst:
                lst.append(value)
    def __iter__(self):
        return iter(self.elements)
    def __contains__(self, value):
        return value in self.elements
    def __len__(self):
        return len(self.elements)

s1 = ListBasedSet('abcdef')
s2 = ListBasedSet('defghi')
overlap = s1 & s2

Implementacja: Tworzenie abstrakcyjnej klasy bazowej

Możemy stworzyć własną Abstrakcyjną Klasę Podstawową, ustawiając metaklasę abc.ABCMetai używając abc.abstractmethoddekoratora na odpowiednich metodach. Metaklasa zostanie dodana udekorowane funkcje do __abstractmethods__atrybutu, zapobiegając tworzeniu wystąpień, dopóki nie zostaną zdefiniowane.

import abc

Na przykład „efektywny” jest definiowany jako coś, co można wyrazić słowami. Powiedzmy, że chcieliśmy zdefiniować abstrakcyjną klasę bazową, która jest efektywna, w Pythonie 2:

class Effable(object):
    __metaclass__ = abc.ABCMeta
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Lub w Pythonie 3, z niewielką zmianą deklaracji metaklasy:

class Effable(object, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __str__(self):
        raise NotImplementedError('users must define __str__ to use this base class')

Teraz, jeśli spróbujemy stworzyć efektowny obiekt bez implementacji interfejsu:

class MyEffable(Effable): 
    pass

i spróbuj utworzyć go:

>>> MyEffable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyEffable with abstract methods __str__

Mówi się nam, że nie zakończyliśmy pracy.

Teraz, jeśli zastosujemy się do tego, zapewniając oczekiwany interfejs:

class MyEffable(Effable): 
    def __str__(self):
        return 'expressable!'

jesteśmy w stanie użyć konkretnej wersji klasy pochodzącej z abstrakcyjnej:

>>> me = MyEffable()
>>> print(me)
expressable!

Są inne rzeczy, które moglibyśmy z tym zrobić, np. Zarejestrować wirtualne podklasy, które już implementują te interfejsy, ale myślę, że to wykracza poza zakres tego pytania. Inne metody przedstawione tutaj musiałyby dostosować tę metodę za pomocąabc modułu.

Wniosek

Wykazaliśmy, że utworzenie abstrakcyjnej klasy bazowej definiuje interfejsy dla obiektów niestandardowych w języku Python.

Aaron Hall
źródło
101

Python> = 2.6 ma abstrakcyjne klasy podstawowe .

Abstrakcyjne klasy podstawowe (w skrócie ABC) uzupełniają pisanie kaczek, zapewniając sposób definiowania interfejsów, gdy inne techniki, takie jak hasattr (), byłyby niezdarne. Python zawiera wiele wbudowanych ABC dla struktur danych (w module kolekcji), liczb (w module liczb) i strumieni (w module io). Możesz stworzyć własne ABC za pomocą modułu abc.

Istnieje również moduł interfejsu Zope , który jest używany w projektach poza Zope , takich jak skręcone. Nie jestem do końca zaznajomiony z tym, ale tutaj jest strona wiki , która może pomóc.

Ogólnie rzecz biorąc, nie potrzebujesz koncepcji klas abstrakcyjnych ani interfejsów w Pythonie (edytowane - patrz odpowiedź S.Lott, aby uzyskać szczegółowe informacje).

JimB
źródło
2
Co zyskujesz, używając ABC w Pythonie?
CpILL
38

Python tak naprawdę nie ma żadnej koncepcji.

Używa pisania kaczego, co eliminuje potrzebę interfejsów (przynajmniej dla komputera :-))

Python <= 2.5: Klasy podstawowe oczywiście istnieją, ale nie ma wyraźnego sposobu oznaczenia metody jako „czystej wirtualnej”, więc klasa nie jest tak naprawdę abstrakcyjna.

Python> = 2.6: Istnieją abstrakcyjne klasy podstawowe ( http://docs.python.org/library/abc.html ). I pozwalają określić metody, które muszą być zaimplementowane w podklasach. Nie podoba mi się składnia, ale funkcja jest dostępna. W większości przypadków prawdopodobnie lepiej jest pisać na kaczych stronach po stronie klienta.

Douglas Leeder
źródło
3
Python 3.0 dodaje prawdziwe abstrakcyjne klasy podstawowe. Są one używane w module kolekcji, a także w innych miejscach. docs.python.org/3.0/library/abc.html
Lara Dougan
Przydałoby się odniesienie do tego, dlaczego pisanie kaczką eliminuje potrzebę interfejsów. Nie wydaje mi się oczywiste, że pisanie kaczek, które rozumiem jako umiejętność „szturchania” dowolną metodę lub atrybut dowolnego obiektu, oznaczałoby, że nie musisz określać wymaganych zachowań (i poprosić kompilatora o przypomnienie do ich wdrożenia), czyli w jaki sposób rozumiem abstrakcyjne klasy podstawowe.
Reb.Cabin
To mniej pisania na kaczkę niż obsługa wielokrotnego dziedziczenia, która usuwa sztuczną linię między interfejsem a klasą abstrakcyjną, którą narysowała np. Java.
masi
35

W bardziej prosty sposób wyjaśnić: Interfejs przypomina rodzaj pustej mufinki. Jest to plik klasy z zestawem definicji metod, które nie mają kodu.

Klasa abstrakcyjna to to samo, ale nie wszystkie funkcje muszą być puste. Niektóre mogą mieć kod. Nie jest ściśle pusty.

Po co różnicować: Nie ma praktycznej różnicy w Pythonie, ale na poziomie planowania dużego projektu może być bardziej powszechne mówienie o interfejsach, ponieważ nie ma kodu. Zwłaszcza jeśli pracujesz z programistami Java, którzy są przyzwyczajeni do tego terminu.

SilentSteel
źródło
+1 za wyróżnienie, w którym ABC może mieć własne implementacje - co wydaje się bardzo fajnym sposobem na przechytrzenie samego siebie
Titou
17

Zasadniczo interfejsy są używane tylko w językach, które używają modelu klasy z pojedynczym dziedziczeniem. W tych językach z pojedynczym dziedzictwem interfejsy są zwykle używane, jeśli jakakolwiek klasa może użyć określonej metody lub zestawu metod. Również w tych językach z dziedziczeniem pojedynczym klasy abstrakcyjne mają albo zdefiniowane zmienne klas oprócz żadnej lub więcej metod, albo wykorzystują model pojedynczego dziedziczenia do ograniczenia zakresu klas, które mogłyby korzystać z zestawu metod.

Języki obsługujące model wielokrotnego dziedziczenia zwykle używają tylko klas lub abstrakcyjnych klas bazowych, a nie interfejsów. Ponieważ Python obsługuje wielokrotne dziedziczenie, nie używa interfejsów i chciałbyś użyć klas podstawowych lub abstrakcyjnych klas podstawowych.

http://docs.python.org/library/abc.html

Lara Dougan
źródło
2

Klasy abstrakcyjne to klasy zawierające jedną lub więcej metod abstrakcyjnych. Oprócz metod abstrakcyjnych klasy abstrakcyjne mogą mieć metody statyczne, klasy i instancji. Ale w przypadku interfejsu będzie miał tylko abstrakcyjne metody, a nie inne. Dlatego dziedziczenie klasy abstrakcyjnej nie jest obowiązkowe, ale obowiązkowe jest dziedziczenie interfejsu.

Akash Ukarande
źródło
1

Dla kompletności powinniśmy wspomnieć o PEP3119, gdzie ABC zostało wprowadzone i porównane z interfejsami oraz oryginalny komentarz Talina .

Klasa abstrakcyjna nie jest doskonałym interfejsem:

  • należy do hierarchii dziedziczenia
  • jest zmienny

Ale jeśli pomyślisz o napisaniu go po swojemu:

def some_function(self):
     raise NotImplementedError()

interface = type(
    'your_interface', (object,),
    {'extra_func': some_function,
     '__slots__': ['extra_func', ...]
     ...
     '__instancecheck__': your_instance_checker,
     '__subclasscheck__': your_subclass_checker
     ...
    }
)

ok, rather as a class
or as a metaclass
and fighting with python to achieve the immutable object
and doing refactoring
...

dość szybko zdasz sobie sprawę, że wymyślasz koło, by ostatecznie osiągnąć abc.ABCMeta

abc.ABCMeta został zaproponowany jako użyteczny dodatek do brakującej funkcjonalności interfejsu, co jest dość sprawiedliwe w języku takim jak python.

Z pewnością można go było ulepszyć podczas pisania wersji 3 oraz dodając nową składnię i niezmienną koncepcję interfejsu ...

Wniosek:

The abc.ABCMeta IS "pythonic" interface in python
Sławomir Lenart
źródło