Python'da neden Soyut Temel Sınıflar kullanılıyor?


213

Python'da ördek yazmanın eski yollarına alışkın olduğum için ABC'ye (soyut temel sınıflar) olan ihtiyacı anlayamıyorum. Yardım bunların nasıl kullanılacağı üzerinde iyidir.

PEP'deki mantığı okumaya çalıştım , ama başımın üzerinden geçti. Değişebilir bir dizi kabı arıyordum, kontrol ettim __setitem__veya daha büyük olasılıkla ( EAFP ) kullanmayı denerdim . Sayılar modülü için gerçek yaşam kullanımı ile karşılaşmadım , bu da ABC'leri kullanıyor, ancak bu en yakın anlamak zorundayım.

Gerekçeyi bana açıklayan var mı, lütfen?

Yanıtlar:


162

Kısa versiyon

ABC'ler, müşteriler ve uygulanan sınıflar arasında daha yüksek düzeyde anlamsal sözleşme sunar.

Uzun versiyon

Bir sınıf ve arayanlar arasında bir sözleşme vardır. Sınıf belirli şeyler yapmaya ve belirli özelliklere sahip olmaya söz verir.

Sözleşmenin farklı seviyeleri vardır.

Çok düşük bir seviyede, sözleşme bir yöntemin adını veya parametre sayısını içerebilir.

Statik olarak yazılan bir dilde, bu sözleşme aslında derleyici tarafından uygulanacaktır. Python'da, bilinmeyen nesnenin beklenen bu sözleşmeyi karşıladığını doğrulamak için EAFP'yi kullanabilir veya introspection yazabilirsiniz .

Ancak sözleşmede daha üst düzey, anlamsal vaatler de var.

Örneğin, bir __str__()yöntem varsa , nesnenin dize olarak temsilini döndürmesi beklenir. Bu olabilir , nesnenin tüm içeriğini silmek hareketi tamamlamak ve yazıcıdan dışarı boş bir sayfa tükürmek ... ama Python kılavuzda açıklanan yapması gerektiğine dair ortak bir anlayış yoktur.

Bu, anlamsal sözleşmenin kılavuzda açıklandığı özel bir durumdur. Ne gereken print()yöntem do? Nesneyi bir yazıcıya mı yoksa ekrana bir satır mı yoksa başka bir şey mi yazmalıdır? Bu bağlıdır - burada tam sözleşmeyi anlamak için yorumları okumalısınız. Sadece print()yöntemin var olup olmadığını kontrol eden bir müşteri kodu , sözleşmenin bir kısmını doğruladı - bir yöntem çağrısı yapılabileceğini, ancak çağrının üst düzey anlamında anlaşmanın olmadığını doğruladı.

Soyut Temel Sınıf (ABC) tanımlamak, sınıf uygulayıcıları ve arayanlar arasında bir sözleşme üretmenin bir yoludur. Bu sadece yöntem adlarının bir listesi değil, aynı zamanda bu yöntemlerin ne yapması gerektiğine dair ortak bir anlayış. Bu ABC'den miras alırsanız, print()yöntemin semantiği de dahil olmak üzere yorumlarda açıklanan tüm kurallara uymayı vaat ediyorsunuz .

Python'un ördek yazmasının statik yazmaya göre esneklikte birçok avantajı vardır, ancak tüm sorunları çözmez. ABC'ler, Python'un serbest biçimi ile statik olarak yazılan bir dilin esaret ve disiplini arasında bir ara çözüm sunar.


12
Sanırım orada bir noktan var, ama seni takip edemem. Peki, sözleşme açısından, uygulayan __contains__bir sınıf ile miras kalan bir sınıf arasındaki fark collections.Containernedir? Örneğinizde, Python'da her zaman paylaşılan bir anlayış vardı __str__. Uygulama __str__bazı ABC'den miras almak ve sonra uygulamakla aynı vaatlerde bulunur __str__. Her iki durumda da sözleşmeyi bozabilirsiniz; statik yazımda olduğu gibi kanıtlanabilir bir anlambilim yoktur.
Muhammad Alkarouri

15
collections.Containerdejenere bir durumdur, sadece \_\_contains\_\_önceden tanımlı konvansiyonu içerir ve sadece bu konvansiyonu ifade eder. Bir ABC kullanmak tek başına fazla değer katmıyor, katılıyorum. Ben (örneğin) Setondan miras bırakmak için eklendi şüpheli . Ulaştığınız zaman Set, aniden ABC'ye ait olan önemli semantiklere sahiptir. Bir öğe koleksiyona iki kez ait olamaz. Bu yöntemlerin varlığıyla tespit edilemez.
Tuhaf düşünceler

3
Evet, bence Setbundan daha iyi bir örnek print(). Anlamı belirsiz olan bir yöntem adı bulmaya çalışıyordum ve sadece isimle söylenemedi, bu yüzden sadece ismiyle ve Python kılavuzuyla doğru şeyi yapacağından emin olamazdınız.
Oddthinking

3
Cevabı Setyerine örnek olarak yeniden yazma şansı var printmı? Setmantıklı geliyor, @Odthinking.
Ehtesh Choudhury

1
Bence bu makale çok iyi açıklıyor: dbader.org/blog/abstract-base-classes-in-python
szabgab

228

@ Oddthinking'in cevabı yanlış değil, ama bence Python'un ördek yazma dünyasında ABC'lere sahip olmasının gerçek , pratik nedenini kaçırıyor .

Soyut yöntemler temizdir, ancak bence zaten ördek yazmanın kapsamadığı herhangi bir kullanım durumunu gerçekten doldurmuyorlar. Soyut temel sınıfların gerçek gücü , isinstanceve davranışlarını özelleştirmenize izin verdiği şekildeissubclass yatar . ( __subclasshook__temel olarak Python __instancecheck__ve__subclasscheck__ kancaların üstünde daha dostça bir API'dir .) Dahili yapıları özel türler üzerinde çalışacak şekilde uyarlamak Python'un felsefesinin büyük bir parçasıdır.

Python'un kaynak kodu örnek niteliğindedir. İşte böyle collections.Container(yazma anda) standart kütüphanesinde tanımlanır:

class Container(metaclass=ABCMeta):
    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if any("__contains__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Bu tanım __subclasshook____contains__ özniteliği olan herhangi bir sınıfın, doğrudan alt sınıfı olmasa bile, bir Container alt sınıfı olarak kabul edildiğini belirtir. Yani şunu yazabilirim:

class ContainAllTheThings(object):
    def __contains__(self, item):
        return True

>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True

Diğer bir deyişle, doğru arayüzü uygularsanız, bir alt sınıfsınız! ABC'ler, ördek yazım ruhuna sadık kalırken, Python'daki arayüzleri tanımlamak için resmi bir yol sağlar. Ayrıca bu, Açık-Kapalı Prensibi'ni onurlandıracak şekilde çalışır .

Python'un nesne modeli, daha "geleneksel" bir OO sistemine (Java * demek istediğim) yüzeysel olarak benziyor - yer sınıflarımız, yer nesneleriniz, yer yöntemleriniz var - ancak yüzeyi çizdiğinizde çok daha zengin bir şey bulacaksınız ve daha esnek. Benzer şekilde, Python'un soyut temel sınıflar kavramı bir Java geliştiricisi tarafından tanınabilir, ancak pratikte çok farklı bir amaca yöneliktir.

Bazen kendimi tek bir eşyaya veya eşya koleksiyonuna etki edebilecek polimorfik fonksiyonlar yazarken buluyorum ve isinstance(x, collections.Iterable) ve hasattr(x, '__iter__')eşdeğer bir try...exceptbloktan çok daha okunabilir . (Python'u bilmiyorsanız, bu üçünden hangisi kodun amacını en açık hale getirir?)

Bununla birlikte, nadiren kendi ABC'mi yazmam gerektiğini buluyorum ve tipik olarak yeniden düzenleme yoluyla ihtiyacını keşfediyorum. Çok fazla öznitelik denetimi yapan bir polimorfik işlev veya aynı öznitelik denetimini yapan birçok işlev görürsem, bu koku, çıkarılmayı bekleyen bir ABC'nin varlığını gösterir.

* Java'nın "geleneksel" bir OO sistemi olup olmadığı tartışmasına girmeden ...


Zeyilname : soyut bir taban sınıfı davranışını geçersiz kılabilir olsa isinstanceve issubclasshala girmezse MRO sanal alt sınıfının. Bu, müşteriler için potansiyel bir tuzaktır: isinstance(x, MyABC) == Trueüzerinde tanımlanan yöntemlere sahip her nesne değil MyABC.

class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises AttributeError

Ne yazık ki bu "sadece bunu yapma" tuzaklarından biri (Python'un nispeten az olduğu!): ABC'leri hem a hem de __subclasshook__soyut olmayan yöntemlerle tanımlamaktan kaçının . Ayrıca, tanımınızı __subclasshook__ABC'nizin tanımladığı soyut yöntemlerle tutarlı hale getirmelisiniz .


21
"Eğer doğru arayüzü uygularsan, bir alt sınıftasın" Bunun için çok teşekkürler. Oddthinking'in kaçırıp kaçırmadığını bilmiyorum, ama kesinlikle yaptım. FWIW, isinstance(x, collections.Iterable)benim için daha net ve Python'u tanıyorum.
Muhammed Alkarouri

Mükemmel gönderi. Teşekkür ederim. Addendum, "sadece bunu yapmayın" tuzak, biraz normal alt sınıf miras yapmak gibi ama daha sonra Calt sınıf silme (veya onarım ötesinde vida) sahip gibi abc_method()olduğunu düşünüyorum MyABC. Temel fark, alt sınıfı değil miras sözleşmesini bozan süper sınıf olmasıdır.
Michael Scott Cuthbert

Container.register(ContainAllTheThings)Verilen örneğin çalışması için bir şey yapmanıza gerek kalmaz mı?
BoZenKhaa

1
@BoZenKhaa Yanıttaki kod çalışıyor! Dene! Anlamı __subclasshook__"bu yüklem amaçları için bir alt sınıf olarak kabul edilir tatmin herhangi sınıftır isinstanceve issubclass, çek olursa olsun ABC ile tescil edilmiş olsun ve ne olursa olsun doğrudan bir alt sınıf var olmadığı ". Cevabımda söylediğim gibi , doğru arayüzü uygularsanız, bir alt sınıfsınız!
Benjamin Hodgson

1
Biçimlendirmeyi italik yazılardan kalın yazı tipine değiştirmelisiniz. "Eğer doğru arayüzü uygularsan, bir alt sınıftasın!" Çok kısa bir açıklama. Teşekkür ederim!
marti

108

ABC'lerin kullanışlı bir özelliği, gerekli tüm yöntemleri (ve özellikleri) uygulamazsanız AttributeError, aslında eksik yöntemi kullanmaya çalıştığınızda, potansiyel olarak çok daha sonra değil, örnekleme üzerinde bir hata almanızdır .

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

Https://dbader.org/blog/abstract-base-classes-in-python adresinden bir örnek

Düzenleme: python3 sözdizimini dahil etmek için teşekkürler @PandasRocks


5
Örnek ve bağlantı çok yardımcı oldular. Teşekkürler!
nalyd88

Base ayrı bir dosyada tanımlanmışsa, bu dosyayı Base.Base olarak miras almalı veya içe aktarma satırını 'Base import Base'den' olarak değiştirmelisiniz
Ryan Tennill

Python3'te sözdiziminin biraz farklı olduğuna dikkat edilmelidir. Bu cevaba bakın: stackoverflow.com/questions/28688784/…
PandasRocks

7
Bir C # arka plandan geliyor, bu soyut sınıflar kullanmayı sebep. İşlevler sağlıyorsunuz, ancak işlevlerin daha fazla uygulama gerektirdiğini belirtiyorsunuz. Diğer cevaplar bu noktayı kaçırıyor gibi görünüyor.
Josh Noe

18

Bir nesnenin, protokoldeki tüm yöntemlerin varlığını kontrol etmek zorunda kalmadan veya desteklemediği için "düşman" bölgesinde derin bir istisna tetiklemeden belirli bir protokolü destekleyip desteklemediğini belirleme çok daha kolay olacaktır.


6

Abstract yöntemi, üst sınıfta çağırdığınız yöntemin alt sınıfta görünmesini sağlar. Özet noraml çağırma ve kullanma yolu aşağıdadır. Python3 ile yazılmış program

Normal arama şekli

class Parent:
def methodone(self):
    raise NotImplemented()

def methodtwo(self):
    raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

'methodone () adı'

c.methodtwo()

NotImplementedError

Abstract yöntemi ile

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

TypeError: Soyut yöntem Son'u soyut yöntemlerle başlatamazsınız.

Methodtwo alt sınıfta çağrılmadığından hata aldık. Doğru uygulama aşağıdadır

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

'methodone () adı'


1
Teşekkürler. Yukarıda cerberos cevabında verilen aynı nokta değil mi?
Muhammad Alkarouri
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.