Skip to content

面向对象(基础)

约 1099 个字 86 行代码 预计阅读时间 5 分钟

基础

Python使用class创建类,比如

# 不写明继承的父类,会默认继承obj
class Student:
    pass
# 初始化一个实例
bart=Student()
# 可以任意给实例绑定属性
# 这个是Python作为动态语言的特性
bart.name="Jack"
和其它面向对象语言一样,我们可以定义一些必须的元素,可以定义一个__init__构造函数,__init__的第一个参数都是self,表示对象本身。在__init__里面赋值的变量是属于实例的,但是在类里面直接绑定的就是类变量(静态变量)

在类中定义一个方法第一个参数也是self,这是必须的,否则会报错,因为调用时会默认传入self,但是如果定义中少了一个参数,就会直接报错。

“私有”和“保护”

在Python中,类中的元素(如属性和方法)默认是公有的,即可以从类的外部直接访问。Python没有严格的访问控制机制(如C++中的private或protected),但通过命名约定和特殊语法,可以实现类似“私有”的效果: * 如果一个属性或方法的名字以单下划线(_)开头,例如 _name,它被视为受保护的(protected)。这是一种约定,表示该成员仅供内部使用或子类使用,但外部仍然可以访问。 * 如果一个属性或方法的名字以双下划线(__)开头,例如 __name,它会被Python解释器进行名称改写(name mangling),变成 _类名__name 的形式。这种改写使得该成员在外部难以直接访问,从而实现类似“私有”的效果。

class MyClass:
    def __init__(self):
        self.__private_var = "I am private"  # 私有属性

obj = MyClass()

# 访问私有成员(会报错)
print(obj.__private_var)  # 报错:AttributeError

# 但是这个报错并不是真的封装
# 而是因为解释器把__private_var名称改写成了_MyClass__private_var
#(实际情况因解释器而异),实际上还是可以访问的

# 通过名称改写访问私有成员
print(obj._MyClass__private_var)  # 可以访问

继承和多态

这一章可以很好的用到其他主流面向对象语言的经验,Python是支持多重继承的,Python也有一个万物之源的object对象。Python中的继承的基类是用括号括起来就行,如果有多个基类,打逗号隔开就好。

Python支持全面的动态绑定,也就是任何方法都相当于C++中的虚函数。但是Python作为动态语言,假设一个函数的参数是My_Class类的对象,会调用这个对象的f()方法,传进来的参数如果是My_Class或者其派生类,这显然是可以的,但是实际上不是也可以,只要传进来的对象有f()方法,这就是所谓的鸭子类型(究竟是什么类型是由行为决定的,一个东西走路像鸭子,叫声像鸭子,那它就是鸭子)

class Animal:
    def what(self):
        print("animal")

# 但是传入的实例并非一定是Animal或者其派生类
def f(animal):
    animal.what()

# 只要有speak()方法就行,这也是Python中鸭子类型的解释
class plant:
    def what(self):
        print("plant")

f(plant()) # 输出 plant

调用父类方法

在子类方法中,super().method() 允许调用父类的同名方法,而不是直接使用 ParentClass.method(self),这样可以避免硬编码父类名称,提高代码的可维护性。

class Parent:
    def show(self):
        print("Parent method")

class Child(Parent):
    def show(self):
        super().show()  # 调用父类的 show 方法
        print("Child method")

c = Child()
c.show()

支持多重继承(Method Resolution Order, MRO)

在多重继承时,super() 遵循方法解析顺序(MRO) 规则,可以确保正确调用多个父类的方法,而不会导致重复调用或错误。

# 这是一个菱形继承的例子
class A:
    def show(self):
        print("A method")

class B(A):
    def show(self):
        print("B method")
        super().show()  # 按 MRO 规则调用下一个类的方法

class C(A):
    def show(self):
        print("C method")
        super().show()

class D(B, C):  # 多重继承,继承顺序为 D -> B -> C -> A
    def show(self):
        print("D method")
        super().show()

d = D()
d.show()

内置类型的继承

在Python早期版本中,内置类型不能被继承。虽然现在内置类型也可以被继承,但是有一个重要的注意事项:内置类型(使用C语言编写)通常不调用用户定义的类覆盖的方法。比如:

class DoppelDict(dict):
    def __setitem__(self,key,value):
        super().__setitem__(key,[value]*2)
dd = DoppelDict(one=1)
dd['two']=2
dd.update(three=3)
print(dd)

输出的结果是{'one': 1, 'two': [2, 2], 'three': 3},这个例子中1和3都没重复,说明继承自dict__init__update并没有使用我们自定义的__setitem__

如果真的要继承内置类,应该从collection模块中的UserDict、UserList等继承,这些类是直接使用Python编写的

运算符重载

基本运算符重载

Python 通过定义类的特殊方法(魔术方法)来重载运算符,例如: * __add__(+) * __sub__(-) * __mul__(*) * __truediv__(/) * __mod__(%) * __pow__(**)

比如

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        raise TypeError("Unsupported operand type for +")

比较运算符

其实只需要重载__eq____lt__,其它的可以由此衍生

索引和切片运算符

  • __getitem__(self, key):支持 obj[key]
  • __setitem__(self, key, value):支持 obj[key] = value
  • __delitem__(self, key):支持 del obj[key]

自定义对象转换

  • __int__() -> int(obj)
  • __float__() -> float(obj)
  • __str__() -> str(obj)
  • __repr__() -> repr(obj)

算术运算符的反向重载

对于 a + b,如果 a 没有 __add__,或者 a.__add__(b) 返回 NotImplemented,Python 会尝试 b.__radd__(a),以此类推

获取对象的信息

  • type():用来返回对象类型,函数类型在types模块里面
  • isinstance():用来判断是否是某个类的实例,类优先使用这个
  • dir():获取一个对象的所有属性和方法