函数式编程
装饰器
若要增强某个函数的功能,使其在调用前后自动打印日志,但又不希望修改该函数的定义,这种在代码运行期间动态增加功能的方式,称为装饰器(Decorator)。在面向对象的设计模式中,decorator被称为装饰模式。
本质上,decorator就是一个返回函数的高级函数,因此定义一个能打印日志的decorator可以定义成:
1 | def log(func): |
由于上面的log
是一个decorator,因此接收一个函数作为参数,并返回一个函数,故要借助Python的@语法。将decorator置于函数的定义处:
1 |
|
调用now()
函数时,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志。其中的@log
语句在now()
函数的定义出就相当于执行now=log(now)
语句。由于log()
是一个decorator,返回一个函数。因此调用now()
将执行新函数,即在函数中返回wrapper()
函数。在wrapper()
函数内,首先打印日志,再调用原始函数。
若decorator本身需要传入参数,便需要编写一个返回decorator的高阶函数。
例如自定义log的文本:
1 | def log(text): |
上述代码中执行log('execute')
,返回的是decorator函数,再调用返回函数,参数是now
函数,返回值最终是wrapper
函数。此时now.__name__
属性已经从原来的now
变成了wrapper
。因此需要把原始函数的__name__
属性复制到wrapper函数中,即在定义wrapper()
的前面加上@functools.wraps(func)
,写法如下:
1 | import functools |
练习题
请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
代码如下:
1 | import time, functools |
偏函数
偏函数可以通过设定参数的默认值,降低函数调用的难度。例如int()
函数默认按十进制转换,也可以传入base
参数做N进制的转换,如:int('123',base=8)
。
创建一个偏函数可以用functools.partial
,比如创建一个转换二进制的函数如下:
1 | import functools |
创建偏函数时,也可以接收函数对象、*args
,**kw这三个参数。
模块
自己创建模块时要注意命名,不要使用中文、特殊字符,不能和python自带的模块名称冲突。检查系统是都已存在该模块的方法是先执行import abc
,若成功则说明系统存在该模块。
导入模块:import 模块名称
使用模块
模块的使用涉及到作用域的概念。
作用域
类似于__xxx__
这样的变量是特殊变量,可以被直接引用,但有特殊用途。类似于_xxx
和__xxx
的变量是私有的,不应被直接引用。
安装第三方模块
安装第三方模块需要添加模块搜索路径。
方法一:直接修改sys.path
1 | import sys |
方法二:设置环境变量pythonpath
面向对象编程
类和实例
通过class
关键字定义类,格式如下:
1 | class 类名(Object): #Object表示该类的基类是谁,默认使用Object类 |
创建实例:变量=类名()
引用实例属性:指向实例的变量.属性名
通过__init__
方法初始化实例,例子如下:
1 | class Student(object): |
__init__
方法第一个参数永远是self,代表实例本身。有了__init__
方法,在创建实例时不能传入空参数,必须传入与__init__
方法匹配的参数,但self
不需要传。
调用实例方法:指向实例的变量.方法名
访问限制
将public变量变成private变量:在属性名称前加两个下划线__
。
只有一个下划线开头的实例变量名是protected变量。
外部函数访问私有变量方法:增加get_name和get_score方法。例如:
1 | class Student(object): |
外部函数修改私有变量方法:增加set_score方法。例如:
1 | class Student(object): |
间接访问private属性的方法:指向实例的变量._类名_属性名
,但由于不同版本的python解释器对变量的解析不同,这种操作尽量不要用。
类外代码定义的private变量与类内同名的变量不是同一个变量,类内的变量会被自动改成指向实例的变量._类名_属性名
的形式,而外部代码只能新增一个__属性名
变量。
练习题
请把下面的Student
对象的gender
字段对外隐藏起来,用get_gender()
和set_gender()
代替,并检查参数有效性:
1 | class Student(object): |
继承和多态
定义一个类时可以从某个现有的class继承,新的class称为子类,被继承的class称为基类、父类或超类。继承可以将基类的所有功能都直接拿过来,子类只需要新增特有的方法或重写父类的方法即可。
类继承的格式:class 子类名(基类名)
子类和基类存在相同方法时,子类的方法会覆盖基类的,代码运行时默认调用子类的方法,这一特性称之为多态。
开闭原则
对扩展开放:允许新增子类
对修改封闭:不需要修改依赖子类类型的函数。
静态语言 vs 动态语言
对于静态语言(如java),如果需要传入Animal
的类型,则传入的对象必须是Animal
类型或者是它的子类,否则将无法调用对应的run()
方法。
对于动态语言(如python),则不一定需要传入Animal
类型,只需要保证传入的对象有一个run()
方法就可以了。