ฉันจะใช้อินเทอร์เฟซในหลามได้อย่างไร


182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

ฉันจะใช้ Python เทียบเท่ากับรหัส C # นี้ได้อย่างไร

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")


class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

นี่เป็นความคิดที่ดีหรือไม่? โปรดยกตัวอย่างในคำตอบของคุณ


วัตถุประสงค์ในการใช้อินเทอร์เฟซในกรณีของคุณคืออะไร
Bandi-T

23
ไม่มีจุดประสงค์เลย! ฉันแค่อยากเรียนรู้ว่าต้องทำอย่างไรเมื่อคุณต้องการอินเตอร์เฟสใน python?
Pratik Deoghare

18
raise NotImplementedErrorเป็นสิ่งที่showร่างกายควรจะ - ทำให้ไม่มีเหตุผลที่จะยกระดับทั่วไปอย่างสมบูรณ์Exceptionเมื่องูใหญ่กำหนดในตัวที่สมบูรณ์แบบหนึ่งเดียว! -)
อเล็กซ์ Martelli

2
ไม่ควรเริ่มต้น call call () ใน IInterface (หรือเพิ่มข้อยกเว้น) ดังนั้นคุณจึงไม่สามารถสร้างอินเทอร์เฟซแบบนามธรรมได้?
Katastic Voyage

1
ฉันเห็นการใช้งานบางอย่างสำหรับสิ่งนี้ ... สมมติว่าคุณมีวัตถุที่คุณต้องการให้แน่ใจว่ามีลายเซ็นเฉพาะ ด้วยการพิมพ์เป็ดคุณไม่สามารถรับประกันได้ว่าวัตถุจะเป็นลายเซ็นที่คุณคาดหวัง บางครั้งอาจมีประโยชน์ในการบังคับใช้การพิมพ์บนคุณสมบัติที่พิมพ์แบบไดนามิก
Prime By Design

คำตอบ:


151

ดังที่คนอื่น ๆ พูดถึงที่นี่:

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

ที่กล่าวว่ายังคงมีการใช้งานหลายอย่างสำหรับอินเทอร์เฟซ บางคนถูกปกคลุมด้วยคลาสพื้นฐานของ Pythons Abstract ซึ่งแนะนำใน Python 2.6 มันมีประโยชน์ถ้าคุณต้องการสร้างคลาสพื้นฐานที่ไม่สามารถสร้างอินสแตนซ์ได้ แต่มีส่วนติดต่อเฉพาะหรือส่วนหนึ่งของการนำไปใช้

การใช้งานอื่นคือถ้าคุณต้องการระบุว่าวัตถุนั้นใช้อินเทอร์เฟซเฉพาะและคุณสามารถใช้ ABC สำหรับสิ่งนั้นได้โดยการทำคลาสย่อยจากพวกมัน อีกวิธีหนึ่งคือ zope.interface โมดูลที่เป็นส่วนหนึ่งของ Zope Component Architecture ซึ่งเป็นเฟรมเวิร์กคอมโพเนนต์ที่ยอดเยี่ยมจริงๆ ที่นี่คุณไม่ได้คลาสย่อยจากอินเตอร์เฟส แต่ทำเครื่องหมายคลาส (หรืออินสแตนซ์) แทนเป็นการใช้อินเทอร์เฟซแทน สิ่งนี้สามารถใช้เพื่อค้นหาส่วนประกอบจากการลงทะเบียนส่วนประกอบ Supercool!


11
คุณช่วยอธิบายเรื่องนี้ได้ไหม? 1. วิธีการที่หนึ่งใช้อินเทอร์เฟซเช่น 2. จะใช้ในการค้นหาส่วนประกอบได้อย่างไร
geoidesic

43
"อินเทอร์เฟซไม่จำเป็นใน Python ยกเว้นเมื่อเป็น"
Baptiste Candellier

8
อินเทอร์เฟซส่วนใหญ่จะใช้เพื่อให้มีผลลัพธ์ที่คาดการณ์ / บังคับใช้ความถูกต้องของสมาชิกเมื่อผ่านวัตถุ มันจะดีถ้างูใหญ่สนับสนุนสิ่งนี้เป็นตัวเลือก นอกจากนี้ยังช่วยให้เครื่องมือในการพัฒนามีระบบภายในที่ดีขึ้น
Sonic Soul

1
ตัวอย่างจะปรับปรุงคำตอบนี้อย่างมาก
bob

5
"นี่เป็นเพราะไพ ธ อนมีการสืบทอดหลายแบบที่เหมาะสม" ใครบอกว่าอินเทอร์เฟซสำหรับมรดกหลาย ๆ
adnanmuttaleb

76

การใช้โมดูล abc สำหรับคลาสพื้นฐานที่เป็นนามธรรมดูเหมือนจะใช้กลอุบาย

from abc import ABCMeta, abstractmethod

class IInterface:
    __metaclass__ = ABCMeta

    @classmethod
    def version(self): return "1.0"
    @abstractmethod
    def show(self): raise NotImplementedError

class MyServer(IInterface):
    def show(self):
        print 'Hello, World 2!'

class MyBadServer(object):
    def show(self):
        print 'Damn you, world!'


class MyClient(object):

    def __init__(self, server):
        if not isinstance(server, IInterface): raise Exception('Bad interface')
        if not IInterface.version() == '1.0': raise Exception('Bad revision')

        self._server = server


    def client_show(self):
        self._server.show()


# This call will fail with an exception
try:
    x = MyClient(MyBadServer)
except Exception as exc:
    print 'Failed as it should!'

# This will pass with glory
MyClient(MyServer()).client_show()

11
Yugghh ต้องการโมดูลสำหรับสิ่งที่ควรเป็นส่วนหนึ่งของภาษาเองหรือไม่ได้ใช้เลย IMO
Mike de Klerk

คุณหมายถึง: if not server.version() == '1.0': raise ...? ฉันไม่ได้รับบรรทัดนี้จริงๆ ยินดีต้อนรับการอธิบาย
Skandix

1
@MikedeKlerk ฉันไม่เห็นด้วยมากขึ้น เหมือนกับคำตอบของ Python ในการพิมพ์; ฉันไม่ควรต้องนำเข้าโมดูลเพื่อประกาศว่าฉันต้องการประเภทที่จะเป็นประเภท การตอบสนองต่อสิ่งนั้นมักจะเป็น "well Python ถูกพิมพ์แบบไดนามิก" แต่นั่นไม่ใช่ข้อแก้ตัว Java + Groovy แก้ปัญหานี้ได้ Java สำหรับสิ่งของแบบคงที่, Groovy สำหรับเนื้อหาแบบไดนามิก
ubiquibacon

6
@MikedeKlerk โมดูล abc มีอยู่แล้วภายในกับ python มันเป็นงานอีกเล็กน้อยที่จะตั้งค่ารูปแบบเหล่านี้เพราะส่วนใหญ่ไม่จำเป็นใน Python เนื่องจากรูปแบบทางเลือกที่มีการพิจารณาว่า 'Pythonic มากกว่า' สำหรับนักพัฒนาส่วนใหญ่มันจะเป็นอย่างที่คุณพูดว่า 'ไม่ได้ใช้เลย' อย่างไรก็ตามยอมรับว่ามีบางกรณีที่จำเป็นต้องใช้ความสามารถในการเชื่อมต่อเหล่านี้อย่างแท้จริงผู้สร้าง Python ได้จัดทำ API ที่ใช้งานง่ายเพื่อให้เป็นไปได้
David Culbreth

39

อินเตอร์เฟซรองรับ Python 2.7 และ Python 3.4+

ในการติดตั้งส่วนต่อประสานคุณต้อง

pip install python-interface

รหัสตัวอย่าง:

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass


class MyClass(implements(MyInterface)):

    def method1(self, x):
        return x * 2

    def method2(self, x, y):
        return x + y

7
ข้อได้เปรียบที่สำคัญของไลบรารีนี้ IMHO คือความล้มเหลวในช่วงต้นที่จะช่วยให้คุณ: หากคลาสของคุณใช้อินเทอร์เฟซที่ระบุไม่ถูกต้องคุณจะได้รับข้อยกเว้นทันทีที่ชั้นเรียนอ่าน - คุณไม่จำเป็นต้องใช้ . ด้วยคลาสฐานนามธรรมของ Python คุณจะได้รับการยกเว้นเมื่อคุณสร้างอินสแตนซ์ของคลาสขึ้นครั้งแรกซึ่งอาจช้ากว่านั้น
ฮันส์

มันไม่จำเป็นเลย ABC เสนอฟังก์ชั่นในตัวที่คล้ายกัน
แดเนียล

@DanielCasares ABC นำเสนออินเทอร์เฟซที่เกิดขึ้นจริงหรือคุณหมายถึงคลาสนามธรรมที่ไม่มีรัฐหรือการนำไปใช้งานเป็นวิธีแก้ปัญหาที่ ABC เสนอให้หรือไม่
asaf92

36

การนำอินเตอร์เฟสไปใช้กับคลาสฐานนามธรรมนั้นง่ายกว่ามากใน Python 3 ที่ทันสมัยและใช้เป็นวัตถุประสงค์ในการทำสัญญาเป็นส่วนต่อประสานสำหรับส่วนขยายปลั๊กอิน

สร้างอินเทอร์เฟซ / นามธรรมคลาสพื้นฐาน:

from abc import ABC, abstractmethod

class AccountingSystem(ABC):

    @abstractmethod
    def create_purchase_invoice(self, purchase):
        pass

    @abstractmethod
    def create_sale_invoice(self, sale):
        log.debug('Creating sale invoice', sale)

สร้างคลาสย่อยปกติและแทนที่เมธอด abstract ทั้งหมด:

class GizmoAccountingSystem(AccountingSystem):

    def create_purchase_invoice(self, purchase):
        submit_to_gizmo_purchase_service(purchase)

    def create_sale_invoice(self, sale):
        super().create_sale_invoice(sale)
        submit_to_gizmo_sale_service(sale)

คุณสามารถเลือกที่จะมีการนำไปใช้ทั่วไปในเมธอด abstract ดังเช่นในcreate_sale_invoice()เรียกมันsuper()อย่างชัดเจนในคลาสย่อยดังกล่าวข้างต้น

การสร้างอินสแตนซ์ของคลาสย่อยที่ไม่ใช้เมธอด abstract ทั้งหมดล้มเหลว:

class IncompleteAccountingSystem(AccountingSystem):
    pass

>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice

นอกจากนี้คุณยังสามารถมีคุณสมบัตินามธรรมวิธีคงที่และวิธีการเรียนโดยการรวมคำอธิบายประกอบที่เกี่ยวข้องกับ @abstractmethodนอกจากนี้คุณยังสามารถมีคุณสมบัตินามธรรมแบบคงที่และวิธีการเรียนโดยการรวมคำอธิบายประกอบที่สอดคล้องกับ

คลาสพื้นฐานที่เป็นนามธรรมนั้นยอดเยี่ยมสำหรับการติดตั้งระบบที่ใช้ปลั๊กอิน คลาสย่อยที่นำเข้าทั้งหมดของคลาสนั้นสามารถเข้าถึงได้ผ่านทาง__subclasses__()ดังนั้นหากคุณโหลดคลาสทั้งหมดจากไดเรกทอรีปลั๊กอินด้วยimportlib.import_module()และหากพวกเขาคลาสย่อยคลาสฐานคุณมีการเข้าถึงโดยตรงกับคลาสเหล่านั้นผ่านทาง__subclasses__()และคุณสามารถมั่นใจได้ว่าสัญญาอินเทอร์เฟซ พวกเขาในระหว่างการเริ่ม

นี่คือการใช้งานการโหลดปลั๊กอินสำหรับAccountingSystemตัวอย่างด้านบน:

...
from importlib import import_module

class AccountingSystem(ABC):

    ...
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module(module_name)
            subclasses = cls.__subclasses__()
            if len(subclasses) > 1:
                raise InvalidAccountingSystemError('More than one '
                        f'accounting module: {subclasses}')
            if not subclasses or module_name not in str(subclasses[0]):
                raise InvalidAccountingSystemError('Accounting module '
                        f'{module_name} does not exist or does not '
                        'subclass AccountingSystem')
            cls._instance = subclasses[0]()
        return cls._instance

จากนั้นคุณสามารถเข้าถึงวัตถุปลั๊กอินระบบบัญชีผ่านAccountingSystemคลาสได้

>>> accountingsystem = AccountingSystem.instance()

(แรงบันดาลใจจากโพสต์ PyMOTW-3 )


คำถาม:ชื่อโมดูล "ABC" หมายถึงอะไร
เซบาสเตียนนีลเซ่น

"ABC" ย่อมาจาก "Abstract Base Classes" ดูเอกสารทางการ
mrts

31

มีการใช้งานอินเทอร์เฟซของบุคคลที่สามสำหรับ Python (ที่นิยมมากที่สุดคือZope's , ใช้ในTwisted ), แต่โดยทั่วไปแล้ว Python coders ชอบที่จะใช้แนวคิดยิ่งขึ้นที่รู้จักกันในชื่อ "Abstract Base Class" (ABC) ซึ่งรวมอินเตอร์เฟสกับ ความเป็นไปได้ที่จะมีแง่มุมในการนำไปปฏิบัติบางอย่างเช่นกัน ABCs ได้รับการสนับสนุนเป็นอย่างดีใน Python 2.6 และใหม่กว่าให้ดูPEPแต่แม้ใน Python เวอร์ชันก่อนหน้านี้พวกเขามักจะถูกมองว่าเป็น "ทางที่จะไป" - เพียงกำหนดคลาสที่วิธีการยกระดับของNotImplementedErrorคลาสย่อย เมื่อสังเกตเห็นว่าพวกเขาควรจะแทนที่วิธีการเหล่านั้น! -)


3
มีการใช้อินเทอร์เฟซของบุคคลที่สามสำหรับ Pythonหมายความว่าอย่างไร คุณช่วยอธิบาย ABC ได้ไหม?
Pratik Deoghare

2
ดีฉันจะมีปัญหากับการที่ ABC 'เป็น' ยิ่งขึ้น ' ;) มีสิ่ง zope อินเทอร์เฟซสามารถทำสิ่งนั้นของ ABC ไม่ได้เช่นเดียวกับวิธีอื่น ๆ แต่ไม่เช่นนั้นคุณก็ถูกต้องตามปกติ +1
Lennart Regebro

1
@Alfred: หมายความว่าโมดูลเช่น zope.interface ไม่รวมอยู่ในไลบรารีมาตรฐาน แต่มีให้จาก pypi
Lennart Regebro

ฉันยังมีเวลายากที่จะห้อมล้อมแนวคิดของ ABC เป็นไปได้ไหมที่ใครสักคนจะเขียนtwistedmatrix.com/documents/current/core/howto/components.html (IMHO ซึ่งเป็นคำอธิบายที่ยอดเยี่ยมเกี่ยวกับแนวคิดอินเตอร์เฟส) ในแง่ของ ABC มันสมเหตุสมผลไหม?
mcepl

21

บางอย่างเช่นนี้ (อาจไม่ทำงานเนื่องจากฉันไม่มี Python อยู่):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"

2
ฉันควรทำอย่างไรกับ__init__(self)ตัวสร้าง?
Pratik Deoghare

1
แล้วแต่คุณ. เนื่องจากไม่มีการตรวจสอบเวลาคอมไพล์กับการสร้างวัตถุจากคลาสนามธรรมคุณจะไม่ได้รับการป้องกันใด ๆ ระหว่างการเข้ารหัส / การคอมไพล์ จะมีคอนสตรัคเตอร์ที่ได้รับมาดังนั้นวัตถุจะถูกสร้างขึ้นเพียงแค่ "ว่างเปล่า" มันขึ้นอยู่กับคุณในการตัดสินใจว่าคุณจะดีขึ้นหรือไม่โดยการอนุญาตให้สิ่งนี้เกิดขึ้นและตรวจจับความล้มเหลวในภายหลังหรือหยุดโปรแกรมอย่างชัดเจนในตอนนั้นและโดยการใช้ตัวสร้างที่คล้ายกัน
Bandi-T

1
นั่นเป็นเหตุผลที่abc.ABCดีกว่าการยกระดับNotImplementedError- การสร้างอินสแตนซ์ของabc.ABCคลาสย่อยที่ไม่ใช้วิธีนามธรรมทั้งหมดล้มเหลว แต่เนิ่นๆดังนั้นคุณจึงได้รับการปกป้องจากข้อผิดพลาด ดูคำตอบของฉันด้านล่างเพื่อดูว่าข้อผิดพลาดเป็นอย่างไร
mrts

8

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

เช่นถ้าคุณมีผู้สังเกตการณ์และสามารถสังเกตได้สังเกตได้คือความสนใจในการสมัครสมาชิกวัตถุที่รองรับส่วนต่อประสาน IObserver ซึ่งในทางกลับกันจะมี notifyการดำเนินการ นี่คือการตรวจสอบในเวลารวบรวม

ใน Python ไม่มีสิ่งดังกล่าวcompile timeและการค้นหาเมธอดจะดำเนินการที่รันไทม์ นอกจากนี้เราสามารถแทนที่การค้นหาด้วย __getattr __ () หรือ __getattribute __ () วิธีการเวทย์มนตร์ กล่าวอีกนัยหนึ่งคุณสามารถส่งผ่านเป็นผู้สังเกตการณ์วัตถุใด ๆ ที่สามารถส่งคืน callable ในการเข้าถึงnotifyคุณลักษณะ

นี่ทำให้ฉันสรุปได้ว่าอินเตอร์เฟสใน Python มีอยู่จริง - มันเป็นเพียงการบังคับใช้ของพวกเขาถูกเลื่อนออกไปเป็นช่วงเวลาที่พวกเขาใช้งานจริง

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