如何在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__方法。

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


当前回答

在你的代码片段中,你也可以通过在子类中提供__new__方法的实现来解决这个问题,类似地:

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

但这是一个黑客,我建议你不要这样做,除非你知道你在做什么。对于几乎所有的情况,我建议您使用abc模块,在我之前的其他人已经建议过了。

同样,当你创建一个新的(基)类时,让它成为子类对象,就像这样:我不知道它是否还有那么重要,但它有助于保持代码的风格一致性

其他回答

你也可以利用__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关键字。这就是你错过的。

你可以通过扩展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模块创建抽象类。使用abstractmethod装饰器来声明方法抽象,并使用三种方法之一来声明类抽象,具体取决于您的Python版本。

在Python 3.4及以上版本中,可以从ABC继承。在早期版本的Python中,需要将类的元类指定为ABCMeta。指定元类在Python 3和Python 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>
 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

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