zoukankan      html  css  js  c++  java
  • 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变量。

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

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

    函数结束,干货满满。

  • 相关阅读:
    jsonp 的 post
    js replace常用用法
    zindex
    x秒前
    手写jsonp
    webview 冒泡慢?
    人民币大写转阿拉伯数字
    checked
    Deadlock Troubleshooting Trace 1222
    [转]基于LUCENE实现自己的推荐引擎
  • 原文地址:https://www.cnblogs.com/sidianok/p/12319811.html
Copyright © 2011-2022 走看看