لماذا نستخدم الفئات الأساسية المجردة في بايثون؟

StackOverflow https://stackoverflow.com/questions/3570796

  •  01-10-2019
  •  | 
  •  

سؤال

نظرًا لأنني معتاد على الطرق القديمة لكتابة البطة في بايثون، فقد فشلت في فهم الحاجة إلى ABC (الفئات الأساسية المجردة).ال يساعد جيد في كيفية استخدامها.

حاولت قراءة الأساس المنطقي في بيب, ، لكنه ذهب فوق رأسي.إذا كنت أبحث عن حاوية تسلسل قابلة للتغيير، فسوف أتحقق منها __setitem__, ، أو على الأرجح حاول استخدامه (EAFP).لم أجد استخدامًا حقيقيًا للحياة لـ أعداد الوحدة النمطية، التي تستخدم ABCs، ولكن هذا هو الأقرب إلى الفهم.

هل يمكن لأحد أن يشرح لي السبب المنطقي، من فضلك؟

هل كانت مفيدة؟

المحلول

نسخة مختصرة

تقدم ABCs مستوى أعلى من العقد الدلالي بين العملاء والفئات المنفذة.

نسخة طويلة

هناك عقد بين الفصل والمتصلين به.يعد الفصل بالقيام بأشياء معينة والحصول على خصائص معينة.

هناك مستويات مختلفة للعقد.

على مستوى منخفض جدًا، قد يتضمن العقد اسم الطريقة أو عدد معلماتها.

في اللغة المكتوبة بشكل ثابت، سيتم تنفيذ هذا العقد فعليًا بواسطة المترجم.في Python، يمكنك استخدام EAFP أو الاستبطان للتأكد من أن الكائن غير المعروف يلبي هذا العقد المتوقع.

ولكن هناك أيضًا وعودًا دلالية ذات مستوى أعلى في العقد.

على سبيل المثال، إذا كان هناك __str__() الطريقة، من المتوقع أن تقوم بإرجاع تمثيل سلسلة للكائن.هو - هي استطاع احذف جميع محتويات الكائن، وأكمل المعاملة وأخرج صفحة فارغة من الطابعة...ولكن هناك فهم مشترك لما يجب أن يفعله، وهو موصوف في دليل بايثون.

هذه حالة خاصة، حيث يتم وصف العقد الدلالي في الدليل.ماذا ينبغي print() طريقة تفعل؟هل يجب كتابة الكائن على الطابعة أو خط على الشاشة أو أي شيء آخر؟يعتمد الأمر على ذلك - تحتاج إلى قراءة التعليقات لفهم العقد بالكامل هنا.جزء من رمز العميل الذي يتحقق ببساطة من أن print() لقد أكدت الطريقة الموجودة جزءًا من العقد - أنه يمكن إجراء استدعاء الطريقة، ولكن ليس هناك اتفاق على دلالات المستوى الأعلى للاستدعاء.

يعد تعريف فئة أساسية مجردة (ABC) طريقة لإنتاج عقد بين منفذي الفصل والمتصلين.إنها ليست مجرد قائمة بأسماء الطرق، ولكنها فهم مشترك لما يجب أن تفعله تلك الطرق.إذا ورثت من هذا ABC، فإنك تعد باتباع جميع القواعد الموضحة في التعليقات، بما في ذلك دلالات print() طريقة.

تتمتع كتابة البطة في بايثون بالعديد من المزايا من حيث المرونة مقارنة بالكتابة الثابتة، ولكنها لا تحل جميع المشكلات.تقدم ABCs حلاً وسطًا بين الشكل الحر لـ Python والعبودية والانضباط للغة المكتوبة بشكل ثابت.

نصائح أخرى

@إجابة OddThinking ليست خاطئة ، لكنني أعتقد أنها تفوت حقا, عملي سبب بيثون لديه ABCs في عالم من البط.

الأساليب المجردة رائعة ، لكن في رأيي أنها لا تملأ حقًا أي حالات استخدام غير مغطاة بالفعل بكتابة البط. تكمن القوة الحقيقية لدروس القاعدة المجردة الطريقة التي تسمح لك بتخصيص سلوكها isinstance و issubclass. (__subclasshook__ هو في الأساس واجهة برمجة تطبيقات ودية على رأس بيثون __instancecheck__ و __subclasscheck__ السنانير.) يعد تكييف بنيات مدمجة للعمل على أنواع مخصصة جزءًا كبيرًا من فلسفة بيثون.

رمز مصدر بيثون مثالي. هنا انا اعرض collections.Container تم تعريفه في المكتبة القياسية (وقت كتابة هذا التقرير):

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

هذا التعريف __subclasshook__ يقول أن أي فصل مع أ __contains__ تعتبر السمة فئة فرعية من الحاوية ، حتى لو لم تكن فئة فرعية مباشرة. لذلك يمكنني كتابة هذا:

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

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

بعبارات أخرى، إذا قمت بتنفيذ الواجهة الصحيحة ، فأنت فئة فرعية! توفر ABCs طريقة رسمية لتحديد واجهات في بيثون ، مع الحفاظ على صدق روح التصميم. علاوة على ذلك ، يعمل هذا بطريقة تكرم مبدأ مفتوح.

يبدو نموذج كائن Python مشابهًا بشكل سطحي لنظام OO "التقليدي" (الذي أعني به Java*) - لقد حصلنا أكثر مرونة. وبالمثل ، قد تكون مفهوم بيثون لفصول القاعدة التجريدية يمكن التعرف عليها لمطور Java ، ولكن في الممارسة العملية ، فإنها مخصصة لغرض مختلف تمامًا.

أجد نفسي أحيانًا أكتب وظائف متعددة الأشكال يمكن أن تعمل على عنصر واحد أو مجموعة من العناصر ، وأجد isinstance(x, collections.Iterable) أن تكون أكثر قابلية للقراءة من hasattr(x, '__iter__') أو ما يعادلها try...except الكتلة. (إذا لم تكن تعرف Python ، أي من هؤلاء الثلاثة سيجعل نية الكود أوضح؟)

ومع ذلك ، أجد أنني نادراً ما أحتاج إلى كتابة ABC الخاصة بي وأكتشف عادة الحاجة إلى أحد من خلال إعادة البناء. إذا رأيت وظيفة متعددة الأشكال تجعل الكثير من عمليات فحص السمات ، أو الكثير من الوظائف التي تقوم بفحص السمة نفسها ، فإن الرائحة تشير إلى وجود ABC في انتظار استخراجها.

*دون الدخول في النقاش حول ما إذا كان جافا هو نظام "تقليدي" ...


إضافة: على الرغم من أن فئة قاعدة مجردة يمكن أن تتجاوز سلوك isinstance و issubclass, ، لا يزال لا يدخل MRO من الفئة الفرعية الافتراضية. هذا هو مأزق محتمل للعملاء: ليس كل كائن من أجله isinstance(x, MyABC) == True هل تم تحديد الطرق 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

لسوء الحظ ، هذا واحد من تلك الفخاخ "فقط لا تفعل ذلك" (والتي لديها قلة قليلة نسبيًا!): تجنب تعريف ABCs مع كل من __subclasshook__ وطرق غير المجردة. علاوة على ذلك ، يجب أن تجعل تعريفك __subclasshook__ تمشيا مع مجموعة الأساليب المجردة التي تحددها ABC.

ميزة مفيدة لـ ABCS هي أنه إذا لم تقم بتطبيق جميع الأساليب (والخصائص) الضرورية ، فستحصل على خطأ عند التثبيت ، بدلاً من AttributeError, ، ربما في وقت لاحق ، عندما تحاول بالفعل استخدام الطريقة المفقودة.

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

تحرير: لتضمين بناء جملة Python3 ، شكرًا @pandasrocks

سيؤدي ذلك إلى تحديد ما إذا كان الكائن يدعم بروتوكولًا معينًا دون الحاجة إلى التحقق من وجود جميع الطرق في البروتوكول أو دون إثارة استثناء في عمق منطقة "العدو" بسبب عدم الدعم أسهل بكثير.

تأكد من أن الطريقة المجردة تتأكد من أن الطريقة التي تتصل بها في الفصل الأصل يجب أن تظهر في فئة الطفل. فيما يلي طريقة Noraml للاتصال واستخدام الملخص. البرنامج المكتوب في Python3

الطريقة العادية للاتصال

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 () يسمى"

c.methodtwo()

notimplementederror

مع الطريقة التجريدية

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: لا يمكن إنشاء مثيل لابن الفئة التجريدية مع Methodtwo Methodtwo.

نظرًا لأن MethodTwo لا يتم استدعاؤه في فئة الطفل ، فقد حصلنا على خطأ. التنفيذ المناسب أدناه

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 () يسمى"

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top