我了解PHP或Java中的虚方法。
在Python中如何实现虚方法?
还是说我需要在抽象类中定义一个空方法并重写它?
我了解PHP或Java中的虚方法。
在Python中如何实现虚方法?
还是说我需要在抽象类中定义一个空方法并重写它?
当然,你甚至不必在基类中定义一个方法。在Python中,方法比虚函数更好-它们是完全动态的,因为Python中的类型是鸭子类型。
class Dog:
def say(self):
print "hau"
class Cat:
def say(self):
print "meow"
pet = Dog()
pet.say() # prints "hau"
another_pet = Cat()
another_pet.say() # prints "meow"
my_pets = [pet, another_pet]
for a_pet in my_pets:
a_pet.say()
在Python中,即使Cat
和Dog
没有从共同的基类派生,它们也可以实现相同的行为 - 这个功能是默认提供的。尽管如此,一些程序员更喜欢以更严格的方式定义他们的类层次结构,以便更好地记录并强制执行某些类型的严格性。这也是可能的 - 例如,可以查看abc
标准模块。raise NotImplementedError()
:动态类型检查
这是建议用于"抽象"基类上未实现方法的"纯虚拟方法"的异常。
https://docs.python.org/3.5/library/exceptions.html#NotImplementedError 上说:
这个异常派生自
RuntimeError
。在用户自定义的基类中,当抽象方法需要派生类覆盖方法时,应该引发此异常。
正如其他人所说,这主要是一种文档约定,不是必需的,但这样您就可以获得比缺少属性错误更有意义的异常。
dynamic.py
class Base(object):
def virtualMethod(self):
raise NotImplementedError()
def usesVirtualMethod(self):
return self.virtualMethod() + 1
class Derived(Base):
def virtualMethod(self):
return 1
print Derived().usesVirtualMethod()
Base().usesVirtualMethod()
给出:2
Traceback (most recent call last):
File "./dynamic.py", line 13, in <module>
Base().usesVirtualMethod()
File "./dynamic.py", line 6, in usesVirtualMethod
return self.virtualMethod() + 1
File "./dynamic.py", line 4, in virtualMethod
raise NotImplementedError()
NotImplementedError
typing.Protocol
: 静态类型检查(Python 3.8)
Python 3.8新增了typing.Protocol
,现在我们也可以静态类型检查子类上是否实现了虚方法。
protocol.py
from typing import Protocol
class CanFly(Protocol):
def fly(self) -> str:
pass
def fly_fast(self) -> str:
return 'CanFly.fly_fast'
class Bird(CanFly):
def fly(self):
return 'Bird.fly'
def fly_fast(self):
return 'Bird.fly_fast'
class FakeBird(CanFly):
pass
assert Bird().fly() == 'Bird.fly'
assert Bird().fly_fast() == 'Bird.fly_fast'
# mypy error
assert FakeBird().fly() is None
# mypy error
assert FakeBird().fly_fast() == 'CanFly.fly_fast'
如果我们运行此文件,断言将通过,因为我们没有添加任何动态类型检查:
python protocol.py
但是如果我们对mypy
进行类型检查:
python -m pip install --user mypy
mypy protocol.py
我们得到了预期的错误:
protocol.py:22: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
protocol.py:24: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
有些不幸的是,错误检查仅在实例化时捕获错误,而不是在类定义时。
typing.Protocol
将方法视为抽象方法,当它们的主体是“空”时
我不确定他们认为什么是空的,但是以下所有内容都被认为是空的:
pass
...
省略对象raise NotImplementedError()
因此,最好的可能性很可能是:
protocol_empty.py
from typing import Protocol
class CanFly(Protocol):
def fly(self) -> None:
raise NotImplementedError()
class Bird(CanFly):
def fly(self):
return None
class FakeBird(CanFly):
pass
Bird().fly()
FakeBird().fly()
失败是预期的:
protocol_empty.py:15: error: Cannot instantiate abstract class "FakeBird" with abstract attribute "fly"
protocol_empty.py:15: note: The following method was marked implicitly abstract because it has an empty function body: "fly". If it is not meant to be abstract, explicitly return None.
但是,如果我们替换:
raise NotImplementedError()
例如,随意添加一个“非空”的陈述:
x = 1
如果 mypy
没有将它们视为虚拟的,就不会产生错误。
@abc.abstractmethod
:动态 + 静态 + 文档同时实现
之前在这里提到过:https://dev59.com/pG445IYBdhLWcg3wwcyg#19316077 ,但在 Python 3 中 metaclass
语法已经改变:
class C(metaclass=abc.ABCMeta):
不再使用Python 2:
class C:
__metaclass__=abc.ABCMeta
现在要使用之前在https://dev59.com/pG445IYBdhLWcg3wwcyg#19316077提到的@abc.abstractmethod
,需要:
abc_cheat.py
import abc
class C(metaclass=abc.ABCMeta):
@abc.abstractmethod
def m(self, i):
pass
try:
c = C()
except TypeError:
pass
else:
assert False
关于 raise NotImplementedError
和 Protocol
:
https://peps.python.org/pep-0544 中简要提到了这两种方法。
例如:
abc_bad.py
#!/usr/bin/env python
import abc
class CanFly(metaclass=abc.ABCMeta):
'''
doc
'''
@abc.abstractmethod
def fly(self) -> str:
'''
doc
'''
pass
class Bird(CanFly):
'''
doc
'''
def fly(self):
'''
doc
'''
return 'Bird.fly'
class Bat(CanFly):
'''
doc
'''
pass
def send_mail(flyer: CanFly) -> str:
'''
doc
'''
return flyer.fly()
assert send_mail(Bird()) == 'Bird.fly'
assert send_mail(Bat()) == 'Bat.fly'
那么:
mypy abc_bad.py
失败,正是预期的结果:
main.py:40: error: Cannot instantiate abstract class "Bat" with abstract attribute "fly"
Sphinx:如何在文档中显示
在上述提到的方法中,只有一个会在Sphinx文档输出中显示:@abc.abstractmethod
。
结语
参考文献:
typing.Protocol
PEP在Python 3.10.7、mypy 0.982和Ubuntu 21.10上测试通过。
Python的方法始终是虚拟的。
实际上,在Python 2.6版本中提供了称为抽象基类的东西,您可以像这样显式设置虚拟方法:
from abc import ABCMeta
from abc import abstractmethod
...
class C:
__metaclass__ = ABCMeta
@abstractmethod
def my_abstract_method(self, ...):
只要这个类没有继承已经使用元类的类,它就可以非常好地工作。
class C(metaclass=abc.ABCMeta)
:https://dev59.com/pG445IYBdhLWcg3wwcyg#38717503 - Ciro Santilli OurBigBook.comPython的方法始终是虚拟的
就像Ignacio所说的,使用类继承可能是实现您想要的功能的更好方法。
class Animal:
def __init__(self,name,legs):
self.name = name
self.legs = legs
def getLegs(self):
return "{0} has {1} legs".format(self.name, self.legs)
def says(self):
return "I am an unknown animal"
class Dog(Animal): # <Dog inherits from Animal here (all methods as well)
def says(self): # <Called instead of Animal says method
return "I am a dog named {0}".format(self.name)
def somethingOnlyADogCanDo(self):
return "be loyal"
formless = Animal("Animal", 0)
rover = Dog("Rover", 4) #<calls initialization method from animal
print(formless.says()) # <calls animal say method
print(rover.says()) #<calls Dog says method
print(rover.getLegs()) #<calls getLegs method from animal class
结果应该是:
I am an unknown animal
I am a dog named Rover
Rover has 4 legs
class A:
def prop_a(self):
return 1
def prop_b(self):
return 10 * self.prop_a()
class B(A):
def prop_a(self):
return 2
>>> B().prop_b()
20
>>> A().prob_b()
10
class A:
def __prop_a(self):
return 1
def prop_b(self):
return 10 * self.__prop_a()
class B(A):
def __prop_a(self):
return 2
>>> B().prop_b()
10
>>> A().prob_b()
10
prop_a()
变成了dunder方法。prop_b()
的行为,就无法在派生类中更改prop_a()
的行为。 Raymond Hettinger的这个非常好的演讲给出了一个使用案例,说明这种情况是不方便的。Python 3.6 引入了__init_subclass__
,这使得你可以轻松地执行以下操作:
class A:
def method(self):
'''method needs to be overwritten'''
return NotImplemented
def __init_subclass__(cls):
if cls.method is A.method:
raise NotImplementedError(
'Subclass has not overwritten method {method}!')