Skip to content

面向对象

约 1141 个字 28 行代码 预计阅读时间 4 分钟

面向过程当中的数据和函数是分离的,面向对象把数据和函数组织到了一起。Python中一切皆对象,包括内置intstr等,因为它们本质上是封装过的C语言指针

封装

封装指的是把数据和方法组织到同一个类中,Python的类中一般是首字母大写,每个类都有一个名为__init__的特殊方法,在创建实例时自动运行。类中的所有对象方法的第一个参数都是self(可以是其他标识符),表示当前对象本身,不过通过实例访问方法并不需要显式传递参数。

构造

  • __init__是一个特殊方法,它会在对象创建时自动调用
  • 可以用isinstance()来判断一个对象是否为某个类的实例
  • Python实现加减乘除是通过魔术方法__add____sub__

访问控制

Python的访问控制并不使用privatepublic关键字,Python无法实现真正意义上的private,Python约定:

  • _var:表示这是一个内部变量,不应该在外部访问,但其实还是可以访问,不能通过from module import *导入
  • __var:Python 解释器会执行名称修饰,将这个名称转换为_类名__原始名称 的形式。

类成员/方法

类成员很简单,类似于C++的静态成员,不过不需要关键字限定,只需要在所有方法之外定义,但是类方法就需要使用装饰器,类方法既可以通过类,也可以通过实例来调用:

  • 类方法:使用@classmethod,类方法不能访问实例变量,只能访问类成员,并且定义时第一个参数必须是对象本身cls但是在调用时不需要也不能给该参数传参,因为事实上Python解释器会自动传入,在继承时会自动绑定给子类。
  • 静态方法:使用@staticmethod,静态方法通常是一个独立的方法,不对实例成员和类成员进行操作,在继承时不会绑定给子类,本质上是独立的函数,只是命名空间在类中。

Python作为一门动态类型的语言,可以在类创建之后添加新的成员,可以是给类添加,比如class.new_class_var = xxx,也可以是给实例添加,比如object.new_boject_var = xxx

Python因为没有封装,所以实际上可以直接访问类的成员和方法,比如

class MyClass:
    def f(x):
        print(x)

MyClass.f(1)

访问成员

Python直接.就可以访问任意成员,此外可以用@property去修饰一个与成员变量同名的方法,来做到通过instance.get_method访问对应成员变量。@property 装饰器是一个内置的装饰器,用于将一个类的方法转换为属性进行访问,这个主要是可以做一些校验,也未必是真的赋值,比如你可以在赋值的时候就完成某些计算。

class MyClass:
    def __init__(self):
        self._value = None

    @property
    def value(self):
        print("Calling getter...")
        return self._value

    @value.setter # 注意这里是 @value.setter,value 是上面 @property 方法的名字
    def value(self, new_value):
        print(f"Calling setter with {new_value}...")
        if new_value < 0:
            raise ValueError("Value cannot be negative")
        self._value = new_value

obj = MyClass()
obj.value = 20 # 调用 setter 方法
print(obj.value)
try:
    obj.value = -5 # 触发 setter 中的校验
except ValueError as e:
    print(e)

继承

Python的继承使用class 类的名称(父类名称),如果参数为空,则默认继承自object,可以使用self调用自身的方法,supper调用父类的方法。Python中有个重要的概念是鸭子类型,因为Python是动态类型的语言,实际上不会检查类型到底是否正确,在鸭子类型中,一个对象的类型(它是什么类的实例)并不重要,重要的是它能做什么(它有什么方法和属性)。鸭子类型其实说明Python不仅仅是动态绑定,甚至哪怕没有继承关系的类,只要有对应的方法,也可以动态地绑定。

Python支持多继承,使用MRO算法查找,基本原则是从下往上找,优先选择最近的父类,如果有多个同级的无继承关系的父类,那么就按照定义的顺序,此外,子类的 MRO 不应该改变父类之间已建立的相对顺序。

多态

Python没有重载,不过子类覆写父类的方法还是可以的。Python如果定义同名的函数,后定义的会覆盖先前的,一个不是那么好的解决手段是默认参数,如果要真的重载可以考虑第三方库。Python许多运算符的重载是通过魔术方法,比如+实际会调用__add__print实际上会优先调用__repr__,如果没有则尝试__str__