Apakah mungkin membuat kelas abstrak dengan Python?

321

Bagaimana saya bisa membuat kelas atau metode abstrak dengan Python?

Saya mencoba mendefinisikan ulang __new__()seperti:

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

tetapi sekarang jika saya membuat kelas Gyang mewarisi dari Fseperti:

class G(F):
    pass

maka saya tidak bisa instantiate Gjuga, karena ia memanggil __new__metode super class-nya .

Apakah ada cara yang lebih baik untuk mendefinisikan kelas abstrak?

Martin Thoma
sumber
Ya, Anda bisa membuat kelas abstrak dalam python dengan modul abc (abstract base classes). Situs ini akan membantu Anda dengan itu: http://docs.python.org/2/library/abc.html
ORION

Jawaban:

554

Gunakan abcmodul untuk membuat kelas abstrak. Gunakan abstractmethoddekorator untuk mendeklarasikan abstrak metode, dan mendeklarasikan abstrak kelas menggunakan salah satu dari tiga cara, tergantung pada versi Python Anda.

Di Python 3.4 dan di atasnya, Anda bisa mewarisi dari ABC. Di versi Python sebelumnya, Anda perlu menentukan metaclass kelas Anda sebagai ABCMeta. Menentukan metaclass memiliki sintaks yang berbeda dalam Python 3 dan Python 2. Tiga kemungkinan ditunjukkan di bawah ini:

# 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

Dengan cara apa pun yang Anda gunakan, Anda tidak akan dapat membuat instance kelas abstrak yang memiliki metode abstrak, tetapi akan dapat membuat instance subkelas yang memberikan definisi konkret dari metode tersebut:

>>> 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>
alexvassel
sumber
9
apa yang dilakukan metode @abstractmethod? Mengapa Anda membutuhkannya? Jika kelas sudah ditetapkan sebagai abstrak, bukankah seharusnya compiler / interpretasikan tahu bahwa semua metode berasal dari kelas abstrak yang dimaksud?
Charlie Parker
30
@CharlieParker - Membuatnya @abstractmethodsehingga fungsi yang didekorasi harus ditimpa sebelum kelas dapat dipakai. Dari dokumen:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Nama Palsu
6
@CharlieParker - Pada dasarnya, ini memungkinkan Anda menentukan kelas dengan cara di mana sub-kelas harus mengimplementasikan serangkaian metode tertentu untuk instantiate sama sekali.
Nama Palsu
13
Apakah ada cara untuk melarang pengguna membuat Abstract () tanpa metode @abstractmethod?
Joe
2
@ Joe tampaknya Anda dapat menggunakan @abstractmethoduntuk __init__metode juga, lihat stackoverflow.com/q/44800659/547270
scrutari
108

Cara sekolah lama (pra- PEP 3119 ) untuk melakukan ini adalah hanya untuk raise NotImplementedErrordi kelas abstrak ketika metode abstrak dipanggil.

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): [...]

Ini tidak memiliki properti bagus yang sama seperti menggunakan abcmodul. Anda masih dapat membuat instance kelas dasar abstrak itu sendiri, dan Anda tidak akan menemukan kesalahan Anda sampai Anda memanggil metode abstrak saat runtime.

Tetapi jika Anda berurusan dengan satu set kecil kelas sederhana, mungkin hanya dengan beberapa metode abstrak, pendekatan ini sedikit lebih mudah daripada mencoba mengarungi abcdokumentasi.

Tim Gilbert
sumber
1
Hargai kesederhanaan dan efektifitas pendekatan ini.
mohit6up
Haha, saya melihat ini di mana-mana dalam kursus OMSCS saya dan tidak tahu apa itu :)
mLstudent33
18

Inilah cara yang sangat mudah tanpa harus berurusan dengan modul ABC.

Dalam __init__metode kelas yang Anda inginkan menjadi kelas abstrak, Anda dapat memeriksa "tipe" diri. Jika tipe diri adalah kelas dasar, maka penelepon mencoba untuk membuat instance kelas dasar, jadi ajukan pengecualian. Berikut ini contoh sederhana:

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

Saat dijalankan, ini menghasilkan:

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

Ini menunjukkan bahwa Anda bisa instantiate subclass yang mewarisi dari kelas dasar, tetapi Anda tidak bisa membuat instance kelas dasar secara langsung.

Irv Kalb
sumber
11

Sebagian besar jawaban Sebelumnya benar tetapi berikut adalah jawaban dan contoh untuk Python 3.7. Ya, Anda bisa membuat kelas dan metode abstrak. Sama seperti pengingat, kadang-kadang sebuah kelas harus mendefinisikan metode yang secara logis milik kelas, tetapi kelas itu tidak bisa menentukan cara mengimplementasikan metode tersebut. Sebagai contoh, di kelas Orangtua dan Bayi di bawah ini mereka berdua makan tetapi implementasinya akan berbeda untuk masing-masing karena bayi dan orang tua makan jenis makanan yang berbeda dan berapa kali mereka makan berbeda. Jadi, makan metode subclass menimpa AbstractClass.eat.

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

KELUARAN:

moms ----------
eat solid food 3 times each day
infants ----------
Milk only 3 times or more each day
grepit
sumber
Mungkin import abctidak berguna.
Benyamin Jafari
Bisakah kita menulis @abstractmethod pada fungsi init juga?
variabel
+1 Kenapa @abstrakmethod def makan (mandiri)? Jika kelas ini abstrak dan karenanya tidak dimaksudkan untuk dipakai mengapa Anda lulus (diri) untuk makan? Ini berfungsi dengan baik tanpa itu
VMMF
7

Yang ini akan bekerja di python 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
Rajkumar SR
sumber
4

Seperti yang dijelaskan dalam jawaban lain, ya Anda bisa menggunakan kelas abstrak di Python menggunakan abcmodul . Di bawah ini saya memberikan contoh aktual menggunakan abstrak @classmethod, @propertydan @abstractmethod(menggunakan Python 3.6+). Bagi saya biasanya lebih mudah untuk memulai dengan contoh-contoh yang saya dapat dengan mudah menyalin & menempel; Saya harap jawaban ini juga bermanfaat bagi orang lain.

Pertama mari kita membuat kelas dasar yang disebut 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

BaseKelas kami akan selalu memiliki a from_dict classmethod, a property prop1(yang hanya baca) dan a property prop2(yang juga dapat diatur) serta fungsi yang disebut do_stuff. Apa pun kelas yang sekarang dibangun berdasarkan Baseharus menerapkan semua ini untuk metode / properti. Harap dicatat bahwa untuk metode menjadi abstrak, diperlukan dua dekorator - classmethoddan abstrak property.

Sekarang kita bisa membuat kelas Aseperti ini:

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

Semua metode / properti yang diperlukan diimplementasikan dan kami - tentu saja - juga dapat menambahkan fungsi tambahan yang bukan bagian dari Base(di sini i_am_not_abstract:).

Sekarang kita bisa melakukan:

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

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

Seperti yang diinginkan, kami tidak dapat mengatur prop1:

a.prop1 = 100

akan kembali

AttributeError: tidak dapat mengatur atribut

from_dictMetode kami juga berfungsi dengan baik:

a2.prop1
# prints 20

Jika sekarang kita mendefinisikan kelas kedua Bseperti ini:

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

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

dan mencoba untuk instantiate objek seperti ini:

b = B('iwillfail')

kami akan mendapatkan kesalahan

TypeError: Tidak dapat membuat instance kelas abstrak B dengan metode abstrak do_stuff, from_dict, prop2

daftar semua hal yang didefinisikan di Basemana kami tidak menerapkannya B.

Cleb
sumber
2

ini juga berfungsi dan sederhana:

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()
Giovanni Angeli
sumber
2

Anda juga dapat memanfaatkan metode __new__ untuk keuntungan Anda. Anda baru saja melupakan sesuatu. Metode __new__ selalu mengembalikan objek baru sehingga Anda harus mengembalikan metode baru superclassnya. Lakukan sebagai berikut.

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)

Saat menggunakan metode baru, Anda harus mengembalikan objek, bukan kata kunci None. Itu semua yang Anda lewatkan.

Michael
sumber
2

Saya menemukan jawaban yang diterima, dan semua yang lain aneh, karena mereka lolos selfke kelas abstrak. Kelas abstrak tidak dipakai jadi tidak bisa memiliki self.

Jadi coba ini, ini berhasil.

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()
Sean Bradley
sumber
1
Bahkan dokumentasi abc menggunakan diri dalam tautan
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
Shivam Bharadwaj
sumber
Nice @Shivam Bharadwaj, saya melakukan hal yang sama
Muhammad Irfan
-3

Dalam cuplikan kode Anda, Anda juga bisa menyelesaikan ini dengan memberikan implementasi untuk __new__metode di subkelas, juga:

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

Tetapi ini adalah retasan dan saya menyarankan Anda untuk tidak melakukannya, kecuali jika Anda tahu apa yang Anda lakukan. Untuk hampir semua kasus, saya menyarankan Anda untuk menggunakan abcmodul, yang disarankan orang lain sebelum saya.

Juga ketika Anda membuat (base) kelas baru, membuat subclass object, seperti ini: class MyBaseClass(object):. Saya tidak tahu apakah itu jauh lebih penting lagi, tetapi ini membantu mempertahankan konsistensi gaya pada kode Anda

NlightNFotis
sumber
-4

Hanya tambahan cepat ke jawaban old-school @ TimGilbert ... Anda dapat membuat init () metode abstrak kelas dasar Anda membuat pengecualian dan itu akan mencegahnya dari dipakai, bukan?

>>> 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! 
Dave Wade-Stein
sumber
6
Ini akan mencegah subclass dari manfaat dengan kode init umum yang secara logis harus ada di kelas dasar umum mereka.
Arpit Singh
Cukup adil. Begitu banyak untuk sekolah tua.
Dave Wade-Stein