Có thể tạo các lớp trừu tượng trong Python không?


321

Làm thế nào tôi có thể tạo một lớp hoặc phương thức trừu tượng trong Python?

Tôi đã cố gắng xác định lại __new__()như vậy:

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

nhưng bây giờ nếu tôi tạo một lớp Gkế thừa từ Fnhư vậy:

class G(F):
    pass

sau đó tôi cũng không thể khởi tạo G, vì nó gọi __new__phương thức siêu hạng của nó .

Có cách nào tốt hơn để định nghĩa một lớp trừu tượng?


Có, bạn có thể tạo các lớp trừu tượng trong python với mô đun abc (lớp cơ sở trừu tượng). Trang web này sẽ giúp bạn với nó: http://docs.python.org/2/l
Library / abc.html

Câu trả lời:


554

Sử dụng abcmô-đun để tạo các lớp trừu tượng. Sử dụng trình abstractmethodtrang trí để khai báo một tóm tắt phương thức và khai báo một tóm tắt lớp bằng một trong ba cách, tùy thuộc vào phiên bản Python của bạn.

Trong Python 3.4 trở lên, bạn có thể kế thừa từ ABC. Trong các phiên bản trước của Python, bạn cần chỉ định siêu dữ liệu của lớp là ABCMeta. Chỉ định siêu dữ liệu có cú pháp khác nhau trong Python 3 và Python 2. Ba khả năng được hiển thị bên dưới:

# 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

Dù bạn sử dụng theo cách nào, bạn sẽ không thể khởi tạo một lớp trừu tượng có các phương thức trừu tượng, nhưng sẽ có thể khởi tạo một lớp con cung cấp các định nghĩa cụ thể về các phương thức đó:

>>> 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
@ababmetmethod làm gì? Tại sao bạn cần nó? Nếu lớp đã được thiết lập là trừu tượng thì trình biên dịch / phiên dịch có biết rằng tất cả các phương thức là từ lớp trừu tượng trong câu hỏi không?
Charlie Parker

29
@CharlieParker - Làm @abstractmethodcho nó để chức năng trang trí phải được ghi đè trước khi lớp có thể được khởi tạo. Từ các tài liệu:A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden.
Tên giả

6
@CharlieParker - Về cơ bản, nó cho phép bạn định nghĩa một lớp theo cách mà lớp con phải thực hiện một bộ phương thức được chỉ định để khởi tạo tất cả.
Tên giả

13
Có cách nào để cấm người dùng tạo Tóm tắt () mà không cần bất kỳ phương thức @ababmetmethod nào không?
Joe

2
@Joe có vẻ như bạn cũng có thể sử dụng @abstractmethodcho __init__phương thức, xem stackoverflow.com/q/44800659/547270
Scrutari

108

Cách thức cũ (trước PEP 3119 ) để làm điều này chỉ là raise NotImplementedErrortrong lớp trừu tượng khi một phương thức trừu tượng được gọi.

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

Điều này không có các thuộc tính đẹp giống như sử dụng abcmô-đun. Bạn vẫn có thể khởi tạo chính lớp cơ sở trừu tượng và bạn sẽ không tìm thấy lỗi của mình cho đến khi bạn gọi phương thức trừu tượng trong thời gian chạy.

Nhưng nếu bạn đang xử lý một nhóm nhỏ các lớp đơn giản, có thể chỉ bằng một vài phương thức trừu tượng, cách tiếp cận này dễ hơn một chút so với cố gắng lội qua abctài liệu.


1
Đánh giá cao sự đơn giản và hiệu quả của phương pháp này.
mohit6up

Haha, tôi đã thấy điều này ở mọi nơi trong khóa học OMSCS của tôi và không biết nó là gì :)
mLstudent33

18

Đây là một cách rất dễ dàng mà không phải đối phó với mô-đun ABC.

Trong __init__phương thức của lớp mà bạn muốn trở thành một lớp trừu tượng, bạn có thể kiểm tra "loại" của bản thân. Nếu kiểu bản thân là lớp cơ sở, thì người gọi đang cố gắng khởi tạo lớp cơ sở, vì vậy hãy đưa ra một ngoại lệ. Đây là một ví dụ đơn giản:

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

Khi chạy, nó tạo ra:

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

Điều này cho thấy rằng bạn có thể khởi tạo một lớp con kế thừa từ một lớp cơ sở, nhưng bạn không thể khởi tạo trực tiếp lớp cơ sở.


11

Hầu hết các câu trả lời trước đây đều đúng nhưng đây là câu trả lời và ví dụ cho Python 3.7. Có, bạn có thể tạo một lớp trừu tượng và phương thức. Cũng giống như một lời nhắc nhở đôi khi một lớp nên định nghĩa một phương thức thuộc về logic một lớp, nhưng lớp đó không thể chỉ định cách thực hiện phương thức. Ví dụ, trong các lớp Cha mẹ và Trẻ sơ sinh, cả hai đều ăn nhưng cách thực hiện sẽ khác nhau đối với nhau vì trẻ sơ sinh và cha mẹ ăn một loại thực phẩm khác nhau và số lần chúng ăn là khác nhau. Vì vậy, ăn các lớp con phương thức ghi đè 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())

ĐẦU RA:

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

Có lẽ import abclà vô dụng.
Benyamin Jafari

Chúng ta có thể viết @ababmetmethod trên hàm init không?
biến

+1 Tại sao @ababmetmethod def ăn (tự)? Nếu lớp này là trừu tượng và do đó không có nghĩa là ngay lập tức tại sao bạn vượt qua (tự) để ăn? Nó hoạt động tốt mà không có nó
VMMF

7

Cái này sẽ làm việc trong 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

4

Như đã giải thích trong các câu trả lời khác, có, bạn có thể sử dụng các lớp trừu tượng trong Python bằng abcmô-đun . Dưới đây tôi đưa ra một ví dụ thực tế sử dụng trừu tượng @classmethod, @property@abstractmethod(sử dụng Python 3.6+). Đối với tôi thường dễ dàng hơn để bắt đầu với các ví dụ tôi có thể dễ dàng sao chép & dán; Tôi hy vọng câu trả lời này cũng hữu ích cho những người khác.

Trước tiên chúng ta hãy tạo một lớp cơ sở gọi là 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

BaseLớp của chúng ta sẽ luôn có a from_dict classmethod, a property prop1(chỉ đọc) và a property prop2(cũng có thể được đặt) cũng như một hàm được gọi do_stuff. Bất cứ lớp nào được xây dựng dựa trên Basesẽ phải thực hiện tất cả các lớp này cho các phương thức / thuộc tính. Xin lưu ý rằng để một phương thức trở nên trừu tượng, cần có hai trang trí - classmethodvà trừu tượng property.

Bây giờ chúng ta có thể tạo một lớp Anhư thế này:

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

Tất cả các phương thức / thuộc tính bắt buộc được triển khai và tất nhiên chúng ta có thể - cũng thêm các hàm bổ sung không phải là một phần của Base(ở đây i_am_not_abstract:).

Bây giờ chúng ta có thể làm:

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

a1.prop1
# prints 10

a1.prop2
# prints 'stuff'

Theo mong muốn, chúng tôi không thể đặt prop1:

a.prop1 = 100

sẽ trở lại

AttributionError: không thể đặt thuộc tính

Ngoài ra from_dictphương pháp của chúng tôi hoạt động tốt:

a2.prop1
# prints 20

Nếu bây giờ chúng ta định nghĩa một lớp thứ hai Bnhư thế này:

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

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

và cố gắng khởi tạo một đối tượng như thế này:

b = B('iwillfail')

chúng tôi sẽ nhận được một lỗi

TypeError: Không thể khởi tạo lớp B trừu tượng bằng các phương thức trừu tượng do_ ware, from_dict, prop2

liệt kê tất cả những điều được xác định trong Baseđó chúng tôi đã không thực hiện trong B.


2

điều này cũng hoạt động và đơn giản:

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

Bạn cũng có thể khai thác phương thức __new__ để lợi thế của bạn. Bạn chỉ cần quên một cái gì đó. Phương thức __new__ luôn trả về đối tượng mới, do đó bạn phải trả về phương thức mới của siêu lớp của nó. Làm như sau.

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)

Khi sử dụng phương thức mới, bạn phải trả về đối tượng, không phải từ khóa Không có. Đó là tất cả những gì bạn bỏ lỡ.


2

Tôi tìm thấy câu trả lời được chấp nhận, và tất cả những câu trả lời khác, vì chúng chuyển selfsang một lớp trừu tượng. Một lớp trừu tượng không được khởi tạo nên không thể cóself .

Vì vậy, hãy thử điều này, nó hoạt động.

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
Ngay cả tài liệu abc cũng sử dụng tự trong liên kết
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

Nice @Shivam Bharadwaj, tôi đã làm theo cách tương tự
Muhammad Irfan

-3

Trong đoạn mã của bạn, bạn cũng có thể giải quyết điều này bằng cách cung cấp một triển khai cho __new__phương thức trong lớp con, tương tự:

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

Nhưng đây là một hack và tôi khuyên bạn chống lại nó, trừ khi bạn biết những gì bạn đang làm. Đối với gần như tất cả các trường hợp tôi khuyên bạn nên sử dụng abcmô-đun, mà những người khác trước tôi đã đề xuất.

Ngoài ra khi bạn tạo một lớp (cơ sở) mới, hãy tạo một lớp con object, như thế này : class MyBaseClass(object):. Tôi không biết nó có còn ý nghĩa nữa hay không, nhưng nó giúp duy trì tính nhất quán về kiểu dáng trong mã của bạn


-4

Chỉ là một bổ sung nhanh chóng cho câu trả lời trường học cũ của @ TimGilbert ... bạn có thể làm cho phương thức init () của lớp cơ sở trừu tượng của mình ném một ngoại lệ và điều đó sẽ ngăn không cho nó được khởi tạo, phải không?

>>> 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
Điều này sẽ ngăn các lớp con được hưởng lợi với bất kỳ mã init phổ biến nào có mặt logic trong lớp cơ sở chung của chúng.
Arpit Singh

Đủ công bằng. Quá nhiều cho trường học cũ.
Dave Wade-Stein
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.