Python基础之高阶函数

十一:生成器

生成器generator
生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到个生成器函数,调用这个函数得到一个生成器对象
生成器函数

  1. 函数体中包含yield语句的函数,返回生成器对象
  2. 生成器对象是一个可迭代对象,是一个迭代器
  3. 生成器对象是延迟计算,惰性求值的

简易版生成器:

y = (i for i in range(5))
print(type(y))			# <class 'generator'>
print(next(y))			# 0
print(next(y))			# 1

复杂版生成器:

def inc():
    for i in range(5):
        yield i

print(type(inc))		# <class 'function'>
print(type(inc()))		# <class 'generator'>

x = inc()
print(type(x))			# <class 'generator'>
print(next(x))

for m in x:
    print(m, '*')

for m in x:
    print(m, '**')

普通的函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行
生成器函数等价于生成器表达式,只不过生成器函数可以更加复杂
那么生成器是如何执行的呢?

def gen():
    print('line 1')
    yield 1
    print('line 2')
    yield 2
    print('line 3')
    yield 3

next(gen())
next(gen())
g = gen()
print(next(g))
print(next(g))
print(next(g))
print(next(g, 'End'))	# 没有元素给个缺省值,不然会抛出 StopIteration 异常

输出结果如下:

line 1
line 1
line 1
1
line 2
2
line 3
3
End

在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回
再次执行会执行到下一个yield语句
return语句依然可以终止函数运行,但return语句的返回值不能被获取到
return会导致无法继续获取下一个值,抛出 StopIteration异常
如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出StopIteration异常

# 计数器
def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()
    return lambda : next(c)

foo = inc()
print(foo())		# 1
print(foo())		# 2

return返回了一个匿名函数,等价于下面的代码:

def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()
    def _inc():
        return next(c)
    return _inc

foo = inc()
print(foo())
print(foo())

11.1 生成器处理递归问题

def fib():
    x = 0
    y = 1
    while True:
        yield y
        x, y = y, x+y

foo = fib()
for _ in range(5):
    print(next(foo))

11.2 协程coroutine

生成器的高级用法
比进程、线程轻量级
是在用户空间调度函数的一种实现
Python3 asyncio就是协程实现,已经加入到标准库
Python3.5使用async, await关键字直接原生支持协程
协程调度器实现思路:

  1. 有2个生成器A, B
  2. next(A)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)…如此反复就实现了调度的效果
  3. 可以引入调度的策略来实现切换的方式

协程是一种非抢占式调度

11.3 yield from

def inc():
    for x in range(1000):
        yield x

foo = inc()
print(next(foo))
print(next(foo))
print(next(foo))

等价于下面的代码:

def inc():
    yield from range(1000)

foo = inc()
print(next(foo))

yield from是Python3.3出现的新语法
yield from iterable是for item in iterable: yield item形式的语法糖

def counter(n):
    for x in range(n):
        yield x

def inc(n):
    yield from counter(n)	# 从可迭代对象中一个个拿元素

foo = inc(10)
print(next(foo))
print(next(foo))

十二:高阶函数

First Class Object:

  1. 函数在Python中是一等公民
  2. 函数也是对象,可调用的对象
  3. 函数可以作为普通变量,参数,返回值等等

高阶函数:

  1. 数学概念 y = g(f(x))
  2. 在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数
    接受一个或多个函数作为参数
    输出一个函数
def counter(base):
    def inc(step = 1):
        nonlocal base
        base += step
        return base
    return inc

f1 = counter(10)
foo = counter(10)
print(id(f1))		# 2144107907256
print(id(foo))		# 2144109248376
print(f1 == foo)	# False

12.1 高阶函数实例

排序问题:依照内建函数sorted,请自行实现一个sort函数(不使用内建函数),能够为列表元素排序
思路:
内建函数sorted函数是返回一个新的列表,可以设置升序或降序,可以设置一个排序的函数
新建一个列表,遍历原列表,和新列表的值依次比较决定如何插入到新列表中
思考:sorted函数的实现原理,扩展到map, filter函数的实现原理

def sort(iterable, key = lambda x,y: x < y):
    ret = []
    for x in iterable:
        for i, y in enumerate(ret):
            if key(x, y):
                ret.insert(i, x)
                break
        else:
            ret.append(x)
    return ret
if __name__ == '__main__':
    lis = [1,2,5,4,8,7,3,9,6]
    print('升序:',sort(lis))
    print('降序:',sort(lis, lambda a,b: a > b))

12.2 内建函数

filter函数
filter函数用于遍历序列中的每个元素,根据判断逻辑判断序列中每个元素得到一个布尔值,如果是True则保留,False则过滤掉。会把序列中的所有元素按条件筛选一遍,返回筛选后的‘列表’(其实是个可迭代对象)。

filter(function or None, iterable)
# 把列表中小于等于18岁的人过滤出来
people = [
    {'name': '张三', 'age': 35},
    {'name': '赵四', 'age': 57},
    {'name': '王五', 'age': 42},
    {'name': '游客520', 'age': 18}
]
res = filter(lambda p: p['age'] <= 18, people)
print('filter函数的输出结果是', res)
print(list(filter(lambda p: p['age'] <= 18, people)))

map函数
map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

map(function, *iterable)  --> map object
list(map(lambda x: 2*x + 1, range(5)))
dict(map(lambda x: (x%5,x), range(500)))

12.3 柯里化 (Currying)

柯里化指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数

z = f(x,y) 转换成 z=f(x)(y)

将加法函数柯里化:

def add(x, y):
    return x+y

通过嵌套函数就可以把函数转换成柯里化函数。转换如下:

def add(x):
    def _add(y):
        return x + y
    return _add
print(add(4)(5))

十三:装饰器

13.1 无参装饰器

首先看一个需求:
一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息

def add(x, y):
    return x + y

增加信息输出功能:

def add(x, y):
    print('call {}, {}+{}'.format(add.__name__, x, y))
    return x + y

上面的加法函数是完成了需求,但是有以下的缺点:

  1. 打印语句的耦合度太高
  2. 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中

于是对增加函数进一步改进:

def add(x, y):
    return x + y
    
def logger(fn):
    print('begin')
    x = fn(4, 5)
    print('end')
    return x

print(logger(add))

经过上面对函数的改进,虽然做了业务功能分离,但是fn函数传参还是个问题
为了解决传参问题,进一步改变

def logger(fn, *args, **kwargs):
    print('begin')
    x = fn(*args, **kwargs)
    print('end')
    return x

print(logger(add, 5, y = 60))

对增强函数进行柯里化:

def logger(fn):
    def wrapper(*args, **kwargs):
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return wrapper

add = logger(add)
print(add(x = 5, y = 10))

装饰器函数:

def logger(fn):
    def wrapper(*args, **kwargs):
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return wrapper

@logger         # 语法糖,等价于 add = logger(add)
def add(x, y):
    return x + y

print(add(4,100))

装饰器(无参):

  1. 它是一个函数
  2. 函数作为它的形参
  3. 返回值也是一个函数
  4. 可以使用@functionname方式,简化调用
  5. 装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
import datetime
import time

def logger(fn):
    def wrap(*args, **kwargs):
        # before功能增强
        print('args = {}, kwargs = {}'.format(args, kwargs))
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)
        # after功能增强
        duration = datetime.datetime.now() - start
        print('function {} took {}s'.format(fn.__name__, duration.total_seconds()))
        return ret
    return wrap

@logger
def add(x, y):
    print('=========== call add =========')
    time.sleep(2)
    return x + y
print(add(4, y = 7))

13.2 文档字符串

Python是文档字符串 Documentation Strings
在函数语句块第一行,且习惯是多行的文本,所以多使用三引号
惯例是多首字母大写,第一行写概述,空一行,第三行写详细描述
可以使用特殊属性__doc__访问这个文档

def add(x, y):
    '''This is a function of addition'''
    ret = x + y
    return ret

print(add.__name__, add.__doc__, sep = '\n')	# sep:separation character 分隔符
# add
# This is a function of addition
print(help(add))
# Help on function add in module __main__:

# add(x, y)
#     This is a function of addition

# None

使用装饰器后的函数文档:

def logger(fn):
    def wrapper(*args, **kwargs):
        '''This is a wrapper'''
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return wrapper

@logger
def add(x, y):
    '''This is function add'''
    return x + y

print(add.__name__, add.__doc__, sep='\n')

输出结果为:

wrapper
This is a wrapper

也就是add函数使用装饰器后的某些属性被改变了,那么如何在使用装饰器后被装饰的函数的属性不变呢?
提供一个函数,被封闭函数属性 =copy=包装函数属性

def copy_properties(src, dst):
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
    dst.__qualname__ = src.__qualname__

def logger(fn):
    def wrapper(*args, **kwargs):
        '''This is a wrapper'''
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    copy_properties(fn, wrapper)
    return wrapper

@logger
def add(x, y):
    '''This is function add'''
    return x + y

print(add.__name__, add.__doc__, add.__qualname__, sep='\n')

13.3 带参装饰器

将上面的copy_properties函数改写成装饰器函数

def copy_properties(src):
    def _copy(dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        dst.__qualname__ = src.__qualname__
        return dst
    return _copy

def logger(fn):
    @copy_properties(fn)		# wrapper = _copy(fn)(wrapper)
    def wrapper(*args, **kwargs):
        '''This is a wrapper'''
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return wrapper

@logger
def add(x, y):
    '''This is function add'''
    return x + y

print(add.__name__, add.__doc__, add.__qualname__, sep='\n')

输出结果:

add
This is function add
add

带参装饰器实例:获取函数的执行时长,对时长在指定时间范围内的函数记录一下

import datetime
import time

def logger(t1, t2):
    def _logger(fn):
        def wrap(*args, **kwargs):
            # before功能增强
            # print('args = {}, kwargs = {}'.format(args, kwargs))
            start = datetime.datetime.now()
            time.sleep(5)
            ret = fn(*args, **kwargs)
            # after功能增强
            duration = (datetime.datetime.now() - start).total_seconds()
            if duration > t1 and duration < t2:
                print('function {} took {}s'.format(fn.__name__, duration))
            return ret
        return wrap
    return _logger

@logger(3, 8)     #  add = logger(3)(add)
def add(x, y):
    print('=========== call add =========')
    time.sleep(2)
    return x + y

print(add(4, y=7))

13.4 functools

1:装饰器
使用装饰器时,原函数会损失一些信息,因为指向装饰器中的函数。Python提供了一个装饰器functools.wraps可以保持当前装饰器去装饰的函数的 name 等的值不变

import functools

def logger(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        '''This is a wrapper'''
        print('begin')
        x = fn(*args, **kwargs)
        print('end')
        return x
    return wrapper

@logger
def add(x, y):
    '''This is function add'''
    return x + y

print(add.__name__, add.__doc__, add.__qualname__, sep='\n')

2:partial方法
偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数返回
从partial生成的新函数,是对原函数的封装

import functools
import inspect
def add(x,y) -> int:
    ret = x + y
    return ret
new_add = functools.partial(add, x = 6)
print(inspect.signature(add))
print(inspect.signature(new_add))
print(new_add(y = 4))

输出信息:

(x, y) -> int
(*, x=6, y) -> int
10

十四:练习题

1:把一个字典扁平化
Python基础之高阶函数
源字典:{‘a’: {‘b’:1, ‘c’: 2}, ‘d’: {‘e’:3, ‘f’: {‘g’:4}}}
目标字典:{‘a.b’: 1, ‘a.c’: 2, ‘d.e’: 3, ‘d.f.g’: 4}

source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
# recursion
def flat_map(src, dest = None, prefix = ''):
    if dest == None:
        dest = {}
    for k,v in src.items():
        if isinstance(v, (list, tuple, set, dict)):
            flat_map(v, dest, prefix = prefix + k + '.')
        else:
            dest[prefix + k] = v
    return dest

print(flat_map(source))

能否不暴露给外界内部的字典呢?
能否函数就提供一个参数源字典,返回一个新的扁平化字典呢?
递归的时候要把目标字典的引用传递多层,怎么处理?

source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
# recursion
def flat_map(src):
    def _flat_map(src, dest = None, prefix = ''):
        for k,v in src.items():
            key = prefix + k
            if isinstance(v, (list, tuple, dict, set)):
                _flat_map(v, dest, key + '.')
            else:
                dest[key] = v
    dest = {}
    _flat_map(src, dest)
    return dest

print(flat_map(source))

2:求2个字符串的最长公共子串
方法一:从最长的字符串开始比较

s1 = 'abcdefg'
s2 = 'defabcd'

def find_sub(str1, str2):
    count = 0
    length = len(str1)
    for sublen in range(length, 0, -1):
        for start in range(0, length - sublen + 1):
            substr = str1[start: start + sublen]
            count += 1
            if str2.find(substr) > -1:
                print("count = {}, substrlen = {}".format(count, sublen))
                return substr

print(find_sub(s1, s2))

十五:函数注解

函数注解:Function Annotations

  1. Python3.5引入
  2. 对函数的参数进行类型注解
  3. 对函数的返回值进行类型注解
  4. 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
  5. 提供给第三方工具,做代码分析,发现隐藏的bug
  6. 函数注解的信息,保存在__annotations__属性中,是一个字典

15.1 inspect模块

inspect模块:提供获取对象信息的函数,可以检查函数和类,类型检查
signature(callable)方法:获取签名(函数签名包含了一个函数的信息,包括函数名,它的参数类型,它所在的类和名称空间及其他信息)

import inspect

def add(x:int, y:int, *args, **kwargs) -> int:
    return x + y

print(add.__annotations__)		# {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
sig = inspect.signature(add)
print(sig)
print('params: ', sig.parameters)   # OrderedDict
print('return: ', sig.return_annotation)
print(sig.parameters['x'])
print(sig.parameters['y'].annotation)
print(sig.parameters['args'])
print(sig.parameters['args'].annotation)
print(sig.parameters['kwargs'])
print(sig.parameters['kwargs'].annotation)

输出结果:

(x: int, y: int, *args, **kwargs) -> int
params:  OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
return:  <class 'int'>
x: int
<class 'int'>
*args
<class 'inspect._empty'>
**kwargs
<class 'inspect._empty'>
inspect.isfunction(add)				# 是否是函数
inspect.ismethod(add)				# 是否是类的方法
inspect.isgenerator(add)			# 是否是生成器对象
inspect.isgeneratorfunction(add)	# 是否是生成器函数
inspect.isclass(add)				# 是否是类
inspect.ismodule(inspect)			# 是否是模块
inspect.isbuiltin(print)			# 是否是内建对象
# 还有很多is函数,需要的时候查阅inspect模块帮助
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值