zoukankan      html  css  js  c++  java
  • Cookbook—函数

    1. 编写可接受任意数量参数的函数

      1.1 接受任意数量的位置参数,使用以 * 开头的参数,例如:

    def avg(first, *args):
        print(args)
        return (first + sum(args)) / (1 + len(args))
                        
    print(avg(1, 2))     1.5
    print(avg(1, 2, 3, 4, 5))    3.0

      args是一个元组,包含所有传过来的位置参数,代码在之后的计算中会将其视为一个元组序列来处理。

      1.2 接收任意数量的关键字参数,使用以 ** 开头的参数,例如:

    def func(name, **kwargs):
        print(kwargs)
        age = kwargs.get("age") or 18
        return f"name: {name}, age: {age}"
    
    
    print(func("ming", gender="female"))            # name: ming, age: 18
    print(func("ming", gender="female", age=22))    # name: ming, age: 22

      kwargs是一个字典,包含所有传过来的关键字参数,代码中要是用到相关参数,可通过字典的方式取值。

      一个函数想要同时接收任意数量的位置参数和关键字参数,只要联合使用 *  和 ** 即可,所有的位置参数都会放置在元组args中,所有的关键字参数都会放置在字典kwargs中。

      1.3 在定义函数时,以 * 打头的参数后仍然可以跟其他参数,这种参数只能作为关键字参数使用,称为keyword-only参数。

    def func2(*args, name):
        pass
    
    func2("a", "b", "c", "d")   # TypeError: func2() missing 1 required keyword-only argument: 'name'
    func2("a", "b", "c", name="d")

    2. 将元数据信息附加到函数参数上

      我们在编写好一个函数后,希望为参数附加上一些额外的信息(即函数的参数注解),这样其他人可以对函数的使用方法有更多的了解。

      函数注解只会保存在函数的__annotations__属性中。

    def func3(x: int, y: int=20) -> int:
        return x + y
    
    print(func3(60))                # 80
    print(func3("a", "b"))          # ab
    print(func3.__annotations__)    # {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

      上述函数注解即:x,y为int类型参数,y的默认值为20,输出的结果类型为int类型。

      python 解释器应不会附加任何语法意义到这些参数注解上,他们既不是类型检查也不会改变Python的行为,所以即使声明形参x,y为int类型,但是调用函数的时候实参为字符串,函数也能正常执行。

      尽管可以将任何类型的对象作为函数注解附加到函数定义上(比如数字,字符串,实例等),但是通常只有类和字符串才显得最有意义。

    3. 定义带有默认值参数的函数

      3.1 如何定义?只需要在定义函数参数时为参数赋值,并确保默认值参数出现在形参最后即可。

    def func4(a, b=50):
        return a + b
    
    print(func4(10))
    print(func4(10, 100))

      给默认参数赋值时的值一定是不可变对象,比如 None,True,False,数字或者字符串或者元组。否则默认值在函数体之外被修改了,那么这种修改会对参数的默认值产生持续影响。

    def func5(a, score_list=[]):
        return score_list
    
    result1 = func5(25)
    print(result1)      # []
    result1.append(50)
    result1.append(100)
    
    result2 = func5(25)
    print(result2)      # [50, 100]  因为result1和score_list的内存地址一样,result1添加值导致score_list也被修改了,所以result2再次调用函数时,对应的score_list已经是修改过的了,已经不再是空[]了

      3.2 如果默认值是可变容器(list,set,dict)时,此时应该把None作为默认值,在函数体里面再对默认参数处理。

      在函数体对默认参数处理时一定是使用 is 操作符,如果使用 if not score_list: pass,那么输入值为空list,空dict,空字符串也会被判定为False。

    def func6(score_list=None):
        if score_list is None:
            score_list = []
            return score_list

      这样 result1=func6(),实际上result1的内存地址对应的是函数体里面重新赋值的空 [] 的内存地址,不会对参数的默认值产生影响。

      3.3 如果不打算提供默认值,只是想检测可选参数是否被赋予了某个特定的值,可以用object()创建一个私有实例。

    _no_value_var = object()
    def func7(score_list=_no_value_var):
        if score_list is _no_value_var:
            print("there is no varies!")
    
    func7()

      在函数中检测是否对可选参数提供了某个特定值,我们不能用None,0,False等作为默认值来检测用户是否提供了参数,因为这些值都是完全合法的参数,用户极有可能把它们当做入参使用。这时我们可以用object()创造一个独特的私有变量,因为对用户来说把这个实例变量作为入参是几乎不可能的,所以它就成为了一个恶意用来安全比较的值。

    4. 匿名函数中绑定变量的值

      4.1 定义匿名函数时,给定默认值:就行普通函数的默认值参数一样。

    f = lambda x, y=10: x + y
    print(f(10))        # 20
    print(f(10, 40))    # 50

      4.2 我们在定义匿名函数时,希望对某个变量进行绑定。

    func_list = [lambda x: x+n for n in range(5)]
    for f in func_list:
        print(f(0))

      仔细思考一下,输出的结果是什么?为什么是5个4呢?这是因为lambda表达式中用到的n是一个自由变量,n只有在执行时才进行绑定而不是定义的时候绑定,所以上面的func_list的5个元素都是x+n,在for循环遍历func_list时,n已经等于4了,所以每一次for循环都是return 0+4!如果我们在定义匿名函数时进行变量绑定,将某个值作为默认参数和变量进行绑定,这样匿名函数在定义时就能捕获到n的值了,如下:

    func_list2 = [lambda x, n=n: x+n for n in range(5)]
    for f in func_list2:
        print(f(0))

      在每次遍历 range(5) 时,都把变量n的值作为默认参数赋值给匿名函数,匿名函数中的变量n的内存地址指向该次遍历值得内存空间,所以得到的结果就为0,1,2,3,4

    5. 让带有N个参数的可调用对象能够以较少的参数个数调用

      对下面一组数据进行排序(如果值小于20就加100)。

    info = {'lilee': 25, 'age': 24, 'phone': 12}
    
    def add_val(x):
        return x[1] + 100 if x[1] < 20 else x[1]
    
    print(sorted(info.items(), key=add_val))        # [('age', 24), ('lilee', 25), ('phone', 12)]

      上面的100是我们规定好了的,如果让用户自己输入值呢?那就在add_val函数中再增加一个参数,但是在sorted函数中就会报错,因为sorted()方法可接受一个key参数用来做自定义的排序处理,但是这个key只能和接受单个参数的函数一个工作,因此不能在add_val中添加第二个参数!

      如果需要减少交互函数的入参个数,应该使用 functools.partial(),函数partial()允许我们给一个或多个参数指定固定的值,以此减少需要提供给交互函数的参数数量。上面的排序就可以重新实现:

    def add_val_2(x, y, z):
        return x[1] + y + z if x[1] < 20 else x[1]
    
    info = {'lilee': 25, 'age': 24, 'phone': 12}
    
    from functools import partial
    print(sorted(info.items(), key=partial(add_val_2, y=50, z=100)))

      例1:以多进程异步方式计算某个结果,并将这个结果传递个给一个回调函数,还毁掉函数可接受这个结果以及一个可选的日志参数:

    import logging
    from multiprocessing import Pool
    from functools import partial
    
    logging.basicConfig(level=logging.DEBUG)
    log = logging.getLogger("test")
    
    def add(x, y):
        return x + y
    
    def output_result(result, log=None):
        if log is not None:
            log.debug(f"Get result: {result}")
        else:
            print(f"Get result: {result}")
    
    if __name__ == '__main__':
        p = Pool()
        # p.apply_async(add, args=(3, 4), callback=output_result)
        p.apply_async(add, args=(3, 4), callback=partial(output_result, log=log))
        p.close()
        p.join()

      用 partial() 就可以继续给回调函数传多个参数(log对象或其他参数),非常实用!

      注意:有时候使用lambda表达式也可以来代替 partial(),所以上面的表达式也可以这么写:

    sorted(info.items(), key=lambda x: add_val_2(x, y=0, z=100))
    p.apply_async(add, args=(3, 4), callback=lambda x: output_result(x, log=log))

      这些代码也可以正常运行,但是却显得非常啰嗦,让人读起来特别地费解。使用partial()会使你的意图更加明确,partial()即为某些函数的参数提供默认值。

    6. 用函数替代只有单个方法的类

    class PerInfo:
        
        def __init__(self, name):
            self.name = name
    
        def get_name(self):
            return self.name
    
    p = PerInfo("ming")
    name = p.get_name()

      我们使用单个方法(不包括__init__()方法)的类的唯一原因就是保存额外的状态给类的方法使用。但是一个类里只有一个方法是不是有点大题小做了?使用嵌套函数或者说闭包常常会显得更加优雅。

    def person_info(name, baseage=15):
        def get_age():
            return f"{name} age is {baseage}"
        return get_age()
    
    age = person_info("gang")
    print(age)

      闭包就是一个函数,但是它还保存着额外的变量环境,是的这些个变量可以在函数中使用。闭包的核心特性就是他可以记住定义闭包时的环境。所以在编写代码中遇到需要附加额外的状态给函数时,我们可以考虑使用闭包。

    7. 访问定义在闭包内的变量

      7.1 为函数添加属性

      函数定义好之后,我们能否继续为这个函数添加一些属性?

    def func():
        return "123"
    
    func.name = "get_123"
    print(func())    # 123
    print(func.name)        # get_123

      显然是可以的,这是因为我们在定义函数时,关键字def 有两个功能:它可以创建一个函数对象;然后把这个函数对象赋值给一个变量(即我们的函数名)。函数的属性以字典的形式存储,key为属性名,value为属性值,可以在定义函数的同时定义属性,也可以在定义函数完成之后再定义函数属性(函数属性可以用过__dict__()查看所有属性)

    def func():
        return "123"
    print(func.__dict__)    # {}
    func.name = "get_123"
    print(func.__dict__)    # {'name': 'get_123'}

      7.2 访问并修改闭包内的变量

      在闭包内层定义的变量对于外界来说是完全隔离的,但是可以通过编写存取函数(如下示例的 get_n,set_n)并将它们作为函数属性附加到闭包上来提供对内存变量的访问支持,如下:

    def sample():
        n = 0
        def func():
            print(f"n={n}")
    
        def get_n():
            return n
    
        def set_n(value):
            nonlocal n
            n = value
        func.get_n = get_n
        func.set_n = set_n
        return func
    
    f = sample()
    f()
    print(f.get_n())    # 0
    f.set_n(100)
    print(f.get_n())    # 100

      这里主要用到了两个特性:nonlocal声明使得编写函数来修改内层变量成为可能;将存取函数以属性的方式附加到闭包函数上,使闭包函数可以直接调用。

     8. 在回调函数中携带额外的状态参数

      我们在编写使用回调函数的代码时,希望回调函数可以携带额外的状态参数以便在回调函数内部使用。

    def apply_async(func, args, callback=None):
        result = func(*args)
        callback(result)
    
    def add(x, y):
        return x + y
    
    def print_result(result):
        print(f"the final result is {result}")
    
    apply_async(add, args=(2, 5), callback=print_result)

      上述的回调函数的入参只能是add()的返回结果,无法携带第二个或多个参数,有多种方式可以解决这个问题。

      1. 使用partial() 或 lambda匿名函数来为回调函数提供多个参数,详细内容可参考上述第5条;

      2. 在回调函数时使用绑定类实例方法:

    def apply_async(func, args, callback=None):
        result = func(*args)
        callback(result)
    
    def add(x, y):
        return x + y
    
    class ResultHandler:
        def __init__(self, n):
            self.multi_value = n
    
        def handler(self, result):
            final_result = result * self.multi_value
            print(f"the final result is {final_result}")
    
    n = int(input("input multi_value: "))
    hd = ResultHandler(n)
    apply_async(add, args=(2, 5), callback=hd.handler)

      3.作为类的替代方案,也可以使用闭包来捕获状态:

    def apply_async(func, args, callback=None):
        result = func(*args)
        callback(result)
    
    def add(x, y):
        return x + y
    
    def result_handler(n):
        multi_value = n
        def result_multi(result):
            nonlocal multi_value
            multi_value += 1
            final_result = result * multi_value
            print(f"the final result is {final_result}")
        return result_multi
    
    n = int(input("input multi_value: "))               # 10
    handler = result_handler(n)
    apply_async(add, args=(2, 5), callback=handler)     # 77
    apply_async(add, args=(2, 5), callback=handler)     # 84
    apply_async(add, args=(2, 5), callback=handler)     # 91

      4. 利用生成器的方式实现(yield关键字用法参考 https://www.cnblogs.com/wushuaishuai/p/9212812.html):

    def apply_async(func, args, callback=None):
        result = func(*args)
        callback(result)
    
    def add(x, y):
        return x + y
    
    def func_count():
        sequence = 0
        while 1:
            result = yield
            sequence += 1
            print(f"times:{sequence}, result value is {result}")
    
    handler = func_count()
    next(handler)
    apply_async(add, args=(2, 5), callback=handler.send)    # times:1, result value is 7
    apply_async(add, args=(2, 5), callback=handler.send)    # times:2, result value is 7

      首先实现一个生成器,然后调用next()方法,使程序执行到 result=yield这句,因为是一个yield语句,所以整个执行过程被挂起,next()的返回值为yield后面的内容,即None;然后在执行send()方法时,将表达式yield的返回值定义为send()方法的参数值(即经过add()计算后的result值),即程序中的result=add()返回值,然后程序继续执行后面的语句,+=1...print...

      注意:在使用生成器的send()方法时,一定要先对其调用一次next(),使程序运行到yield处然后挂起。

     9. 内联回调函数

      我们在编写使用回调函数代码时,总是担心小型函数在代码中大肆泛滥,程序的控制流就会因此而失控,比如下面的这几段代码。

    # 回调函数功能:将计算出的结果打印出来
    def apply_sync(func, args, callback):
        result = func(*args)
        callback(result)
    
    def add(a, b):
        return a + b
    
    def printout(result):
        print(result)
    
    apply_sync(add, args=(2, 3), callback=printout)

      我们希望能够有某种方法使代码看起来更像一般的过程式步骤,如何实现?我们可以通过生成器和协程将回调函数内联到一个函数中:

    from functools import wraps
    from queue import Queue
    
    class Async:
        def __init__(self, func, args):
            self.func = func
            self.args = args
    
    def apply_sync(func, args, callback):
        result = func(*args)
        callback(result)
    
    def add(a, b):
        return a + b
    
    def inline_async(func):
        @wraps(func)
        def wrapper(*args):
            result = func(*args)
            result_queue = Queue()
            result_queue.put(None)
            while True:
                result_from_queue = result_queue.get()
                try:
                    a = result.send(result_from_queue)
                    apply_sync(a.func, a.args, callback=result_queue.put)
                except StopIteration:
                    break
        return wrapper
    
    @inline_async
    def test():
        r1 = yield Async(add, (2, 3))
        print(r1)
        r2 = yield Async(add, ("hello", "world"))
        print(r2)
    
    test()


      除了上面特殊的装饰器和对yield的使用外,我们发现代码中根本没有出现回调函数(它们事不过隐藏在装饰器里而已)。

      程序的核心点在inline_async()装饰器函数中:对于生成器函数的所有yield语句,装饰器都会逐条进行跟踪,一次一个。我们创建了一个队列来保存结果,初始值我们用None来填充,当执行到send(None)时, 生成器函数开始执行并在yield处挂起,然后用变量a来接收yield后面的值(即Async的实例),紧接着执行spply_sync(),并将结果put入队列里,之后通过循环将结果从队列中取出,然后send()发送给生成器,生成器紧接着执行yield表达式并将yield表达式的返回值定义为取出来的结果,用变量接收,然后打印出来,实现回调函数的功能;如果从队列中什么都没有get()到,操作就会阻塞,等待之后某个时刻会有结果到来。至于结果如何产生就取决于apply_async()主体函数的实现了。

  • 相关阅读:
    javascript Math.random()随机数函数
    asp.net 前台获取后台c#代码信息
    关于C#网站一般处理程序(ashx)中session的问题
    怎样才能在一般处理文件中创建新的Session和访问已经存在的Session?
    使用SqlParameter向数据库中插入数据
    C#串口编程学习简单实例
    认识nodejs
    01.Javascript中的接口Interface [转载]
    动态添加脚本,并触发回调函数 初步实现按需加载
    JS正则表达式 收藏
  • 原文地址:https://www.cnblogs.com/bk9527/p/13264809.html
Copyright © 2011-2022 走看看