PythonCookbook第七章(函数)【透彻完结】

函数(前面已经看过了,这次把博客补上,书中讲的内容并不难)

用def语句定义的函数是所有程序的基石。书中介绍默认参数、可接受任意数量参数的函数、关键字参数、参数注解以及闭包。

7.1 编写可接收任意数量参数的函数

问题:

编写一个可接收任意数量参数的函数

解决方案:

*args,**kwargs

def avg(first, *args):
    # args 返回的是元祖
    return (first + sum(args)) / (1 + len(args))

import html
def make_element(name, value, **kwargs):
    # kwargs返回的是字典
    keyvals = [' %s="%s"' % item for item in kwargs.items()]
    attr_str = ''.join(keyvals)
    element = '<{name} {attrs}>{value}</{name}>'.format(
        name=name,
        attrs = attr_str,
        # 替换value中的<>符号
        value = html.escape(value)
    )
    return element

if __name__ == '__main__':
    print(avg(12, 3, 4, ))
    print(avg(12, 3, 2, 32, 34, 3))

    print(make_element('item', 'Albatross', size='large', quantity=6))
    print(make_element('p', '<spam>'))
    

 

/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_7/t1_2.py
6.333333333333333
14.333333333333334
<item  size="large" quantity="6">Albatross</item>
<p ><spam></p>

Process finished with exit code 0

 讨论:

在函数定义中,以*打头的参数只能作为最后一个位置参数出现,而以**打头的参数只能作为最后一个参数出现。

不要犯初级的错误了,默认参数的位置,一定要在非默认参数的后面,今天这个小问题尽然卡住我了,丢人丢到家了。

 

7.2 编写只接收关键字参数的函数

问题

只通过关键字形式接收特定的参数

解决方案

# 传入的参数中,无论多少,第一个maxsize接收第一个参数,后面所有的位置参数全部*接收
# 想给*后面的参数赋值,必须通过关键形式.
def recv(maxsize, *, block):
    ...


def minium(*values, clip=None):
    m = min(values)
    # 如果默认不是空
    if clip is not None:
        m = clip if clip < m else m
    return m


if __name__ == '__main__':
    print(minium(1, 2, 3, 4, clip=5))

    print(recv(1024, block=True))
    print(recv(1024, True))

 讨论

这个叫kewword-only参数常常是一种提高代码可读性的好方法。

 

7.3 将元数据信息附加到函数上

讨论:

在函数上面添加一些元信息

解决方案:

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


if __name__ == '__main__':
    print(help(add))
    # 通过annotations属性输出信息
    print(add.__annotations__)

 讨论:

一般没什么用,我也基本不会用,可以在验证参数的时候用一下,但装饰器更好用。

 

7.4 从函数中返回多个值

问题:

我们想从函数中返回多个值

解决方案:

返回一个元祖

def myfun(x, y, z):
    # 元祖的定义靠逗号
    return x, y, z


if __name__ == '__main__':
    res = myfun(1, 2, 3)
    print(res)
    print(type(res))

 

/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_7/t3_4.py
(1, 2, 3)
<class 'tuple'>

 讨论:

没啥讨论的

 

7.5 定义带有默认参数的函数

问题

定义一个函数或者方法,其中由一个或者多个参数是可选的,并且带有默认值

解决方案:

# 普通默认参数
def spam1(a, b=42):
    print(a, b)

# 定义参数为可变容器的话
def spam2(a, b=None):
    if b is None:
        b = []

_no_value = object()

# 验证参数b有没有填写, 可以赋值一个object实例
def spam3(a, b=_no_value):
    if b is _no_value:
        print('No b value supplied')

if __name__ == '__main__':
    spam3(1)

 讨论:

定义带有默认参数的函数看似容易,但其实并不像看到的那么简单,很有道理。

流畅的Python书中说过,Python的函数传参其实使共享传参,在函数定义好以后,所有的函数参数就好比函数的属性,绑定在函数上面。

In [21]: x = 42                                                                                                                                                                         

In [22]: def spam(a, b=x): 
    ...:     print(a,b) 
    ...:                                                                                                                                                                                

In [23]: spam(1)                                                                                                                                                                        
1 42

In [24]: x = 50                                                                                                                                                                         

In [25]: spam(1)                                                                                                                                                                        
1 42

In [26]: import inspect 

In [31]: inspect.getfullargspec(spam)                                                                                                                                                   
Out[31]: FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(42,), kwonlyargs=[], kwonlydefaults=None, annotations={})

 这里x属于不可变参数,定义函数的时候,b也指向了42值,当修改x的时候,并不影响42的值,所以函数的内部属性不会变。

 

In [32]: def spam(a, b=[]): 
    ...:     print(b) 
    ...:     return b 
    ...:  
    ...:                                                                                                                                                                                

In [33]: x = spam(1)                                                                                                                                                                    
[]

In [34]: x                                                                                                                                                                              
Out[34]: []

In [35]: x.append(99)                                                                                                                                                                   

In [36]: x                                                                                                                                                                              
Out[36]: [99]

In [37]: spam(1)                                                                                                                                                                        
[99]
Out[37]: [99]

In [38]:  

 上面一个定义了可变参数为函数属性的例子,这样使非常不可取的,只要脚本执行中,这个函数无论什么时候执行,这个可变参数都会根据函数的执行而发生变化,所有的函数执行都将共享这个可变参数,这是不对的。

 

def spam2(a, b=None):
if b not b:
b = []

 在这个函数里这样定义b是否为None是不对的,因为'',[],空数据not以后都会变True

 

最后的那个ocject()默认值来测试是否提供了参数,还是比较有意思的,object()作为Python中所有对象的基类而存在,这玩意唯一的用处就是检测相等性。

 

7.6 定义匿名或内联函数

问题:

需要为高阶函数比如(sort)操作,但不想写def

解决方法:

lambda函数

In [44]: add = lambda x, y: x+y                                                                                                                                                         

In [45]: add(2,3)                                                                                                                                                                       
Out[45]: 5

In [46]: add([1,2],[3,4])                                                                                                                                                               
Out[46]: [1, 2, 3, 4]

In [47]: name = ['sidian','wudian','liusidian']                                                                                                                                         

In [48]: sorted(name,key=lambda x: x[0])                                                                                                                                                
Out[48]: ['liusidian', 'sidian', 'wudian']

In [49]:  

 讨论:

lambda不写复杂的逻辑

其实lambda在实际操作中,我经常会犯错误,可能还是我用的少,或者实例有限,在lambda冒号的左边的参数,就好比def(args)里面的参数,所有的在函数里面定义参数的规则,这里也一样。

冒号后面是返回的值,这样时候去理解7.7节的内容就更加好理解,在lambda y,x=x: x+y,中,就好比已经定义了内部参数x,这样冒号后面的函数体也就是返回值不会读取外部变量的x。

如果没有定义,则执行函数的时候就去读取最新的x的值。

 

7.7 在匿名函数中绑定变量的值

问题:

我们利用lambda表达式定义了一个匿名函数,但是希望可以在函数定义的时候完成对特定变量的绑定。

解决方案:

这个还是蛮有意思的,匿名函数在冒号前面的参数,可以直接读取外部的全局变量参数,当匿名函数执行的时候,全部变量是什么他就读取什么。

这个跟定义好的函数,函数内部读取外部参数一样的逻辑。

In [49]: x = 10                                                                                                                                                                         

In [50]: a = lambda y: x+y                                                                                                                                                              

In [51]: x= 20                                                                                                                                                                          

In [52]: b = lambda y: x+y                                                                                                                                                              

In [53]: a(30)                                                                                                                                                                          
Out[53]: 50

In [54]: b(30)                                                                                                                                                                          
Out[54]: 50

In [55]: 

 没有绑定的情况,如果想绑定,可以填写默认参数一样。

In [55]: x = 10                                                                                                                                                                         

In [56]: b = lambda y,x=x: x+y                                                                                                                                                          

In [57]: x= 20                                                                                                                                                                          

In [58]: a = lambda y,x=x: x+y                                                                                                                                                          

In [59]: a(20)                                                                                                                                                                          
Out[59]: 40

In [60]: b(20)                                                                                                                                                                          
Out[60]: 30

In [61]:       

 讨论:

这个还是蛮有意思的,lambda函数还是非常好用的,但流畅的Python里面好像不是很推荐,不知道为什么。

In [61]: func = [lambda x: x+n for n in range(5)]                                                                                                                                       

In [62]: for f in func: 
    ...:     print(f(0)) 
    ...:                                                                                                                                                                                
4
4
4
4
4

 这个是在执行时,读取变量n,那时候n就是4

In [66]: func = [lambda x,n=n: x+n for n in range(5)]                                                                                                                                   

In [67]: for f in func: 
    ...:     print(f(0)) 
    ...:                                                                                                                                                                                
0
1
2
3
4

 这个是在定义的匿名函数的时候设置参数

 

7.8 让带有N个参数的可调用对象以较少的参数形式调用

问题

我们有一个可调用的对象可能会以回调函数的形式同其他的Python代码交互。但是这个可调用对象的参数过多,如果直接调用的话会产生异常。

解决方案:

用funtools.partail(好像国内的教学叫做偏函数,当时想了半天,不懂),但我感觉其实lambda就够了,我的感觉,lambda更加好理解,两个返回的都是函数,但lambda学精了,感觉太强大了

 

In [14]: def spam(a,b,c,d): 
    ...:     print(a,b,c,d) 
    ...:                                                                                                                       

In [15]: s1 = partial(spam,1)                                                                                                  

In [16]: ss1 = lambda b,c,d:spam(1,b,c,d)                                                                                      

In [17]: s1(2,3,4)                                                                                                             
1 2 3 4

In [18]: ss1(2,3,4)                                                                                                            
1 2 3 4

In [19]: s2 = partial(spam,d=42)                                                                                               

In [20]: ss2 = lambda a,b,c,d=42:spam(a,b,c,d)                                                                                 

In [21]: s2(1,2,3)                                                                                                             
1 2 3 42

In [22]: ss2(1,2,3)                                                                                                            
1 2 3 42

In [23]: s3 = partial(spam,1,2,d=42)                                                                                           
                                                                               

In [25]: ss3 = lambda c, d=42:spam(1,2,c,d)                                                                                    

In [26]: s3(1)                                                                                                                 
1 2 1 42

In [27]: ss3(1)                                                                                                                
1 2 1 42

 上面的列子中,我将所有的partial函数全部转换为了lambda函数,从操作可以看出,转换成lambda函数相对代码量更加大。

In [35]: s3 = partial(spam,1,2,d=42)                                                                                           

In [36]: s3(3,4)                                                                                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-36-91a1173e6786> in <module>
----> 1 s3(3,4)

TypeError: spam() got multiple values for argument 'd'

In [37]:                                                                                                                       

In [37]: s3(3,d=4)                                                                                                             
1 2 3 4

In [38]: ss3 = lambda c,d=42:spam(1,2,c,d)                                                                                     

In [39]: ss3(3,4)                                                                                                              
1 2 3 4

In [40]: ss3(3,d=4)                                                                                                            
1 2 3 4

In [41]:       

 刚刚测试了,在偏函数中定义了默认参数,后面执行该变函数,则一定要通过关键字传参,但lambda跟函数一样,定义了默认参数,仍然可以通过位置传参。

讨论:

书中用partial写了几个例子。

def output_result(result, log=None):
    if log is not None:
        log.debug('Got: %r', result)


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


if __name__ == '__main__':
    import logging
    from multiprocessing import Pool
    from functools import partial

    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger('test')
    p = Pool()
    # 进程池很久没用了,第一个是函数,第二为参数,第三为回调函数,将函数返回的值传入回调函数。
    # 本来output_result需要两个参数,但一个参数在partial定义了
    p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
    p.close()
    p.join()

 

def output_result(result, log=None):
    if log is not None:
        log.debug('Got: %r', result)


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


if __name__ == '__main__':
    import logging
    from multiprocessing import Pool
    from functools import partial

    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger('test')
    p = Pool()
    # 进程池很久没用了,第一个是函数,第二为参数,第三为回调函数,将函数返回的值传入回调函数。
    # 本来output_result需要两个参数,但一个参数在partial定义了
    p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
    p.close()
    p.join()

 最后一个案例还是非常经典的,我觉的还是写一些,我主要被继承父类,如果添加一个自定义的初始化属性吸引,对于partial已经看腻了。

from functools import partial
points = [
    (1, 2), (3, 4), (5, 6), (7, 8)
]

import math

# 求两个坐标点的距离
def distance(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    return math.hypot(x2 - x1, y2 - y1)


if __name__ == '__main__':
    # 求points内的点与p直线距离排序
    p = (4, 3)
    # 默认将p填入,key函数只需要读取points里的内容,执行函数,按照大小进行排序
    print(sorted(points, key=partial(distance,p)))

 

def output_result(result, log=None):
    if log is not None:
        log.debug('Got: %r', result)


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


if __name__ == '__main__':
    import logging
    from multiprocessing import Pool
    from functools import partial

    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger('test')
    p = Pool()
    # 进程池很久没用了,第一个是函数,第二为参数,第三为回调函数,将函数返回的值传入回调函数。
    # 本来output_result需要两个参数,但一个参数在partial定义了
    p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
    p.close()
    p.join()

 第三个案例比较有意思的是,如果继承父类的初始化函数,并通过关键字参数添加实例属性。

from socketserver import  StreamRequestHandler, TCPServer

class EchoHandler(StreamRequestHandler):

    def handle(self):
        for line in self.rfile:
            self.wfile.write(b'GOT:' + line)


# serv = TCPServer(('',15000), EchoHandler)
# serv.serve_forever()

# 上面的写法需要该一些,需要添加一个自己的属性.

class EchoHandler_O(StreamRequestHandler):
    # 我主要是被这个初始化写法吸引,他把函数的传参用到了实处
    def __init__(self, *args, ack, **kwargs):
        self.ack = ack
        super(EchoHandler_O, self).__init__(*args, **kwargs)

    def handle(self):
        for line in self.rfile:
            self.wfile.write(self.ack + line)

from functools import partial
# 通过偏函数给实例定义属性,要不然会报错,少参数,但我执行的时候没报错。
serv1 = TCPServer(('',15000),  partial(EchoHandler_O,ack=b'RECEIVED'))
serv1.serve_forever()

 

7.9 用函数替代只有单个方法的类。

问题:

一个只定义了一个方法的类,(除了__init__方法外),为了简化代码,我们用一个函数代替。

解决方案:

简单的来说就是闭包实现。

上代码案例

from urllib.request import urlopen


class UrlTemplate:

    def __init__(self, template):
        self.template = template

    def open(self, **kwargs):
        return urlopen(self.template.format_map(kwargs))
    

# 通过外部函数传入闭包参数template,然后返回内部函数
def urltemplate(template):
    def open(**kwargs):
        urlopen(template.format_map(kwargs))
    return open

 讨论

无论何时,当在编写代码中遇到需要附加额外的状态给函数,请考虑使用闭包。实例中,用闭包的元编程形式更加优雅。

 

7.10 在回调函数中携带额外的状态

问题:

正在编写使用回调函数的代码,但是希望回调函数可以携带额外的状态以便在回调函数内部使用。

解决方案:

通过传入实例,闭包函数,或者生成器,或者变函数内部携带一个实例。

def apply_async(func, args, *, callback):
    # 运行func函数
    result = func(*args)
    # 结果执行回调函数
    callback(result)

def print_result(result):
    print('Got:', result)

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

if __name__ == '__main__':
    apply_async(add, (2,3), callback=print_result)
    apply_async(add, ('hello', 'world'), callback=print_result)

 

/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_7/t10_2.py
Got: 5
Got: helloworld

Process finished with exit code 0

 上面是一个普通的回调函数,但现在希望回调函数可以同其他变量或者部分函数进行交互时,缺乏这类信息就会带来麻烦。

第一种进化方式,回调函数时一个实例方法

def apply_async(func, args, *, callback):
    # 运行func函数
    result = func(*args)
    # 结果执行回调函数
    callback(result)


def print_result(result):
    print('Got:', result)


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


# 定义一个类
class ResultHandle:
    def __init__(self):
        self.sequence = 0

    def handle(self, reslut):
        self.sequence += 1
        print('[{}] Got: {}'.format(self.sequence, reslut))


# 创建一个闭包函数
def make_handle():
    sequence = 0

    def handle(result):
        nonlocal sequence
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))

    return handle


# 创建一个生成器
def make_handle_2():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))


# 通过变函数,在函数体内部先内置一个实例。
class Sequence:
    def __init__(self):
        self.sequence = 0


sequence = Sequence()


def handle(res, seq):
    seq.sequence += 1
    print('[{}] Got: {}'.format(seq.sequence, res))


if __name__ == '__main__':
    apply_async(add, (2, 3), callback=print_result)
    apply_async(add, ('hello', 'world'), callback=print_result)

    print('=.' * 15)
    # 传入实例
    r = ResultHandle()
    apply_async(add, (2, 3), callback=r.handle)
    apply_async(add, ('hello', 'world'), callback=r.handle)

    print('=.' * 15)
    # 传入闭包函数
    f = make_handle()
    apply_async(add, (2, 3), callback=f)
    apply_async(add, ('hello', 'world'), callback=f)

    print('=.' * 15)
    # 传入一个生成器
    generator = make_handle_2()
    # 预激活
    generator.send(None)
    apply_async(add, (2, 3), callback=generator.send)
    apply_async(add, ('hello', 'world'), callback=generator.send)

    print('=.' * 15)
    from functools import partial

    # 通过偏函数传入实例
    apply_async(add, (2, 3), callback=partial(handle, seq=sequence))
    apply_async(add, ('hello', 'world'), callback=partial(handle, seq=sequence))

    # 通过lambda函数传入实例
    apply_async(add, (2, 3), callback=lambda r: handle(r, sequence))
    apply_async(add, ('hello', 'world'), callback=lambda r: handle(r, sequence))

写的时候,一把全上了。

讨论:

重点全部写在代码注释里面了

 

7.11 内联回调函数

问题:

我正在编写使用回调函数的代码,但是担心小型函数在代码中泛滥,程序的控制流会失控,我们希望有某种方法使代码看来更像一般的过程式步骤

解决方案

书中用了装饰器,队列,生成器,回调函数,整个思路流畅,佩服,羡慕,嫉妒,恨。

国内喜欢叫生产者与消费者的关系,但又涉及了回调函数,确实复杂度一下子上去了。

from queue import Queue
from functools import wraps

def apply_async(func, args, *, callback):
    # 运行func函数
    result = func(*args)
    # 结果执行回调函数
    callback(result)

class Asyns:
    def __init__(self, func, args):
        self.func = func
        self.args = args

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

def inlined_async(func):
    @wraps(func)
    def wrapper(*args):
        # 运行生成器函数,f为生成器
        f = func(*args)
        result_queue = Queue()
        # 首相放入None,用与预激活
        result_queue.put(None)
        while True:
            try:
                # 取回第一个返回的对象
                a = f.send(result_queue.get())
                # 执行回调函数,将执行结果放入队列,用于循环中读取队列
                apply_async(a.func, a.args, callback=result_queue.put)
            # 当生成器执行send,后面没有yiled,碰到return了,产生StopIteration信号,停止循环
            except StopIteration:
                break
    return wrapper



@inlined_async
def test():
    # 包装成对象,到时候供func提取属性使用
    r = yield Asyns(add, (2, 3))
    print(r)
    r = yield Asyns(add, ('hello', 'world'))
    print(r)
    for n in range(10):
        r = yield Asyns(add, (n, n))
        print(r)
    print('Goodbye')


if __name__ == '__main__':
    test()

 

/usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_7/t11_2.py
5
helloworld
0
2
4
6
8
10
12
14
16
18
Goodbye

Process finished with exit code 0

 讨论:

这个代码写的很骚,很好,我喜欢。

 

7.12访问定义在闭包内的变量

我们希望通过函数来扩展闭包,使得闭包在内层定义的变量可以被访问和修改

解决方案

In [49]: def sample(): 
    ...:     n = 0 
    ...:     def func(): 
    ...:         print('n=',n) 
    ...:      
    ...:     def get_n(): 
    ...:         return n 
    ...:      
    ...:     def set_n(value): 
    ...:         nonlocal n 
    ...:         n = value 
    ...:      
    ...:     setattr(func, get_n.__name__, get_n) 
    ...:     setattr(func, set_n.__name__, set_n) 
    ...:     return func 
    ...:  
    ...:  
    ...:      
    ...:                                                                                                                       

In [50]: f = sample()                                                                                                          

In [51]: f.set_n(5)                                                                                                            

In [52]: f.get_n                                                                                                               
Out[52]: <function __main__.sample.<locals>.get_n()>

In [53]: f.get_n()                                                                                                             
Out[53]: 5

 给闭包函数的返回函数的属性添加一些绑定的函数,读取或者修改闭包数据。

讨论:

书中通过测试,对于读取修改数据,闭包的数据修改,比定义类,修改实例属性要快,因为闭包函数不设计额外的self变量。

代码不上了,书中一种是通过属性赋值,给对象附带函数

还有一种是通过实例化方式给对象绑定方法,运行下来使一种快。

 

函数结束,干货满满。

 

posted @ 2020-02-17 01:40  就是想学习  阅读(335)  评论(0编辑  收藏  举报