ความแตกต่างระหว่างคลาสนามธรรมและอินเตอร์เฟสใน Python


คำตอบ:


618

สิ่งที่คุณจะเห็นบางครั้งมีดังต่อไปนี้:

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" )

เนื่องจาก Python ไม่มีสัญญาอินเทอร์เฟซที่เป็นทางการ (และไม่จำเป็น) การแบ่งแยกสไตล์ Java ระหว่างนามธรรมและอินเตอร์เฟสจึงไม่มีอยู่ ถ้ามีคนพยายามกำหนดอินเทอร์เฟซที่เป็นทางการมันจะเป็นคลาสนามธรรม ความแตกต่างจะอยู่ในเจตนาที่ระบุไว้ใน docstring

และความแตกต่างระหว่างนามธรรมและอินเทอร์เฟซคือสิ่งที่น่าทึ่งเมื่อคุณพิมพ์เป็ด

Java ใช้ส่วนต่อประสานเนื่องจากไม่มีหลายมรดก

เนื่องจาก Python มีหลายการสืบทอดคุณอาจเห็นบางสิ่งเช่นนี้

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

สิ่งนี้ใช้ซุปเปอร์คลาสนามธรรมชนิดหนึ่งพร้อมมิกซ์อินเพื่อสร้างคลาสย่อยคอนกรีตที่แยกจากกัน


5
S. Lott คุณหมายความว่าเพราะเป็ดพิมพ์ความแตกต่างระหว่าง has-a (อินเตอร์เฟส) และ is-a (การสืบทอด) ไม่มาก?
Lorenzo

3
ความแตกต่างระหว่างนามธรรมและอินเตอร์เฟสเป็นสิ่งที่น่าทึ่งเมื่อคุณพิมพ์เป็ด ฉันไม่รู้ว่า "สำคัญ" หมายถึงอะไร มันคือ "ของจริง" - มันมีสาระสำคัญ - จากมุมมองของการออกแบบ แต่จากมุมมองของภาษาอาจไม่มีการสนับสนุน คุณสามารถใช้ระเบียบแบบแผนเพื่อแยกความแตกต่างระหว่างคลาสนามธรรมและนิยามคลาสอินเตอร์เฟสใน Python
S.Lott

26
@ L.DeLeo - คุณแน่ใจหรือไม่ว่าแนวคิดของ has-a vs. is-a นั้นถูกต้อง? ฉันมักจะดูความแตกต่างเป็น has-a = ตัวแปรสมาชิกเทียบกับ is-a = การสืบทอด (ระดับแม่หรืออินเตอร์เฟซ) คิดเปรียบเทียบหรือรายการใน Java; สิ่งเหล่านี้คือความสัมพันธ์โดยไม่คำนึงถึงว่าพวกเขากำลังอินเทอร์เฟซหรือคลาสนามธรรม
dimo414

43
NotImplementedError("Class %s doesn't implement aMethod()" % (self.__class__.__name__))เป็นข้อผิดพลาดข้อมูลเพิ่มเติม :)
naught101

9
@ Lorenzo a ความสัมพันธ์แบบ a-a ไม่มีส่วนเกี่ยวข้องกับการสืบทอดการพิมพ์เป็ดอินเตอร์เฟสและคลาสนามธรรม (ทั้งสี่อ้างถึงความสัมพันธ์แบบ is-a)
Karl Richter

197

คลาสนามธรรมและอินเตอร์เฟสใน Python แตกต่างกันอย่างไร

อินเทอร์เฟซสำหรับวัตถุคือชุดของวิธีการและคุณลักษณะบนวัตถุนั้น

ใน Python เราสามารถใช้คลาสฐานนามธรรมเพื่อกำหนดและบังคับใช้อินเตอร์เฟส

ใช้คลาสฐานบทคัดย่อ

ตัวอย่างเช่นสมมติว่าเราต้องการใช้คลาสฐานนามธรรมอย่างใดอย่างหนึ่งจากcollectionsโมดูล:

import collections
class MySet(collections.Set):
    pass

ถ้าเราพยายามใช้มันเราจะได้รับTypeErrorเพราะคลาสที่เราสร้างไม่รองรับพฤติกรรมที่คาดหวังของเซต:

>>> 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__

ดังนั้นเราจึงจำเป็นต้องมีการดำเนินการที่น้อย __contains__ , และ__iter__ __len__ลองใช้ตัวอย่างการนำไปปฏิบัตินี้จากเอกสารประกอบ :

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

การใช้งาน: การสร้างคลาสฐานบทคัดย่อ

เราสามารถสร้างชั้นฐานนามธรรมของเราเองโดยการตั้งค่า metaclass เป็นabc.ABCMetaและใช้abc.abstractmethodมัณฑนากรในวิธีการที่เกี่ยวข้อง เมตาคลาสจะเพิ่มฟังก์ชั่นการตกแต่งให้กับแอ__abstractmethods__ททริบิวเพื่อป้องกันการสร้างอินสแตนซ์จนกว่าจะมีการกำหนด

import abc

ตัวอย่างเช่น "effable" หมายถึงสิ่งที่สามารถแสดงเป็นคำได้ สมมติว่าเราต้องการนิยามคลาสพื้นฐานที่เป็นนามธรรมซึ่งสามารถใช้งานได้ใน Python 2:

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

หรือใน Python 3 ที่มีการเปลี่ยนแปลงเล็กน้อยในการประกาศ metaclass:

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

ตอนนี้ถ้าเราพยายามที่จะสร้างวัตถุที่ไม่มีผลได้โดยไม่ต้องใช้อินเตอร์เฟส:

class MyEffable(Effable): 
    pass

และพยายามยกตัวอย่าง:

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

เราได้รับแจ้งว่าเรายังทำงานไม่เสร็จ

ตอนนี้ถ้าเราปฏิบัติตามโดยให้อินเทอร์เฟซที่คาดไว้:

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

จากนั้นเราสามารถใช้คลาสที่เป็นรูปธรรมของคลาสที่ได้มาจากนามธรรมหนึ่ง:

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

มีสิ่งอื่น ๆ ที่เราสามารถทำได้เช่นนี้ลงทะเบียน subclasses เสมือนที่ใช้อินเตอร์เฟซเหล่านี้แล้ว แต่ฉันคิดว่ามันอยู่นอกเหนือขอบเขตของคำถามนี้ อย่างไรก็ตามวิธีการอื่นที่แสดงที่นี่จะต้องปรับวิธีนี้โดยใช้abcโมดูลเพื่อทำเช่นนั้น

ข้อสรุป

เราได้แสดงให้เห็นว่าการสร้าง Abstract Base Class จะกำหนดอินเตอร์เฟสสำหรับวัตถุที่กำหนดเองใน Python


101

งูหลาม> = 2.6 มีการเรียนบทคัดย่อฐาน

บทคัดย่อ Base Classes (ABCs แบบย่อ) เติมเต็มการพิมพ์เป็ดโดยให้วิธีการกำหนดส่วนต่อประสานเมื่อเทคนิคอื่น ๆ เช่น Hasattr () น่าจะซุ่มซ่าม Python มาพร้อมกับ ABCs ในตัวจำนวนมากสำหรับโครงสร้างข้อมูล (ในโมดูลการรวบรวม), ตัวเลข (ในโมดูลตัวเลข) และกระแสข้อมูล (ในโมดูล io) คุณสามารถสร้าง ABC ของคุณเองด้วยโมดูล abc

นอกจากนี้ยังมีโมดูลZope Interfaceซึ่งใช้โดยโครงการที่อยู่นอก zope เช่น twisted ฉันไม่คุ้นเคยกับมันจริงๆ แต่มีหน้าวิกิที่นี่ที่อาจช่วยได้

โดยทั่วไปคุณไม่จำเป็นต้องมีแนวคิดของคลาสนามธรรมหรืออินเทอร์เฟซใน python (แก้ไข - ดูคำตอบของ S.Lott สำหรับรายละเอียด)


2
คุณจะได้อะไรจากการใช้ ABCs ใน Python
CpILL

38

Python ไม่มีแนวคิดใดแนวคิดหนึ่ง

มันใช้การพิมพ์เป็ดซึ่งลบความจำเป็นในการเชื่อมต่อ (อย่างน้อยสำหรับคอมพิวเตอร์ :-))

Python <= 2.5: คลาสพื้นฐานมีอยู่จริง แต่ไม่มีวิธีที่ชัดเจนในการทำเครื่องหมายวิธีเป็น 'pure virtual' ดังนั้นคลาสจึงไม่เป็นนามธรรม

งูหลาม> = 2.6: บทคัดย่อฐานเรียนทำที่มีอยู่ ( http://docs.python.org/library/abc.html ) และอนุญาตให้คุณระบุวิธีการที่ต้องนำไปใช้ในคลาสย่อย ฉันไม่ชอบไวยากรณ์มาก แต่มีคุณลักษณะอยู่ที่นั่น เวลาส่วนใหญ่น่าจะดีกว่าถ้าใช้การพิมพ์เป็ดจากฝั่งไคลเอ็นต์ 'ใช้'


3
Python 3.0 เพิ่มคลาสพื้นฐานจริง ๆ พวกเขาจะใช้ในโมดูลคอลเลกชันเช่นเดียวกับสถานที่อื่น ๆ docs.python.org/3.0/library/abc.html
Lara Dougan

การอ้างอิงถึงสาเหตุที่พิมพ์เป็ดไม่จำเป็นต้องใช้อินเทอร์เฟซจะมีประโยชน์ ดูเหมือนว่าฉันจะพิมพ์เป็ดไม่ได้ซึ่งฉันเข้าใจว่าเป็นความสามารถในการ "กระตุ้น" วิธีการหรือคุณลักษณะใด ๆ บนวัตถุใด ๆ หมายความว่าคุณไม่จำเป็นต้องระบุพฤติกรรมที่ต้องการ (และทำให้คอมไพเลอร์เตือนคุณ เพื่อนำไปใช้งาน) ซึ่งเป็นวิธีที่ฉันเข้าใจคลาสฐานของบทคัดย่อ
Reb.Cabin

มันน้อยกว่าการพิมพ์ของเป็ดจากนั้นรองรับการสืบทอดหลายอย่างที่ลบเส้นเทียมระหว่างอินเตอร์เฟสและคลาสนามธรรมที่เช่น Java ได้วาดขึ้น
masi

35

ด้วยวิธีการขั้นพื้นฐานเพิ่มเติมที่จะอธิบาย: อินเทอร์เฟซนั้นคล้ายกับมัฟฟินแพนเปล่า มันเป็นไฟล์คลาสที่มีชุดของคำจำกัดความวิธีการที่ไม่มีรหัส

คลาสนามธรรมเป็นสิ่งเดียวกัน แต่ไม่ใช่ทุกฟังก์ชันที่จะต้องว่างเปล่า บางคนสามารถมีรหัส มันไม่ได้ว่างเปล่าอย่างเคร่งครัด

ทำไมความแตกต่าง: ไม่มีความแตกต่างในทางปฏิบัติมากใน Python แต่ในระดับการวางแผนสำหรับโครงการขนาดใหญ่อาจเป็นเรื่องธรรมดาที่จะพูดคุยเกี่ยวกับอินเทอร์เฟซเนื่องจากไม่มีรหัส โดยเฉพาะอย่างยิ่งถ้าคุณกำลังทำงานกับโปรแกรมเมอร์ Java ที่คุ้นเคยกับคำว่า


+1 สำหรับความแตกต่างที่ ABC สามารถมีการใช้งานของตัวเอง - ซึ่งเป็นวิธีที่ยอดเยี่ยมในการชิงไหวชิงพริบตัวเอง
Titou

17

โดยทั่วไปแล้วอินเทอร์เฟซจะใช้เฉพาะในภาษาที่ใช้โมเดลคลาสสืบทอดเดี่ยว ในภาษาที่สืบทอดเดียวเหล่านี้โดยทั่วไปแล้วจะใช้อินเทอร์เฟซถ้าคลาสใด ๆ สามารถใช้วิธีการเฉพาะหรือชุดของวิธีการ นอกจากนี้ในภาษาที่สืบทอดเดียวคลาสที่เป็นนามธรรมถูกใช้เพื่อกำหนดตัวแปรคลาสเพิ่มเติมนอกเหนือจากวิธีการใดวิธีหนึ่งหรือมากกว่าหรือเพื่อใช้ประโยชน์จากรูปแบบการสืบทอดเดี่ยวเพื่อ จำกัด ช่วงของคลาสที่สามารถใช้ชุดของเมธอด

ภาษาที่รองรับรูปแบบการสืบทอดหลายแบบมักจะใช้คลาสหรือคลาสพื้นฐานแบบนามธรรมเท่านั้นและไม่ใช่อินเตอร์เฟส เนื่องจาก Python รองรับการสืบทอดหลายแบบมันไม่ได้ใช้อินเตอร์เฟสและคุณต้องการใช้คลาสพื้นฐานหรือคลาสพื้นฐานที่เป็นนามธรรม

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


2

คลาส Abstract เป็นคลาสที่มีเมธอด abstract อย่างน้อยหนึ่งวิธี พร้อมกับวิธีการที่เป็นนามธรรมชั้นเรียนที่เป็นนามธรรมสามารถมีวิธีคงที่ระดับและอินสแตนซ์ แต่ในกรณีของอินเตอร์เฟสมันจะมีวิธีการแบบนามธรรมเท่านั้น ดังนั้นจึงไม่จำเป็นต้องสืบทอดคลาสนามธรรม แต่มันเป็นข้อบังคับที่จะสืบทอดส่วนต่อประสาน


1

เพื่อความสมบูรณ์เราควรพูดถึงPEP3119 ซึ่ง ABC ได้รับการแนะนำและเปรียบเทียบกับอินเทอร์เฟซและความคิดเห็นดั้งเดิมของ Talin

คลาสนามธรรมไม่ใช่อินเตอร์เฟสที่สมบูรณ์แบบ:

  • เป็นของลำดับชั้นการสืบทอด
  • ไม่แน่นอน

แต่ถ้าคุณลองเขียนด้วยวิธีของคุณเอง:

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
...

คุณจะรู้ได้อย่างรวดเร็วว่าคุณกำลังคิดค้นวงล้อให้สำเร็จในที่สุด abc.ABCMeta

abc.ABCMeta ถูกเสนอให้เป็นส่วนเสริมที่มีประโยชน์ของฟังก์ชั่นอินเทอร์เฟซที่หายไปและมันก็ยุติธรรมพอในภาษาอย่างหลาม

แน่นอนว่ามันสามารถปรับปรุงได้ดีขึ้นในขณะที่เขียนเวอร์ชัน 3 และเพิ่มไวยากรณ์ใหม่และแนวคิดอินเทอร์เฟซที่ไม่เปลี่ยนแปลง ...

สรุป:

The abc.ABCMeta IS "pythonic" interface in python
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.