क्या पायथन में अमूर्त कक्षाएं बनाना संभव है?


321

मैं पायथन में एक वर्ग या विधि को कैसे सार कर सकता हूं?

मैंने ऐसा करने की कोशिश की __new__():

class F:
    def __new__(cls):
        raise Exception("Unable to create an instance of abstract class %s" %cls)

लेकिन अब अगर मैं एक ऐसा वर्ग बनाता हूं जो इस तरह Gसे विरासत में मिले F:

class G(F):
    pass

तब मैं तुरंत Gया तो नहीं कर सकता , क्योंकि यह अपनी सुपर क्लास __new__विधि कहता है ।

क्या अमूर्त वर्ग को परिभाषित करने का एक बेहतर तरीका है?


हां, आप एब्स (अमूर्त बेस क्लास) मॉड्यूल के साथ अजगर में सार कक्षाएं बना सकते हैं। यह साइट इसमें आपकी सहायता करेगी: http://docs.python.org/2/library/abc.html
ORION

जवाबों:


553

abcअमूर्त कक्षाएं बनाने के लिए मॉड्यूल का उपयोग करें । abstractmethodएक विधि सार घोषित करने के लिए डेकोरेटर का उपयोग करें , और अपने पायथन संस्करण के आधार पर, तीन तरीकों में से एक का उपयोग करके एक वर्ग सार घोषित करें।

पायथन 3.4 और इसके बाद के संस्करण से, आप विरासत में प्राप्त कर सकते हैं ABC। पायथन के पुराने संस्करणों में, आपको अपनी कक्षा के मेटाक्लास को निर्दिष्ट करने की आवश्यकता है ABCMeta। मेटाक्लास निर्दिष्ट करने के लिए पायथन 3 और पायथन 2 में अलग-अलग वाक्यविन्यास हैं। तीन संभावनाएँ नीचे दी गई हैं:

# Python 3.4+
from abc import ABC, abstractmethod
class Abstract(ABC):
    @abstractmethod
    def foo(self):
        pass
# Python 3.0+
from abc import ABCMeta, abstractmethod
class Abstract(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass
# Python 2
from abc import ABCMeta, abstractmethod
class Abstract:
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

जिस भी तरीके से आप उपयोग करते हैं, आप एक अमूर्त वर्ग को इंस्ट्रूमेंट करने में सक्षम नहीं होंगे, जिसमें अमूर्त विधियां हैं, लेकिन उन उप-विधियों को तत्काल करने में सक्षम होंगे जो उन तरीकों की ठोस परिभाषा प्रदान करते हैं:

>>> Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Abstract with abstract methods foo
>>> class StillAbstract(Abstract):
...     pass
... 
>>> StillAbstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class StillAbstract with abstract methods foo
>>> class Concrete(Abstract):
...     def foo(self):
...         print('Hello, World')
... 
>>> Concrete()
<__main__.Concrete object at 0x7fc935d28898>

9
@abbridmethod क्या करता है? तुम्हें यह क्यों चाहिए? यदि कक्षा पहले से ही स्थापित है तो सार को संकलक / व्याख्या से पता नहीं चलना चाहिए कि सभी विधियाँ सवाल में सार वर्ग से हैं?
चार्ली पार्कर

29
@CharlieParker - @abstractmethodयह इसलिए बनाता है ताकि सजाए गए फंक्शन को क्लास से पहले ही ओवरराइड किया जा सके। डॉक्स से:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
फेक नेम

6
@CharlieParker - मूल रूप से, यह आपको एक वर्ग को इस तरीके से परिभाषित करने की सुविधा देता है जहाँ उप-वर्ग को निर्दिष्ट सभी तरीकों को तुरंत लागू करना चाहिए
फेक नेम

13
क्या उपयोगकर्ताओं को बिना किसी @abbridmethod तरीकों के एब्सट्रैक्ट () बनाने से रोकने का कोई तरीका है?
जो

2
@Joe यह प्रतीत होता है कि आप उपयोग कर सकते हैं @abstractmethodके लिए __init__और साथ ही विधि, देख stackoverflow.com/q/44800659/547270
scrutari

108

ऐसा करने के लिए पुराने स्कूल (पूर्व- PEP 3119 ) का तरीका केवल raise NotImplementedErrorअमूर्त वर्ग में है जब एक सार पद्धति कहा जाता है।

class Abstract(object):
    def foo(self):
        raise NotImplementedError('subclasses must override foo()!')

class Derived(Abstract):
    def foo(self):
        print 'Hooray!'

>>> d = Derived()
>>> d.foo()
Hooray!
>>> a = Abstract()
>>> a.foo()
Traceback (most recent call last): [...]

यह abcमॉड्यूल का उपयोग करने के रूप में एक ही अच्छा गुण नहीं है। आप अभी भी एब्सट्रैक्ट बेस क्लास को तुरंत इंस्टाल कर सकते हैं, और जब तक आप रनटाइम में एब्सट्रैक्ट मेथड को नहीं बुलाते हैं, तब तक आपको अपनी गलती नहीं मिलेगी।

लेकिन अगर आप साधारण कक्षाओं के एक छोटे से सेट के साथ काम कर रहे हैं, तो शायद कुछ सार तरीकों के साथ, यह दृष्टिकोण abcप्रलेखन के माध्यम से मिटाने की कोशिश करने से थोड़ा आसान है ।


1
इस दृष्टिकोण की सादगी और प्रभावशीलता की सराहना करें।
mohit6up

हाहा, मैंने अपने OMSCS पाठ्यक्रम में इसे हर जगह देखा और इसका कोई सुराग नहीं था कि यह क्या था :)
mLstudent33

18

यहां एबीसी मॉड्यूल से निपटने के बिना एक बहुत आसान तरीका है।

__init__जिस कक्षा में आप अमूर्त वर्ग बनना चाहते हैं, उसकी विधि में, आप स्वयं के "प्रकार" की जांच कर सकते हैं। यदि स्वयं का प्रकार बेस क्लास है, तो कॉलर बेस क्लास को तुरंत करने की कोशिश कर रहा है, इसलिए एक अपवाद बढ़ाएं। यहाँ एक सरल उदाहरण दिया गया है:

class Base():
    def __init__(self):
        if type(self) is Base:
            raise Exception('Base is an abstract class and cannot be instantiated directly')
        # Any initialization code
        print('In the __init__  method of the Base class')

class Sub(Base):
    def __init__(self):
        print('In the __init__ method of the Sub class before calling __init__ of the Base class')
        super().__init__()
        print('In the __init__ method of the Sub class after calling __init__ of the Base class')

subObj = Sub()
baseObj = Base()

जब चलाने, यह उत्पादन:

In the __init__ method of the Sub class before calling __init__ of the Base class
In the __init__  method of the Base class
In the __init__ method of the Sub class after calling __init__ of the Base class
Traceback (most recent call last):
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 16, in <module>
    baseObj = Base()
  File "/Users/irvkalb/Desktop/Demo files/Abstract.py", line 4, in __init__
    raise Exception('Base is an abstract class and cannot be instantiated directly')
Exception: Base is an abstract class and cannot be instantiated directly

इससे पता चलता है कि आप बेस क्लास से ली जाने वाली सबक्लास को तुरंत इंस्टाल कर सकते हैं, लेकिन आप बेस क्लास को तुरंत इंस्टेंट नहीं कर सकते।


11

अधिकांश पिछले उत्तर सही थे, लेकिन यहां का जवाब और उदाहरण पायथन 3.7 है। हां, आप एक अमूर्त वर्ग और विधि बना सकते हैं। बस एक अनुस्मारक के रूप में कभी-कभी एक वर्ग को एक विधि को परिभाषित करना चाहिए जो तार्किक रूप से एक वर्ग से संबंधित है, लेकिन वह वर्ग निर्दिष्ट नहीं कर सकता है कि विधि को कैसे लागू किया जाए। उदाहरण के लिए, माता-पिता और शिशुओं के नीचे की कक्षाओं में वे दोनों खाते हैं लेकिन कार्यान्वयन प्रत्येक के लिए अलग होगा क्योंकि शिशुओं और माता-पिता एक अलग तरह का भोजन खाते हैं और उनके खाने की संख्या अलग-अलग होती है। तो, विधि खाओ सबक्लासेस एब्सट्रैडक्लास को मात देता है।

from abc import ABC, abstractmethod

class AbstractClass(ABC):

    def __init__(self, value):
        self.value = value
        super().__init__()

    @abstractmethod
    def eat(self):
        pass

class Parents(AbstractClass):
    def eat(self):
        return "eat solid food "+ str(self.value) + " times each day"

class Babies(AbstractClass):
    def eat(self):
        return "Milk only "+ str(self.value) + " times or more each day"

food = 3    
mom = Parents(food)
print("moms ----------")
print(mom.eat())

infant = Babies(food)
print("infants ----------")
print(infant.eat())

उत्पादन:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day

शायद import abcबेकार है।
बेन्यामिन जाफ़री

हम पर @abstractmethod लिख सकते हैं init भी समारोह?
चर

+1 क्यों @abbridmethod डीई ईट (स्व) खाएं? यदि यह वर्ग अमूर्त है और इसलिए इसका तात्पर्य यह नहीं है कि आप भोजन करने के लिए (स्वयं) पास क्यों हैं? यह इसके बिना ठीक काम करता है
VMMF

7

यह एक अजगर 3 में काम कर रहा होगा

from abc import ABCMeta, abstractmethod

class Abstract(metaclass=ABCMeta):

    @abstractmethod
    def foo(self):
        pass

Abstract()
>>> TypeError: Can not instantiate abstract class Abstract with abstract methods foo

4

जैसा कि अन्य उत्तरों में बताया गया है, हाँ आप abcमॉड्यूल का उपयोग करके पायथन में अमूर्त कक्षाओं का उपयोग कर सकते हैं । नीचे मैं सार का उपयोग करके एक वास्तविक उदाहरण देता हूं @classmethod, @propertyऔर @abstractmethod(पायथन 3.6+ का उपयोग करके)। मेरे लिए यह आम तौर पर उदाहरणों के साथ शुरू करना आसान है जिन्हें मैं आसानी से कॉपी और पेस्ट कर सकता हूं; मुझे उम्मीद है कि यह उत्तर दूसरों के लिए भी उपयोगी है।

आइए सबसे पहले एक बेस क्लास बनाएं जिसका नाम है Base:

from abc import ABC, abstractmethod

class Base(ABC):

    @classmethod
    @abstractmethod
    def from_dict(cls, d):
        pass

    @property
    @abstractmethod
    def prop1(self):
        pass

    @property
    @abstractmethod
    def prop2(self):
        pass

    @prop2.setter
    @abstractmethod
    def prop2(self, val):
        pass

    @abstractmethod
    def do_stuff(self):
        pass

हमारी Baseकक्षा में हमेशा एक from_dict classmethod, property prop1( एक जो केवल पढ़ा जाता है) और एक property prop2(जिसे सेट भी किया जा सकता है) और साथ ही एक फ़ंक्शन भी कहा जाता है do_stuff। अब जो भी वर्ग के आधार पर बनाया गया है, Baseउसे इन सभी तरीकों / गुणों के लिए लागू करना होगा। कृपया ध्यान दें कि अमूर्त होने की विधि के लिए, दो सज्जाकारों की आवश्यकता होती है - classmethodऔर सार property

अब हम Aइस तरह एक वर्ग बना सकते हैं :

class A(Base):
    def __init__(self, name, val1, val2):
        self.name = name
        self.__val1 = val1
        self._val2 = val2

    @classmethod
    def from_dict(cls, d):
        name = d['name']
        val1 = d['val1']
        val2 = d['val2']

        return cls(name, val1, val2)

    @property
    def prop1(self):
        return self.__val1

    @property
    def prop2(self):
        return self._val2

    @prop2.setter
    def prop2(self, value):
        self._val2 = value

    def do_stuff(self):
        print('juhu!')

    def i_am_not_abstract(self):
        print('I can be customized')

सभी आवश्यक विधियों / गुणों को लागू किया गया है और हम कर सकते हैं - निश्चित रूप से - अतिरिक्त कार्यों को भी जोड़ सकते हैं जो Base(यहाँ :) का हिस्सा नहीं हैं i_am_not_abstract

अब हम कर सकते हैं:

a1 = A('dummy', 10, 'stuff')
a2 = A.from_dict({'name': 'from_d', 'val1': 20, 'val2': 'stuff'})

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

इच्छानुसार, हम सेट नहीं कर सकते prop1:

a.prop1 = 100

वापस होगा

विशेषता: विशेषता सेट नहीं कर सकता

इसके अलावा हमारी from_dictविधि ठीक काम करती है:

a2.prop1
# prints 20

यदि हम अब Bइस तरह एक दूसरी श्रेणी को परिभाषित करते हैं :

class B(Base):
    def __init__(self, name):
        self.name = name

    @property
    def prop1(self):
        return self.name

और इस तरह से एक वस्तु को पलटने की कोशिश की:

b = B('iwillfail')

हमें एक त्रुटि मिलेगी

TypeError: अमूर्त वर्ग B को अमूर्त विधियों do_stuff, from_dict, prop2 से नहीं रोक सकता

उन सभी चीजों को सूचीबद्ध करना, Baseजिनमें हमने लागू नहीं किया था B


2

यह भी काम करता है और सरल है:

class A_abstract(object):

    def __init__(self):
        # quite simple, old-school way.
        if self.__class__.__name__ == "A_abstract": 
            raise NotImplementedError("You can't instantiate this abstract class. Derive it, please.")

class B(A_abstract):

        pass

b = B()

# here an exception is raised:
a = A_abstract()

2

आप अपने लाभ के लिए __new__ विधि का उपयोग भी कर सकते हैं। तुम बस कुछ भूल गए। __New__ पद्धति हमेशा नई वस्तु लौटाती है, इसलिए आपको इसकी सुपरक्लास नई विधि अवश्य लौटानी चाहिए। इस प्रकार करें।

class F:
    def __new__(cls):
        if cls is F:
            raise TypeError("Cannot create an instance of abstract class '{}'".format(cls.__name__))
        return super().__new__(cls)

नई पद्धति का उपयोग करते समय, आपको ऑब्जेक्ट को वापस करना होगा, न कि कोई कीवर्ड। वह सब तुम चूक गए।


2

मुझे स्वीकृत उत्तर और अन्य सभी अजीब selfलगते हैं , क्योंकि वे एक अमूर्त वर्ग में आते हैं। एक सार वर्ग तात्कालिक नहीं है इसलिए एक नहीं हो सकता है self

तो यह कोशिश करो, यह काम करता है।

from abc import ABCMeta, abstractmethod


class Abstract(metaclass=ABCMeta):
    @staticmethod
    @abstractmethod
    def foo():
        """An abstract method. No need to write pass"""


class Derived(Abstract):
    def foo(self):
        print('Hooray!')


FOO = Derived()
FOO.foo()

1
यहां तक ​​कि abc प्रलेखन उनके उदाहरण लिंक में स्वयं का उपयोग करता है
igor Smirnov

2
 from abc import ABCMeta, abstractmethod

 #Abstract class and abstract method declaration
 class Jungle(metaclass=ABCMeta):
     #constructor with default values
     def __init__(self, name="Unknown"):
     self.visitorName = name

     def welcomeMessage(self):
         print("Hello %s , Welcome to the Jungle" % self.visitorName)

     # abstract method is compulsory to defined in child-class
     @abstractmethod
     def scarySound(self):
         pass

अच्छा @ शिवम भारद्वाज, मैंने इसे वैसे ही किया
मुहम्मद इरफान

-3

अपने कोड स्निपेट में, आप __new__इसे उपवर्ग में विधि के लिए एक कार्यान्वयन प्रदान करके भी हल कर सकते हैं , इसी तरह:

def G(F):
    def __new__(cls):
        # do something here

लेकिन यह एक हैक है और मैं आपको इसके खिलाफ सलाह देता हूं, जब तक कि आप नहीं जानते कि आप क्या कर रहे हैं। लगभग सभी मामलों के लिए मैं आपको abcमॉड्यूल का उपयोग करने की सलाह देता हूं , जो कि मेरे द्वारा सुझाए जाने से पहले अन्य हैं।

इसके अलावा जब आप एक नया (आधार) वर्ग बनाने के लिए, यह उपवर्ग बनाने object, इस तरह: class MyBaseClass(object):। मुझे नहीं पता कि यह अब और अधिक महत्वपूर्ण है, लेकिन यह आपके कोड पर शैली स्थिरता बनाए रखने में मदद करता है


-4

@ TimGilbert के पुराने स्कूल के उत्तर के लिए बस एक त्वरित जोड़ ... आप अपने अमूर्त आधार वर्ग के init () विधि को एक अपवाद फेंक सकते हैं और जो इसे तात्कालिक होने से रोकेंगे, नहीं?

>>> class Abstract(object):
...     def __init__(self):
...         raise NotImplementedError("You can't instantiate this class!")
...
>>> a = Abstract()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
NotImplementedError: You can't instantiate this class! 

6
यह उपवर्गों को किसी भी सामान्य init कोड से लाभ उठाने से रोकेगा जो तार्किक रूप से उनके सामान्य आधार वर्ग में मौजूद होना चाहिए।
अर्पित सिंह 14

काफी उचित। इतने पुराने स्कूल के लिए।
डेव वेड-स्टाइन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.