08. 面向对象编程(二)

目录

1、前言

2、多重继承

2.1、潜在的问题

3、@Property

4、@staticmethod

5、运算符重载

5.1、加法运算符 + 的重载

5.2、字符串表示运算符 str() 的重载

5.3、索引运算符 [] 的重载

6、小结


1、前言

上一篇文章中,我们介绍了面向对象编程的类和实例,以及三大基本特性。今天我们继续来了解面向对象编程的其他知识。

2、多重继承

多重继承是面向对象编程中的一个概念,它允许一个类同时继承自多个父类。这使得子类可以继承多个父类的属性和方法。在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()

执行结果:

08. 面向对象编程(二)_第1张图片

从上述代码中可以看出,多重继承的语法为class 类名(父类1, 父类2...)。如果继承的多个父类中有相同的方法,那么最先执行最左继承的父类。什么?你问我怎么知道的?我猜的!!!来看我猜的对不对,Python提供了一个特殊的方法__mro__来查看类的方法解析顺序。

在多重继承中,Python 使用 C3 线性化算法来确定方法的调用顺序,这被称为方法解析顺序(Method Resolution Order,简称MRO)。MRO 定义了从多个父类中继承方法时的顺序,它是基于 C3 算法生成的线性列表。

查看结果:

先解析Me类,之后是Father,再Programmer。诚不欺客,没骗人吧!

2.1、潜在的问题

尽管多重继承提供了一定的灵活性,但也容易导致一些设计上的复杂性和潜在的问题,例如:

  • 菱形继承问题: 当一个子类继承自两个有共同父类的类时,可能会出现方法冲突的问题。Python 的 MRO 算法通常能够解决这个问题,但在复杂的继承结构中,需要小心设计以避免混淆。
  • 代码可读性:多重继承可能导致代码可读性降低,因为需要追踪多个父类的方法和属性。
  • 耦合性: 过度使用多重继承可能导致类之间的高度耦合,降低了代码的灵活性和可维护性。

3、@Property

@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方法。

08. 面向对象编程(二)_第2张图片

当然,如果我们已添加了@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}公斤")

这时候,我们运行程序就不会报错了:

08. 面向对象编程(二)_第3张图片

4、@staticmethod

@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

运行结果:

08. 面向对象编程(二)_第4张图片

但是你会发现,其实加不加@staticmethod都允许用类名来访问静态方法,那么既然如此为什么要加上这个注解呢?其实,在 Python 中,加不加 @staticmethod 装饰器影响的是对方法的理解和类的设计风格。从功能上来说,不加 @staticmethod 也可以正常工作,因为 Python 允许通过类名直接调用类的方法。

然而,使用 @staticmethod 装饰器有一些优点:

  • 清晰性:@staticmethod 装饰器明确地表明某个方法是静态方法,提高了代码的可读性。读者可以立即看到这个方法是与类相关但与实例无关的。
  • 一致性:在类中使用 @staticmethod 保持了方法定义的一致性,使得在查看类的方法时更容易识别哪些方法是静态的。
  • IDE支持: 使用 @staticmethod 可以让一些集成开发环境(IDE)更好地支持代码提示和文档生成。

5、运算符重载

运算符重载是指在类中定义特殊方法,使得该类的实例对象可以支持一些内建运算符(如+、-、*等)的操作。通过重载运算符,我们可以定义自定义的行为,使得类对象可以与运算符一样被操作。

在Python中,运算符重载是通过特殊方法来实现的,这些方法以双下划线开头和结尾,例如 __add__、__sub__、__mul__等。以下是一些常用的运算符重载方法。

5.1、加法运算符 + 的重载

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

5.2、字符串表示运算符 str() 的重载

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)

5.3、索引运算符 [] 的重载

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

运算符重载为类提供了更自然和直观的使用方式,使得类的实例对象可以像内建类型一样参与各种操作。然而,需要小心不要滥用运算符重载,以避免使代码变得难以理解和维护。

6、小结

面向对象的内容远不及如此,这里只是简单介绍了最最常用的部分内容,帮助学习和入门。更多的面向对象使用还是需要结合实际项目,才会更加深入和彻底的学习掌握。今天就到这吧,一起学习,一起加油!

你可能感兴趣的:(Python,python,开发语言)