在学 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 = Student('bart', 95) |
此时,如果需要获取 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 文档|模拟数字类型。