如何在Python中使类或方法抽象?

我尝试像这样重新定义__new__():

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

但是现在,如果我创建一个从F继承的类G,像这样:

class G(F):
    pass

然后,我也不能实例化G,因为它调用它的超类的__new__方法。

是否有更好的方法来定义抽象类?


当前回答

你可以通过扩展ABC(抽象基类)来创建一个抽象类,并且可以在抽象类中使用@abstractmethod创建抽象方法,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

并且,要使用抽象类,它应该由子类扩展,并且子类应该覆盖抽象类的抽象方法,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal): # Extends "Animal" abstract class
    def sound(self): # Overrides "sound()" abstract method
        print("Meow!!")

obj = Cat()
obj.sound()

输出:

Meow!!

并且,抽象方法可以有code而不是pass,并且可以由子类调用,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        print("Wow!!") # Here

class Cat(Animal):
    def sound(self):
        super().sound() # Here
        
obj = Cat()
obj.sound()

输出:

Wow!!

而且,抽象类可以有变量和非抽象方法,可以由子类调用,非抽象方法不需要被子类覆盖,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass
    
    def __init__(self): # Here
        self.name = "John" # Here
    
    x = "Hello" # Here
    
    def test1(self): # Here
        print("Test1")
    
    @classmethod # Here
    def test2(cls):
        print("Test2")
        
    @staticmethod # Here
    def test3():
        print("Test3")

class Cat(Animal):
    def sound(self):
        print(self.name) # Here
        print(super().x) # Here
        super().test1()  # Here
        super().test2()  # Here
        super().test3()  # Here

obj = Cat()
obj.sound()

输出:

John
Hello
Test1
Test2
Test3

并且,您可以在抽象类中定义抽象类和静态方法以及抽象getter、setter和delete,如下所示。*@abstractmethod必须是最内部的装饰器,否则会发生错误,你可以看到我的回答,解释了更多关于抽象的getter, setter和delete:

from abc import ABC, abstractmethod

class Person(ABC):

    @classmethod
    @abstractmethod # The innermost decorator
    def test1(cls):
        pass
    
    @staticmethod
    @abstractmethod # The innermost decorator
    def test2():
        pass

    @property
    @abstractmethod # The innermost decorator
    def name(self):
        pass

    @name.setter
    @abstractmethod # The innermost decorator
    def name(self, name):
        pass

    @name.deleter
    @abstractmethod # The innermost decorator
    def name(self):
        pass

然后,你需要在子类中重写它们,如下所示:

class Student(Person):
    
    def __init__(self, name):
        self._name = name
    
    @classmethod
    def test1(cls): # Overrides abstract class method
        print("Test1")
    
    @staticmethod
    def test2(): # Overrides abstract static method
        print("Test2")
    
    @property
    def name(self): # Overrides abstract getter
        return self._name
    
    @name.setter
    def name(self, name): # Overrides abstract setter
        self._name = name
    
    @name.deleter
    def name(self): # Overrides abstract deleter
        del self._name

然后,你可以实例化子类并调用它们,如下所示:

obj = Student("John") # Instantiates "Student" class
obj.test1() # Class method
obj.test2() # Static method
print(obj.name) # Getter
obj.name = "Tom" # Setter
print(obj.name) # Getter
del obj.name # Deleter
print(hasattr(obj, "name"))

输出:

Test1
Test2
John 
Tom  
False

并且,如果你尝试实例化一个抽象类,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

obj = Animal()

出现以下错误:

不能实例化抽象类Animal与抽象方法sound

并且,如果你没有在子类中重写抽象类的抽象方法,并且你实例化了子类,如下所示:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj = Cat() # Here

出现以下错误:

不能用抽象方法实例化抽象类Cat

并且,如果你在非抽象类中定义了一个抽象方法,它没有扩展ABC,那么抽象方法是一个正常的实例方法,所以即使非抽象类被实例化,即使子类没有覆盖非抽象类的抽象方法,也不会出现错误,如下所示:

from abc import ABC, abstractmethod

class Animal: # Doesn't extend "ABC"
    @abstractmethod # Here
    def sound(self):
        print("Wow!!")

class Cat(Animal):
    pass # Doesn't override "sound()" abstract method

obj1 = Animal() # Here
obj1.sound()

obj2 = Cat() # Here
obj2.sound()

输出:

Wow!!
Wow!!

另外,你可以替换Cat类扩展下面的Animal class:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat(Animal):
    def sound(self):
        print("Meow!!")

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

下面是register()代码:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def sound(self):
        pass

# ↓↓↓ Here ↓↓↓

class Cat:
    def sound(self):
        print("Meow!!")
        
Animal.register(Cat)

# ↑↑↑ Here ↑↑↑

print(issubclass(Cat, Animal))

然后,上面的两个代码输出下面相同的结果,显示Cat class是Animal class的子类:

True

其他回答

正如在其他答案中解释的那样,是的,您可以使用abc模块在Python中使用抽象类。下面我给出了一个实际的例子,使用抽象的@classmethod, @property和@abstractmethod(使用Python 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

我们的基类总是有一个from_dict类方法,一个属性prop1(只读)和一个属性prop2(也可以设置),以及一个名为do_stuff的函数。现在基于Base构建的任何类都必须实现所有这四个方法/属性。请注意,对于一个抽象的方法,需要两个装饰器——classmethod和abstract 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

将返回

AttributeError:不能设置属性

我们的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')

我们会得到一个错误

不能用抽象方法实例化抽象类B Do_stuff, from_dict, prop2

列出所有在Base中定义的东西,这些东西我们没有在B中实现。

你也可以利用__new__方法。你只是忘记了一些东西。 __new__方法总是返回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)

在使用新方法时,必须返回对象,而不是None关键字。这就是你错过的。

Most Previous answers were correct but here is the answer and example for Python 3.7. Yes, you can create an abstract class and method. Just as a reminder sometimes a class should define a method which logically belongs to a class, but that class cannot specify how to implement the method. For example, in the below Parents and Babies classes they both eat but the implementation will be different for each because babies and parents eat a different kind of food and the number of times they eat is different. So, eat method subclasses overrides 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())

输出:

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

这里有一个不需要处理ABC模块的非常简单的方法。

在你想要成为抽象类的类的__init__方法中,你可以检查self的“类型”。如果self类型是基类,则调用者试图实例化基类,因此引发异常。这里有一个简单的例子:

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

这表明您可以实例化继承自基类的子类,但不能直接实例化基类。

这个方法也很简单:

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