在学 Python OOP 之前,我在想,怎么还没到结构体,后来才恍然大悟,结构体是 C 的东西,C++ 有了类以后,(不考虑效率问题)就不需要结构体了,而 Python 也是这样,有了类,还需要结构体做什么呢?
该篇博客默认读者曾学过至少一门 OOP 语言。
Python 和 C++ 的对同一个概念的称呼的简单对照表:
Python | C++ |
---|---|
类 class |
类 class |
实例 instance |
对象 object |
方法 methods |
成员函数 member functions |
属性 attributes |
成员变量 member variables |
类和实例
类的定义:
1 | class Student(object): # object 表示被继承的类,如果没有继承类,就写 object |
由类生成的东西,在 C++ 中叫对象 object
,但是在 Python 中叫实例 instance
。可能是因为 Python 的对象被用来指代东西
了吧,比如类对象
、方法对象
。
创建 实例
,并调用实例对应的关联函数(关联函数被称为实例的方法 Method
):
1 | bart = Student('Bart Simpson', 59) |
从第一段代码还可以看出,类的构造方法是 __init__
。
另外,在定义类的时候,每个函数无论在声明、还是在使用时,都要写明第一个参数是 self
(C++ 只在静态成员函数的时候需要写明)
和 C++ 不同的是,外部代码在使用对象的时候,Python 还可以创建成员:
1 | bart.grade = 'A' |
类属性
由于 C++ 需要初始化变量,所以 C++ 类一般先是一堆变量声明,再是成员函数。
到了 Python 这里,不需要声明,直接就是几个方法,反而有点不习惯。
如果想要给类弄一个属性(类似于 C++ 的静态成员),也是可以的,而且方法也浅显易懂:
1 | class Student(object): |
类属性是可以被实例属性覆盖的。并且,如果删除了示例的属性,会还原为类属性。
1 | print(Student.course) # 显示 Chinese |
私有成员
如上,Python 并没有 C++ 一样的,必须写明对象是 public
,否则就是私有 private
的。而 Python 如何在类中创造私有成员呢:
1 | class Student(object): |
此时能够正常执行 print_score
函数,但无法访问 __name
了:
1 | 'bart', 95) bart = Student( |
此时,如果需要获取 name
、修改 name
时,就需要在类里面添加 getName
和 setName
。这是面向对象编程的经典操作。
需要注意的是:
- 形如
__xxx__
的变量名是特殊变量,可以直接访问; - 其实也可以通过访问
_Student__name
来访问__name
,Python 解释器也只是改了一个名字。仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。当然还是强烈建议不要访问。 - 大多数 Python 代码还遵循这样一个约定:带有一个下划线的名称 (例如
_spam
) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。也就是说,不应该去访问它,虽然他并不是私有成员。
继承
继承:
1 | class Animal(object): |
注意:如果基类定义在另一个模块中,可以使用:
1 | class DerivedClassName(modname.BaseClassName): |
多继承
多继承的语法没什么好说的。
1 | class DerivedClassName(Base1, Base2, Base3): |
但是多继承涉及到的问题就挺麻烦了(C++ 对多继承问题的处理可以参考博客 C++ 面向对象——继承、派生和多态):
- 如果有两个不同父类有同一个属性
你可以理解为 Python 的处理办法是深度优先、从左至右地搜索父类的属性,搜到了第一个,就不会管后面的同名属性了。简单粗暴。
真实情况比这个更复杂一些;方法解析顺序会动态改变以支持对
super()
的协同调用。 这种方式在某些其他多重继承型语言中被称为后续方法调用,它比单继承型语言中的super
调用更强大。
- 如果出现了菱形关联,如同在 C++ 那篇博客题到的下图左边的情况(右边是 C++ 默认的实现):
但是 Python 的 动态改变的方法解析顺序
可以保证只调用每个父类一次。(用 C++ 的话来说就是,所有基类都是虚基类 virtual base
)
只调用每个父类一次,并且保持单调(即一个类可以被子类化而不影响其父类的优先顺序)
后面这句不大明白。
多态
多态,即子类重载父类的同名方法:
1 | class Dog(Animal): |
所有函数默认都是虚函数的。
运算符重载
Python 也支持运算符重载,但是和 C++ 的不一样,C++ 可以对任意运算符进行重载,Python 只能通过重载系统给定的的对应的函数,来重载部分运算符。
方法名 | 重载说明 | 运算符调用方式 |
---|---|---|
__init__ |
构造函数 | 对象创建: X = Class(args) |
__del__ |
析构函数 | X 对象收回 |
__add__ /__sub__ |
加减运算 | X+Y ,X+=Y/X-Y ,X-=Y |
__or__ |
运算符` | ` |
_repr__ /__str__ |
打印/转换 | print(X) 、repr(X) /str(X) |
__call__ |
函数调用 | X(*args, **kwargs) |
__getattr__ |
属性引用 | X.undefined |
__setattr__ |
,属性赋值 | X.any=value |
__delattr__ |
属性删除 | del X.any |
__getattribute__ |
属性获取 | X.any |
__getitem__ |
索引运算 | X[key] ,X[i:j] |
__setitem__ |
索引赋值 | X[key] ,X[i:j]=sequence |
__delitem__ |
索引和分片删除 | del X[key] ,del X[i:j] |
__len__ |
长度 | len(X) |
__bool__ |
布尔测试 | bool(X) |
__lt__ ,__gt__ |
特定的比较 | 依次为X<Y ,X>Y |
__le__ ,__ge__ |
X<=Y ,X>=Y |
|
__eq__ ,__ne__ |
X==Y ,X!=Y |
|
__radd__ |
右侧加法 | other+X |
__iadd__ |
实地(增强的)加法 | X+=Y (or else __add__ ) |
__iter__ ,__next__ |
迭代 | I=iter(X) ,next() |
__contains__ |
成员关系测试 | item in X (X为任何可迭代对象) |
__index__ |
整数值 | hex(X) ,bin(X) , oct(X) |
__enter__ ,__exit__ |
环境管理器 | with obj as var: |
__get__ ,__set__ ,__delete__ |
描述符属性 | X.attr ,X.attr=value ,del X.attr |
__new__ |
创建 | 在__init__ 之前创建对象 |
更多的数学符号重载请看 Python 文档|模拟数字类型。