WXL's blog

Talk is cheap, show me your work.

0%

python高级编程-part1

outline

函数与函数式编程:

  • 一等函数
  • 把函数视为对象
  • 高阶函数
  • 列表推导 vs (map/reduce)
  • 函数的参数与返回值

方法和装饰器:

  • 有用的装饰器
  • 由简单到复杂实现各类装饰器

一等函数

特点:

  1. 可以运行的时候创建;
  2. 可以作为变量,传给函数;
  3. 可以作为函数的返回结果。
1
2
3
4
5
6
7
def factorial(n):
"""
return n!
"""
return 1 if n < 2 else factorial(n - 1) * n

print(factorial(5))

既然是对象,就有相应的属性,且可以赋值:

1
2
3
4
5
6
7
# 函数对象的属性
print(factorial.__doc__)
print(type(factorial))
# 既然是一个函数对象,那么就可以赋值
fact = factorial
print(fact)
print(fact(5))

输出结果:

1
2
3
4
5
6

return n!

<class 'function'>
<function factorial at 0x00000184B923F288>
120

为了更好的理解函数作为对象的特征,可以看看下面这个例子:

1
2
3
4
5
6
7
8
9
# 嵌套函数返回函数对象
def make_averager():
nums = []
def averager(new_value):
nums.append(new_value)
total = sum(nums)

return total / len(nums)
return averager

外层函数返回的是函数的对象,这样一来经过下面的测试可以得到预期结果:

1
2
3
avg = make_averager()
print(avg)
print(avg(3))

输出结果如下:

1
2
<function make_averager.<locals>.averager at 0x00000184B923FDC8>
3.0

高阶函数

**特点:**接受函数作为参数或者函数作为返回结果。eg:map/reduce,sorted。

map/reduce/filter就是典型的高阶函数:

1
2
3
num_list = [1, 2, 3, 4, 5, 7, 9]
# map需要传入一个函数对象
print(list(map(factorial, num_list)))

输出:[1, 2, 6, 24, 120, 5040, 362880]

reduce和map的区别是,map只允许传入的函数只能接受1个参数,而reduce可以接受两个参数,如:

1
2
3
4
5
from functools import reduce

lyst = [2, 3, 4, 5, 6]
print(list(map(lambda x: x**2, lyst)))
print(reduce(lambda x, y: x + y, lyst))

注意reduce返回的是一个值,而不是一个可迭代对象。

对于常见的加减乘除,还可以使用这种方式来实现:

1
2
3
4
from functools import partial
from operator import add, mul
op_mul = partial(mul, 3)
op_mul(9)

这个op_mul就相当于一个乘法运算对象,而且其中有一个值固定为3,现在就传入另外一个值即可。从这里就可以看出来partial的作用:

对于一个函数,如果有几个参数是确定的,但是还有一部分不确定,可以将确定的参数传入,然后借助partial函数,将这个传入了几个确定参数之后的函数作为一个新的函数,接下来只用将剩余的参数传入这个新的函数即可。

字典实现列表推导式:

1
2
3
4
# 字典的列表推导式
usernames = ['sophia', 'emma', 'olivia', 'ava']
name_dict = {name: len(name) for name in usernames}
print(name_dict)

输出结果如下:

{'sophia': 6, 'emma': 4, 'olivia': 6, 'ava': 3}

嵌套高阶函数:

1
2
3
4
5
6
7
# 嵌套高阶函数
# 首先将num_list中的偶数筛选掉
print(list(map(factorial, filter(lambda n: n % 2, num_list))))

# 使用列表推导式实现上述功能
result = [factorial(num) for num in num_list if num % 2]
print(result)

输出:

[1, 6, 120, 5040, 362880]

[1, 6, 120, 5040, 362880]

python单下划线和双下划的区别

python下划线有5种:

模式 样例 含义
单前导下划线 _xxx 约定俗成,仅供类内使用,外部不调用,但是强制调用还是可以的
双前导下划线 __xxx 强制不可外部调用,如果是函数,那么函数所在的类的子类无法轻易重写该函数
单末尾下划线 xxx_ 防止和python内置关键字冲突
双前导和末尾下划线 __xxx__ python语义的特殊方法,尽量避免自己使用
单下划线 _ 较多的用法是,该变量在下面的作用域内不会使用到

下面就这几种模式来举一下例子:

单前导下划线:

1
2
3
4
5
6
7
class A():
def __init__(self):
self._name = "julia"
pass
a = A()
# 约定俗成,仅供类内使用,外部不调用,但是强制调用还是可以的
print(a._name)

输出:julia

双前导下划线:

1
2
3
4
5
6
7
class A():
def __init__(self):
self.__name = "julia"
pass
a = A()
# 强制不可外部调用
print(a.__name)

报错:

1
2
3
4
5
6
7
8
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-24-a75c7bdbf94f> in <module>
4 pass
5 a = A()
----> 6 print(a.__name)

AttributeError: 'A' object has no attribute '__name'

如果是带双前导下划线的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A():
def __init__(self):
self.__name = "julia"

def __getname(self):
return self.__name

class B(A):
def __init__(self):
pass

def __getname(self):
pass

print(dir(A))
print(dir(B))

运行结果:

1
2
['_A__getname', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
['_A__getname', '_B__getname', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

这里的__getname函数是带前导双下划线的函数,第一个输出的时候,发现__getname的名称前面添加了_A,B类也有一个自己的函数_B__getname,在python中这种用法是为了避免和子类定义的函数名称冲突。

单末尾下划线:

这种用法是为了避免定义的变量和python的关键字冲突,比如class,这个时候可以使用class_来代替。

单下划线:

1
2
for _ in range(10):
print("000")

比如这种情况就没有使用到循环变量,可以使用_来代替。

参考:https://blog.csdn.net/sinat_38682860/article/details/96902828

函数参数的特殊情况

下面需要实现一个html标签,如<p>hello</p><item class='' size='small'>a</item>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def make_element(name, *contents, cls=None, **kwargs):
if cls:
kwargs['class'] = cls

pairs = [f"{attr}={value}" for attr, value in kwargs.items()]
attr_str = ' '.join(pairs)

if not contents:
return f"<{name} {attr_str}/>"

elements = [f"<{name} {attr_str}>{content}<{name}/>" for content in contents]

return '\n'.join(elements)

print(make_element("p", "hello", "world", cls="sidebar", size="small"))

输出:

1
2
<p size=small class=sidebar>hello<p/>
<p size=small class=sidebar>world<p/>

因为class是python中的关键字,传入函数的话会冲突,所以将cls特意拿出来接受class参数。现在想设计一个专门生成图片标签的函数,可以利用前面说到的partial函数,代码如下:

1
2
3
4
5
6
7
from functools import partial

image_make = partial(make_element, "img", cls="pic-frame")
print(image_make(src="car.png"))

item_make = partial(make_element, "item", size="small")
print(item_make("A", "B", "C"))

输出:

<img src=car.png class=pic-frame/>

<item size=small>A<item/>

<item size=small>B<item/>

<item size=small>C<item/>

这种涉及到的特殊参数是*contents**kwargs,传入函数之后,contents是一个元组,kwargs是一个字典,如果需要对元组和字典进行解包,可以使用如下的方式:

1
2
3
4
5
6
7
8
# *解包元组
a = (2, 3, 4, 5)
print(*a)
# **解包字典
dict_a = {"a": 1, "b": 2}
dict_b = {"c": 3, "d": 4}
new_dict = {**dict_a, **dict_b}
print(new_dict)

输出:

2 3 4 5

{'a': 1, 'b': 2, 'c': 3, 'd': 4}

装饰器

装饰器的本质:在不改变函数签名、返回值的情况下,增加额外的功能。

带运行时间的装饰器

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

def build_func_with_time_record(func):

def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"function {func.__name__} cost {end - start}")
return wrapper

def func_foo(a, b):
time.sleep(2)
print(a + b)

new_func = build_func_with_time_record(func_foo)
new_func(1, 3)

这段代码实现的就是在func_foo函数的基础上,再“装饰”一下,实现装饰功能的函数是build_func_with_time_record

python提供了@语法糖来简化上述的“装饰”过程,使用@关键字可以将上述代码简化为:

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

def build_func_with_time_record(func):

def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"function {func.__name__} cost {end - start}")
return result

return wrapper

@build_func_with_time_record
def func_foo(a, b):
time.sleep(2)
print(a + b)

func_foo(1, 3)

通过这种方式可以省去显示的将函数传入、赋值等操作。注意,上述的函数有参数a,b。参数不是通过build_func_with_time_record传入的,而是通过里面的wrapper函数传入的。

这样,可以将这个装饰器加在上述的make_element函数上:

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
@build_func_with_time_record
def make_element(name, *contents, cls=None, **kwargs):
"""make HTML element

Args:
name (label name): such as p
cls (str, optional): make a distinction with class. Defaults to None.

Returns:
str: the results
"""
if cls:
kwargs['class'] = cls

pairs = [f"{attr}={value}" for attr, value in kwargs.items()]
attr_str = ' '.join(pairs)

if not contents:
return f"<{name} {attr_str}/>"

elements = [f"<{name} {attr_str}>{content}<{name}/>" for content in contents]

return '\n'.join(elements)

print(make_element("p", "hello", "world", cls="sidebar", size="small"))

输出如下:

1
2
3
function make_element cost 0.0
<p size=small class=sidebar>hello<p/>
<p size=small class=sidebar>world<p/>

但是,此时我们再输出函数属性:

1
2
3
4
5
print(make_element.__doc__)
print(make_element.__name__)
# 测试一下函数签名:
from inspect import signature
signature(make_element)
1
2
3
None
wrapper
<Signature (*args, **kwargs)>

发现函数的属性函数签名都发生了变化,我们回到不使用@关键字实现装饰器功能的代码上,发现build_func_with_time_record函数返回值就是wrapper函数对象,所以最后我们得到的不再是一开始的make_element函数了,那么有什么方法能解决这个问题呢?

需要将一开始的build_func_with_time_record做一下修改:

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

def build_func_with_time_record(func):

"""
wraps在装饰器函数返回之前
通过获取func的属性
并存储在返回的最终函数中设置上述属性。
"""
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"function {func.__name__} cost {end - start}")
return result

return wrapper

然后重新尝试一下:

1
2
3
4
5
print(make_element.__doc__)
print(make_element.__name__)
# 测试一下函数签名:
from inspect import signature
signature(make_element)
1
2
3
4
5
6
7
8
9
10
11
make HTML element

Args:
name (label name): such as p
cls (str, optional): make a distinction with class. Defaults to None.

Returns:
str: the results

make_element
<Signature (name, *contents, cls=None, **kwargs)>
行行好,赏一杯咖啡吧~