面向对象(基础)
约 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()
:获取一个对象的所有属性和方法