目录
1、前言
2、多重继承
2.1、潜在的问题
3、@Property
4、@staticmethod
5、运算符重载
5.1、加法运算符 + 的重载
5.2、字符串表示运算符 str() 的重载
5.3、索引运算符 [] 的重载
6、小结
上一篇文章中,我们介绍了面向对象编程的类和实例,以及三大基本特性。今天我们继续来了解面向对象编程的其他知识。
多重继承是面向对象编程中的一个概念,它允许一个类同时继承自多个父类。这使得子类可以继承多个父类的属性和方法。在Python中,多重继承是支持的,但需要小心使用,因为它可能引起一些设计上的复杂性和潜在的问题。
多重继承的场景:举个例子,我们现在可能会拥有多种角色,比如程序员(Programmer)以及老父亲(Father)。那么Python中我们可能定义两个类:
# 老父亲类
class Father:
pass
# 程序员类
class Programmer:
pass
同时,两个不同的类拥有一个相同的方法(eat)和各自不同的方法:老父亲拥有泡奶粉的方法,程序员需要加班改bug的方法。代码示例:
# 老父亲类
class Father:
# 老父亲需要泡奶粉的动作
def soak_milk(self):
print("我是一位老父亲,加班到家后要给娃泡奶粉")
# 老父亲也要吃饭
def eat(self):
print("老父亲要吃饭,吃面条")
# 程序员类
class Programmer:
# 程序员要加班改bug
def fix_bug(self):
print("我是一位程序猿,需要加班改bug")
# 程序员也要吃饭
def eat(self):
print("程序员要吃饭,吃快餐")
最后,我同时拥有这两个角色,那么创建一个我的类,继承了Father和Programmer,之后创建出来的实例就同时拥有了老父亲泡奶粉的能力,以及程序员加班改bug的能力。完整示例代码:
# 创建一个我的类,多重继承Father和Programmer
class Me(Father, Programmer):
pass
# 创建一个我的实例
me = Me()
# 我要吃饭
me.eat()
# 老父亲泡奶粉
me.soak_milk()
# 程序员改bug
me.fix_bug()
执行结果:
从上述代码中可以看出,多重继承的语法为class 类名(父类1, 父类2...)。如果继承的多个父类中有相同的方法,那么最先执行最左继承的父类。什么?你问我怎么知道的?我猜的!!!来看我猜的对不对,Python提供了一个特殊的方法__mro__来查看类的方法解析顺序。
在多重继承中,Python 使用 C3 线性化算法来确定方法的调用顺序,这被称为方法解析顺序(Method Resolution Order,简称MRO)。MRO 定义了从多个父类中继承方法时的顺序,它是基于 C3 算法生成的线性列表。
查看结果:
先解析Me类,之后是Father,再Programmer。诚不欺客,没骗人吧!
尽管多重继承提供了一定的灵活性,但也容易导致一些设计上的复杂性和潜在的问题,例如:
@property 是 Python 中用于将方法转换为只读属性的装饰器。通过使用 @property 装饰器,你可以定义一个方法,使之在调用时表现得像一个属性一样。这有助于隐藏类的内部实现细节,提高代码的可读性。正如上一篇中提到受保护成员的访问控制时,除了使用公开方法访问外,还可以此装饰器进行访问。
class Girl:
def __init__(self, name, age):
self.name = name
self.age = age
self._weight = 90 # 女孩子的体重当然是秘密了
def get_weight(self):
return self._weight
girl = Girl("小花", 20)
# 访问方式1
print(f"{girl.name}今年{girl.age}岁,体重{girl._weight}公斤")
# 访问方式2
print(f"{girl.name}今年{girl.age}岁,体重{girl.get_weight()}公斤")
上面代码是前面介绍到的访问方式,除了这两个以外,还可以使用@Property。代码改造一下:
class Girl:
def __init__(self, name, age):
self.name = name
self.age = age
self._weight = 90 # 女孩子的体重当然是秘密了
def get_weight(self):
return self._weight
# @Property注解装饰的,是被视为只读的属性
@property
def weight(self):
return self._weight
girl = Girl("小花", 20)
print(f"{girl.name}今年{girl.age}岁,体重{girl.weight}公斤")
也是可以访问的,当我们调用girl.weight时,实际上并不是直接访问属性,而是调用get_weight()方法。使用@Property只是简化了get_weight()的调用方式。
但是值得注意的是@Property注释的属性会被视为只读属性,如果此时你试图修改它,那么Python解释器会给你抛出异常,告诉你他没有setter方法。
当然,如果我们已添加了@Property后,还可以使用“@属性名称.setter"方式来定义属性的setter方法。
class Girl:
def __init__(self, name, age):
self.name = name
self.age = age
self._weight = 90
def get_weight(self):
return self._weight
# @Property注解装饰的,是被视为只读的属性
@property
def weight(self):
return self._weight
# 添加 @属性名称.setter 表示给该属性添加了setter方法,该setter方法允许你修改该变量的值
@weight.setter
def weight(self, val):
self._weight = val
girl = Girl("小花", 20)
print(f"{girl.name}今年{girl.age}岁,体重{girl.weight}公斤")
girl.weight = 200
print(f"{girl.name}今年{girl.age}岁,体重{girl.weight}公斤")
这时候,我们运行程序就不会报错了:
@staticmethod 是 Python 中用于定义静态方法的装饰器。静态方法是类中的方法,与类的实例无关,因此它不会访问或修改实例的状态。静态方法通常用于执行与类相关的操作,而不需要访问实例的属性。
class MyClass:
@staticmethod
def static_method_with_decorator():
return "I am a static method with decorator"
def static_method_without_decorator():
return "I am a static method without decorator"
# 通过类名调用带有装饰器的静态方法
result1 = MyClass.static_method_with_decorator()
print(result1) # 输出: I am a static method with decorator
# 通过类名调用没有装饰器的静态方法(不推荐)
result2 = MyClass.static_method_without_decorator()
print(result2) # 输出: I am a static method without decorator
运行结果:
但是你会发现,其实加不加@staticmethod都允许用类名来访问静态方法,那么既然如此为什么要加上这个注解呢?其实,在 Python 中,加不加 @staticmethod 装饰器影响的是对方法的理解和类的设计风格。从功能上来说,不加 @staticmethod 也可以正常工作,因为 Python 允许通过类名直接调用类的方法。
然而,使用 @staticmethod 装饰器有一些优点:
运算符重载是指在类中定义特殊方法,使得该类的实例对象可以支持一些内建运算符(如+、-、*等)的操作。通过重载运算符,我们可以定义自定义的行为,使得类对象可以与运算符一样被操作。
在Python中,运算符重载是通过特殊方法来实现的,这些方法以双下划线开头和结尾,例如 __add__、__sub__、__mul__等。以下是一些常用的运算符重载方法。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if isinstance(other, Point):
return Point(self.x + other.x, self.y + other.y)
else:
raise TypeError("Unsupported operand type")
p1 = Point(1, 2)
p2 = Point(3, 4)
result = p1 + p2
print(result.x, result.y) # 输出: 4 6
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(1, 2)
print(str(p)) # 输出: Point(1, 2)
class Vector:
def __init__(self, values):
self.values = values
def __getitem__(self, index):
return self.values[index]
v = Vector([1, 2, 3, 4, 5])
print(v[2]) # 输出: 3
运算符重载为类提供了更自然和直观的使用方式,使得类的实例对象可以像内建类型一样参与各种操作。然而,需要小心不要滥用运算符重载,以避免使代码变得难以理解和维护。
面向对象的内容远不及如此,这里只是简单介绍了最最常用的部分内容,帮助学习和入门。更多的面向对象使用还是需要结合实际项目,才会更加深入和彻底的学习掌握。今天就到这吧,一起学习,一起加油!