在上一篇中我们介绍了模块和数据结构,这一篇将介绍面向对象编程。
面向对象编程
面向对象编程——Object Oriented Programming,简称 OOP,是一种程序设计思想。OOP 把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。它将数据与功能进行组合,并将其包装在被称作“对象”的东西内。
在Python中,所有数据类型都可以视为对象,也可以自定义对象。一个类(Class)能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance)。
可以这样来类比:你可以拥有类型 int
的变量,也就是说存储整数的变量是 int
类的实例(对象)。
面向对象的设计思想是抽象出Class
,根据Class
创建Instance
。
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。
数据封装
、继承
和多态
是面向对象的三大特点。
类和实例
面向对象最重要的概念就是类(Class)和实例(Instance)。
- 类:是抽象的模板,比如 Student 类。
- 实例:是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
在 Python 中,定义类是通过class
关键字。class
后面紧接着是类名,类名通常是大写开头的单词。紧接着是object
,表示该类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object 类
,这是所有类最终都会继承的类。
创建实例是通过类名加()
实现的:
- 变量
bart
指向的就是一个Student
的实例,后面的0x1032d3470
是内存地址,每个object
的地址都不一样,而Student
本身则是一个类。 - 可以自由地给一个实例变量绑定属性,比如,给实例
bart
绑定一个name
属性。
在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
注意_init__
前后分别有两个下划线。
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python 解释器自己会把实例变量传进去。
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时不用传递该参数。除此之外,类的方法和普通函数没有什么区别,仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
数据封装
在上面的Student
类中,每个实例就拥有各自的name
和score
这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
既然Student
实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student
类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。、
这些封装数据的函数是和Student
类本身是关联起来的,我们称之为类的方法:
要定义一个方法,除了第一个参数是self
外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self
不用传递,其他参数正常传入:
这样,我们从外部看Student
类,就只需要知道创建实例需要给出name
和score
,而如何打印,都是在Student
类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
- 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
- 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
- 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
- 和静态语言不同,Python 允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。
继承和多态
在 OOP 程序设计中,当我们定义一个class
的时候,可以从某个现有的class
继承。新的class
称为子类(Subclass),而被继承的class
称为基类、父类或超类(Base class、Super class)。
继承最大的好处是:子类获得了父类的全部功能。
也可以对子类增加一些方法。
当子类和父类都存在相同的run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。
这样,我们就获得了继承的另一个好处:多态。
当我们定义一个class
的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和 Python 自带的数据类型,比如str
、list
、dict
没什么两样。在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。
新增一个Animal
的子类,不必对run_twice()
做任何修改。实际上,任何依赖Animal
作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态的意思:
对于一个变量,我们只需要知道它是Animal
类型,无需确切地知道它的子类型,就可以放心地调用run()
方法,而具体调用的run()
方法是作用在Animal
、Dog
、Cat
还是Tortoise
对象上,由运行时该对象的确切类型决定。
这就是多态真正的威力,调用方只管调用,不管细节,而当我们新增一种Animal
的子类时,只要确保run()
方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
- 对扩展开放:允许新增 Animal 子类;
- 对修改封闭:不需要修改依赖 Animal 类型的 run_twice( ) 等函数。
任何类,最终都可以追溯到根类object
,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
对于静态语言(例如 Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于 Python 这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
异常
在程序运行过程中,总会遇到各种各样的错误。
- 程序编写有问题造成的:比如本来应该输出整数结果输出了字符串。这种错误我们通常称之为 bug,bug 是必须修复的。
- 用户输入造成的:比如让用户输入 email 地址,结果得到一个空字符串。这种错误可以通过检查用户输入来做相应的处理。
- 完全无法在程序运行过程中预测的:比如写入文件的时候,磁盘满了,写不进去了;或者从网络抓取数据,网络突然断掉了。这类错误也称为异常,在程序中通常是必须处理的,否则,程序会因为各种问题终止并退出。
Python 内置了一套异常处理机制,来帮助我们进行错误处理。
- 调试:跟踪程序的执行,查看变量的值是否正确。Python 的 pdb 可以让我们以单步方式执行代码。
- 编写测试:有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。
错误处理
高级语言通常都内置了一套try...except...finally...
的错误处理机制
try的机制:
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
当我们认为某些代码可能会出错时,就可以用try
来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except
语句块,执行完except
后,如果有finally
语句块,则执行finally
语句块,至此,执行完毕。
可以有多个except
来捕获不同类型的错误。如果没有错误发生,可以在except
语句块后面加一个else
,当没有错误发生时,会自动执行else
语句。
可以跨越多层调用,比如函数main()
调用foo()
,foo()
调用bar()
,结果bar()
出错了,这时,只要main()
捕获到了,就可以处理。也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。
Python 所有的错误都是从BaseException
类派生的,点击可查看常见的错误类型和继承关系。
如果错误没有被捕获,它就会一直往上抛,最后被 Python 解释器捕获,打印一个错误信息,然后程序退出。
错误信息第 1 行:
Traceback (most recent call last):
告诉我们这是错误的跟踪信息。
依次往下看,根据错误类型,判断最后的错误源头。
出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
Python 内置的logging
模块可以非常容易地记录错误信息。同样是出错,但程序打印完错误信息后会继续执行,并正常退出。通过配置,logging
还可以把错误记录到日志文件里,方便事后排查。
更多
更多关于 Python 的学习可阅读Python 官方文档,Python 标准库文档,Python的第三方库。
进一步探索标准库的一个好方法是阅读由 Doug Hellmann 撰写的优秀的 Python Module of the Week 系列。
参考链接:
简明Python教程(电子书可阅读)
廖雪峰Python教程
如有不足,欢迎指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。