在本教程中,您将掌握面向对象编程的另一个重要概念,即多态,以及在 Python 中实现多态的不同方法,例如
等概念,并进行详细讲解。
多态是面向对象编程的基本构建块之一。为了更清晰地理解它,让我们将“多态”一词分解为“poly”和“morphism”。通俗地说,“poly”表示“多”,“morph”表示“形态”。所以多态就是“多种形态”。
在编程中,多态可以定义为单个实体具有多种形态的过程。Python 作为一个 exclusively works with objects 的语言,在对象上体现了多态性,这意味着对象可以呈现多种形态。
在现实生活中,我们人类就是多态的,因为我们在不同的情境下表现不同。例如,我们在办公室和在家的行为方式就不同。
鸭子类型是实现多态的一种方式,它与动态类型密切相关。鸭子类型得名于一句名言:“如果一个东西像鸭子一样走路,像鸭子一样叫,像鸭子一样游泳,那么它就应该是一只鸭子”。这意味着,如果某个东西的行为举止像鸭子,那么它就是一只鸭子。例如,如果一个人像鸭子一样走路,像鸭子一样游泳,那么这个人可能就是一只鸭子。
Python 和 Javascript 等支持动态类型的编程语言广泛使用了鸭子类型概念。这些语言独特且常见的特点是它们不显式声明变量类型。如果我们用另一种数据类型重新赋值同一个变量,编译器不会报错。请观察以下示例:
x = 5
print(type(x))
x = 'Learn eTutorial'
print(type(x))
输出
<class 'int'> <class 'str'>
在这里,我们最初将变量 x 赋值为整数值 5,使其成为 int 类型。稍后,我们用字符串值‘Learn eTutorials’重新赋值同一个变量,使其成为 str 类型。这表明动态类型适用于 int、str、list 等内置类。
同样,我们可以通过鸭子类型在自定义类上实现动态类型。在鸭子类型中,重点在于方法和属性的存在,我们不检查类型和类,因为它们不太重要。
class Duck:
def walk_swim(self):
print("I'm a duck, and I can walk and swim.")
class Robot:
def walk_swim(self):
print("I'm a Robot, and I can walk and swim.")
class Fish:
def swim(self):
print("I'm a fish, and I can swim, but not walk.")
for bird in Duck(),Robert(),Fish():
bird.walk_swim()
输出
I'm a duck, and I can walk and swim. I'm a Robot, and I can walk and swim. Traceback (most recent call last): File "poly_ex.py", line 118, inbird.walk_swim() AttributeError: 'Fish' object has no attribute 'walk_swim'
在上面的代码片段中,bird 是每个类的对象,用于测试每个类中 walk_swim() 方法的存在性。Duck 类通过了测试,因为它确实可以走路和游泳。Robot 类也是如此,因为它也实现了 walk_swim() 方法。但是 Fish 类最终出错,因为它没有实现 walk_swim() 方法,未能通过鸭子测试评估。
鸭子类型的实际应用包括迭代器、可调用对象以及 len() 方法的排序。关键要点是,在使用鸭子类型时,传递什么类的对象并不重要,重要的是对象是否具有相关的方法。
在 Python 中实现多态的另一种方式是通过运算符重载。回想一下什么是运算符。任何执行某种计算的独特符号都称为运算符。所以运算符重载是使用相同的运算符,例如 +,根据使用的操作数,在多种形态中进行操作的过程。查看下面的代码片段,展示了运算符‘+’行为的变化:
a = 1
b = 2
print(a+b)
c ='Python'
print(c*3)
d = 'Programming'
e = 'Language'
print(d+e)
输出
3 PythonPythonPython ProgrammingLanguage
在这里,加号运算符对两个整数执行加法,同时对两个字符串执行连接。
您可能想知道这是如何可能的,以及此运算符背后实际发生了什么。让我们以两个整数相加为例,对其进行内部分解,以了解其背后的过程。
始终记住,Python 中发生的一切都完全基于对象。这里 a+b 是一个带有两个操作数的表达式,操作数 a 和 b 的类型是 'int',这意味着 a 和 b 是int 类的两个属性。显然,类必须有一些方法来计算其操作数。所以当你使用 a+b 时,Python 会在内部调用 魔术方法 - int.__add__(a,b)方法。
a = 1
b = 2
print(int.__add__(a,b))
c ='Python'
print(str.__mul__('Python',3))
d = 'Programming'
e = 'Language'
print(str.__add__('Programming','Language'))
输出
3 PythonPythonPython ProgrammingLanguage
所以当你使用一个
obj.__add__() 方法。obj.__sub__() 方法。obj.__mul__() 方法。在 Python 中,运算符重载适用于 int、str、list 等内置类。但是,我们也可以将运算符的可操作性扩展到用户定义的类。这可以通过运算符重载来实现。
class Quantity:
def __init__(self,w1,w2):
self.w1=w1
self.w2=w2
def __str__(self):
return "{} ,{}".format(self.w1,self.w2)
q1=Quantity(5,6)
q2=Quantity(7,8)
print(q1)
print(q2)
print(q1+q2)
输出
(5 ,6) (7 ,8) Traceback (most recent call last): File "poly_ex.py", line 51, inprint(q1+q2) TypeError: unsupported operand type(s) for +: 'Quantity' and 'Quantity'
让我们分解示例以清楚地理解它。
__str__() 方法与 print 函数的作用相同。这里,此方法返回指定的数量。那么,我们如何以用户定义的方式实现运算符的可操作性呢?我们只需要在类中定义一个额外的方法,使 + 运算符可以对对象进行操作。为此,我们在类中实现了魔术方法 __add__()。
class Quantity:
def __init__(self,w1,w2):
self.w1=w1
self.w2=w2
def __str__(self):
return "{} ,{}".format(self.w1,self.w2)
def __add__(self,other):
w1=self.w1+other.w1
w2=self.w2+other.w2
return (w1,w2)
q1=Quantity(5,6)
q2=Quantity(7,8)
print(q1)
print(q2)
print(q1+q2)
输出
(5 ,6) (7 ,8) (12, 14)
当你调用 print(q1+q2) 时,Python 会调用 add 方法,形式为 Quantity.__add__(q1,q2),这等同于 q1.__add__(q2)。
魔术方法或双下划线方法是 Python 中前后都有双下划线的方法。Dunder 是 Double Under 的缩写。下表列出了一些常用的 Python 魔术方法。
| 运算符 | 表达式 | 魔术方法 |
|---|---|---|
| 加法 | q1+ q2 | ob.__add__(q1,q2) |
| 减法 | q1 – q2 | ob.__sub__(q1,q2) |
| 乘法 | q1 * q2 | ob.__mul__(q1,q2) |
| 除法 | q1 / q2 | ob.__truediv__(q1,q2) |
| 幂 | q1 ** q2 | ob.__pow__(q1,q2) |
| 整除 | q1 // q2 | ob.__floordiv__(q1,q2) |
| 模运算符 | q1 % q2 | ob.__mod__(q1,q2) |
| 按位左移 | q1 << q2 | ob.__lshift__(q1,q2) |
| 按位右移 | q1 >> q2 | ob.__rshift__(q1,q2) |
| 按位非 | ~q1 | ob.__invert__(q1) |
| 按位与 | q1 & q2 | ob.__and__(q1,q2) |
| 按位或 | q1 | q2 | ob.__or__(q1,q2) |
| 按位异或 | q1 ^ q2 | ob.__xor__(q1,q2) |
| 小于 | q1 < q2 | ob.__lt__(q1,q2) |
| 小于等于 | q1 <= q2 | ob.__le__(q1,q2) |
| 大于 | q1 > q2 | ob.__gt__(q1,q2) |
| 大于等于 | q1 >= q2 | ob.__ge__(q1,q2) |
| 等于 | q1 == q2 | ob.__eq__(q1,q2) |
| 不等于 | q1 != q2 | ob.__ne__(q1,q2) |
方法重载,在面向对象编程中,是指方法根据传递给它的参数以不同方式表现的能力。方法重载支持编译时多态。
G说得更清楚一点,如果你有一个类,其中有两个同名但参数数量不同的方法,那么这个方法就被称为重载方法。请观察下面的代码片段:
class A:
def add(self,a,b):
s = a+b
print(s)
def add(self,a,b,c):
s = a+b+c
print(s)
这里我们有一个名为 A 的类,其中包含两个方法。这两个方法都使用相同的名称,即 add(),但是两个方法的参数数量不同。一个方法有两个参数,而另一个方法有三个参数。这里,add() 方法被重载了。
现在让我们看看在 Python 中执行时的输出。
class A:
def add(self,a,b):
s = a+b
print(s)
def add(self,a,b,c):
s = a+b+c
print(s)
ob = A()
ob.add(10,20)
Traceback (most recent call last): File "poly_ex.py", line 73, inob.add(10,20) TypeError: add() missing 1 required positional argument: 'c'
这里发生的情况是,由于两个方法具有相同的名称,Python 会保留类中最后定义的方法,并在调用函数时检查最后定义的方法,即带有三个参数的方法。带有两个参数的 add() 方法在类中就好像什么都没有一样。这意味着 Python 不支持方法重载,与其他 OOP 语言不同。
注意:Python 不支持方法重载。
但是,幸运的是,我们有 Pythonic 的方法可以在 Python 中实现方法重载。
class A:
def add(self,a = None,b = None, c = None):
if a!=None and b!=None and c!=None:
print (a+b+c)
else:
print(a+b)
ob = A()
ob.add(10,20)
ob.add(30,40,50)
ob.add('Programming','Tutorials')
输出
30 120 ProgrammingTutorials
上面的代码片段展示了如何通过一些技巧在 Python 中实现方法重载。在这里,我们可以通过三种不同的方式调用 add() 方法。
再次提醒您,这不是实现方法重载的理想方式,因此在 Python 中不能称为方法重载。但不知何故,我们还是在 Python 中实现了方法重载。
重写,在面向对象编程语言中,是一个支持运行时多态的重要概念。当子类中的一个方法与父类中的一个方法具有相同的名称、相同的参数数量和相同的返回类型(签名)时,该方法就被称为被重写。方法重写的主要好处是子类可以在不更改父类方法的情况下,为继承的方法提供自己的特定实现。
因此,在方法重写中,我们有两个方法:一个在父类中,称为被重写方法;一个在子类中,称为重写方法。两个方法具有相同的名称和相同的签名。
为了更容易理解,请看一个包含两个类的简单示例:父类 - Mom,子类 - Daughter。
#Parent Class
class Mom:
def dance(self):
print("Mom is dancing")
def cook(self):
print("Mom is cooking")
#Child class
class Daughter(Mom):
pass
d = Daughter()
d.dance()
d.cook()
输出
Mom is dancing Mom is cooking
父类包含两个方法:dance() 和 cook(),它们会打印一些消息。目前,我们将 Daughter 类留空,没有任何方法或属性,但是我们创建了一个 Daughter 类的对象,并用它来调用 Mom 中的方法。由于 Daughter 继承自 Mom,它可以访问 Mom 类中的方法并产生输出。
现在,让我们创建重写 dance() 方法的子类 Daughter。
#parent class
class Mom:
#overridden method
def dance(self):
print("Mom is dancing")
def cook(self):
print("Mom is cooking")
#child class
class Daughter(Mom):
#overriding method
def dance(self):
print("Daughter is dancing ")
d = Daughter()
d.dance()
d.cook()
输出
Daughter is dancing Mom is cooking
现在,当我们调用对象 d 时,将执行修改后的 dance() 方法,这在输出中得到了清晰的体现。因此,我们可以得出结论:daughter 类重写了 Mom 类的方法。