唐抉的个人博客

python学习笔记(六)

字数统计: 1.9k阅读时长: 7 min
2022/10/11

函数式编程

装饰器

若要增强某个函数的功能,使其在调用前后自动打印日志,但又不希望修改该函数的定义,这种在代码运行期间动态增加功能的方式,称为装饰器(Decorator)。在面向对象的设计模式中,decorator被称为装饰模式。

本质上,decorator就是一个返回函数的高级函数,因此定义一个能打印日志的decorator可以定义成:

1
2
3
4
5
def log(func):
def wrapper(*args,**kw):
print('call %s():'% func.__name__)#函数对象的__name__属性可以拿到函数的名字
return func(*args,**kw)
return wrapper

由于上面的log是一个decorator,因此接收一个函数作为参数,并返回一个函数,故要借助Python的@语法。将decorator置于函数的定义处:

1
2
3
4
5
6
7
@log
def now():
print('2022-10-11')
now()
#运行结果如下:
call now():
2022-10-11

调用now()函数时,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志。其中的@log语句在now()函数的定义出就相当于执行now=log(now)语句。由于log()是一个decorator,返回一个函数。因此调用now()将执行新函数,即在函数中返回wrapper()函数。在wrapper()函数内,首先打印日志,再调用原始函数。

若decorator本身需要传入参数,便需要编写一个返回decorator的高阶函数。

例如自定义log的文本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print('%s %s():'% (text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
#decorator用法如下:
@log('execute')#即now=log('execute')(now)
def now():
print('2022-10-11')
now()
#运行结果如下:
execute now():
2022-10-11

上述代码中执行log('execute'),返回的是decorator函数,再调用返回函数,参数是now函数,返回值最终是wrapper函数。此时now.__name__属性已经从原来的now变成了wrapper。因此需要把原始函数的__name__属性复制到wrapper函数中,即在定义wrapper()的前面加上@functools.wraps(func),写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print('%s %s():'% (text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
#decorator用法如下:
@log('execute')#即now=log('execute')(now)
def now():
print('2022-10-11')
print(now.__name__)

#运行结果如下:
now

练习题

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time, functools
def metric(fn):
@functools.wraps(fn)
def wrapper(*args,**kw):
print('%s executed in %s ms' % (fn.__name__, 10.24))
return fn(*args,**kw)
return wrapper

# 测试
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y;

@metric
def slow(x, y, z):
time.sleep(0.1234)
return x * y * z;

f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
print('测试失败!')
elif s != 7986:
print('测试失败!')
#运行结果如下:
fast executed in 10.24 ms
slow executed in 10.24 ms

偏函数

偏函数可以通过设定参数的默认值,降低函数调用的难度。例如int()函数默认按十进制转换,也可以传入base参数做N进制的转换,如:int('123',base=8)

创建一个偏函数可以用functools.partial,比如创建一个转换二进制的函数如下:

1
2
3
4
5
import functools
int2=functools.partial(int,base=2)
print(int2('1000000'))
#运行结果如下:
64

创建偏函数时,也可以接收函数对象、*args,**kw这三个参数。

模块

自己创建模块时要注意命名,不要使用中文、特殊字符,不能和python自带的模块名称冲突。检查系统是都已存在该模块的方法是先执行import abc,若成功则说明系统存在该模块。

导入模块import 模块名称

使用模块

模块的使用涉及到作用域的概念。

作用域

类似于__xxx__这样的变量是特殊变量,可以被直接引用,但有特殊用途。类似于_xxx__xxx的变量是私有的,不应被直接引用。

安装第三方模块

安装第三方模块需要添加模块搜索路径。

方法一:直接修改sys.path

1
2
import sys
sys.path.append('/路径名')

方法二:设置环境变量pythonpath

面向对象编程

类和实例

通过class关键字定义类,格式如下:

1
2
class 类名(Object):       #Object表示该类的基类是谁,默认使用Object类
pass

创建实例变量=类名()

引用实例属性指向实例的变量.属性名

通过__init__方法初始化实例,例子如下:

1
2
3
4
5
6
7
8
class Student(object):
def __init__(self,name,score):
self.name=name
self.score=score
bart=Student('zhangsan',56)
print(bart.name,bart.score)
#运行结果如下:
zhangsan 56

__init__方法第一个参数永远是self,代表实例本身。有了__init__方法,在创建实例时不能传入空参数,必须传入与__init__方法匹配的参数,但self不需要传。

调用实例方法:指向实例的变量.方法名

访问限制

将public变量变成private变量:在属性名称前加两个下划线__

只有一个下划线开头的实例变量名是protected变量

外部函数访问私有变量方法:增加get_name和get_score方法。例如:

1
2
3
4
5
6
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score

外部函数修改私有变量方法:增加set_score方法。例如:

1
2
3
4
class Student(object):
...
def set_score(self,score):
self.__score=score

间接访问private属性的方法:指向实例的变量._类名_属性名,但由于不同版本的python解释器对变量的解析不同,这种操作尽量不要用。

类外代码定义的private变量与类内同名的变量不是同一个变量,类内的变量会被自动改成指向实例的变量._类名_属性名的形式,而外部代码只能新增一个__属性名变量。

练习题

请把下面的Student对象的gender字段对外隐藏起来,用get_gender()set_gender()代替,并检查参数有效性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Student(object):
def __init__(self, name, gender):
self.__name = name
self.__gender = gender
def get_gender(self):
return self.__gender
def set_gender(self,gender):
self.__gender=gender
# 测试:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
print('测试失败!')
else:
bart.set_gender('female')
if bart.get_gender() != 'female':
print('测试失败!')
else:
print('测试成功!')
#运行结果如下:
测试成功!

继承和多态

定义一个类时可以从某个现有的class继承,新的class称为子类,被继承的class称为基类、父类或超类。继承可以将基类的所有功能都直接拿过来,子类只需要新增特有的方法或重写父类的方法即可。

类继承的格式:class 子类名(基类名)

子类和基类存在相同方法时,子类的方法会覆盖基类的,代码运行时默认调用子类的方法,这一特性称之为多态

开闭原则

对扩展开放:允许新增子类

对修改封闭:不需要修改依赖子类类型的函数。

静态语言 vs 动态语言

对于静态语言(如java),如果需要传入Animal的类型,则传入的对象必须是Animal类型或者是它的子类,否则将无法调用对应的run()方法。

对于动态语言(如python),则不一定需要传入Animal类型,只需要保证传入的对象有一个run()方法就可以了。

CATALOG
  1. 1. 函数式编程
    1. 1.1. 装饰器
      1. 1.1.1. 练习题
    2. 1.2. 偏函数
  2. 2. 模块
    1. 2.1. 使用模块
      1. 2.1.1. 作用域
    2. 2.2. 安装第三方模块
      1. 2.2.1. 方法一:直接修改sys.path
      2. 2.2.2. 方法二:设置环境变量pythonpath
  3. 3. 面向对象编程
    1. 3.1. 类和实例
    2. 3.2. 访问限制
      1. 3.2.1. 练习题
    3. 3.3. 继承和多态
      1. 3.3.1. 开闭原则
      2. 3.3.2. 静态语言 vs 动态语言