• 掌握这些知识,你的python水平能更上一层楼


    今天讲一些python中的高级用法,有助于大家更好的使用python这门语言。今天讲的这些知识是层层递进的关系,前面是后面的铺垫。

    函数可变参数*args和**kwargs

    python支持固定参数,默认参数,也和很多其他语言一样支持可变参数,只不过python支持的可变参数分为两种,*args是tuple,里面可以有任意多个element(包括0个)。**kwargs则是当你需要指定key word时需要用到的参数类型。

    先考虑*args的情况,先看函数定义:

    >>> def take_any_args(*args):
    ...     print("type of args:" + str(type(args)))
    ...     print("value of args:" + str(args))
    ... 
    >>> take_any_args(1)
    type of args:<type 'tuple'>
    value of args:(1,)
    >>> take_any_args("a","b","c")
    type of args:<type 'tuple'>
    value of args:('a', 'b', 'c')
    >>> take_any_args()
    type of args:<type 'tuple'>
    value of args:()
    >>> take_any_args(['1','2'],['3','4'])
    type of args:<type 'tuple'>
    value of args:(['1', '2'], ['3', '4'])

    再看参数提取:

    def print_args(*args):
        for arg in args:
            print(arg)
    
    print_args("red", "blue", "green")
    
    def print_all(*args, **kwargs):
        for arg in args:
            print(arg)
        for key, value in kwargs.items():
            print("{} -> {}".format(key, value))
    
    print_all(1, 2)
    print_all(a="red", b="blue", c="green")
    red
    blue
    green
    1
    2
    a -> red
    c -> green
    b -> blue

    下面看看为什么需要**kwargs, 对于上面的print_args, 下面这种添加了keyword的调用方式会出错,所以就有了**kwargs的用武之地:

    >>> def print_args(*args):
    ...     for arg in args:
    ...         print arg
    ... 
    >>> print_args(a=1,b=2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: print_args() got an unexpected keyword argument 'a'

    **kwargs的本质其实是dict,如下所示:

    >>> def print_kwargs(**kwargs):
    ...     for key,value in kwargs.items():
    ...         print("{}->{}".format(key,value))
    ... 
    >>> print_kwargs(a="lalala",b="papapa")
    a->lalala
    b->papapa

    通常再使用的时候都是二者合起来使用,如下所示:

    def print_all(*args, **kwargs):
        for arg in args:
            print(arg)
        for key, value in kwargs.items():
            print("{} -> {}".format(key, value))
    
    print_all(1, 2)
    print_all(a="red", b="blue", c="green")
    1
    2
    a -> red
    c -> green
    b -> blue

    上面的知识大家差不多应该都知道,下面这种Unpacking的用法很多人都不太了解:

    >>> def sample_function(a,b,c):
    ...     print("{},{},{}").format(a,b,c)
    ... 
    >>> input = (1,2,3)
    >>> sample_function(1,2,3)
    1,2,3
    #和上面的方法等效
    >>> sample_function(*input)
    1,2,3

    unpack 使用kwargs,记住keyword要和函数声明时的变量名一致才行,否则会报错

    >>> def sample_function(a,b,c):
    ...     print("a->{},b->{},c->{}".format(a,b,c))
    ... 
    >>> input = {"a":1,"b":2,"c":3}
    >>> sample_function(**input)
    a->1,b->2,c->3
    #与上面方法等效
    >>> sample_function(a=input['a'],b=input['b'],c=input['c'])
    a->1,b->2,c->3

    lambda function

    在python 中所有的东西都是object,不管是int也好,list也好都是object。函数也是object。这个概念很重要。

    >>> def f(n):
    ...     return n+1
    ... 
    >>> id(f)
    4374076184
    >>> g = f
    >>> print g(2)
    3
    >>> id(g)
    4374076184

    上面的g和f所指向的object是同一个object

    下面思考这样一个问题,如果 numbers = ["10", "3", "40", "14", "5"], 让你找出最大值怎么找?

    >>> max(numbers)
    '5'

    这显然不对,因为max默认按照字母顺序排序了,所以需要额外提供排序信息:

    >>> max(numbers, key=int)
    '40'

    int 就是一个function,然后看看如果用lambda function表示就是:

    >>> max(numbers, key=lambda x:int(x))
    '40'

    再举一个例子, 下面是几个人的年龄,性别,地址,请找出年纪最大的人:

    >>> person_zhangsan = {'age': 40, 'gender': 'male', 'home': 'beijing'}
    >>> person_lisi = {'age': 35, 'gender': 'male', 'home': 'hangzhou'}
    >>> person_wangwu = { 'age': 21, 'gender': 'female', 'home': 'chongqing'}
    >>> people = [person_zhangsan, person_lisi, person_wangwu]
    >>> max(people, key=lambda x:x['age'])
    {'gender': 'male', 'age': 40, 'home': 'beijing'}

    python在operator中提供了itemgetter这个函数,它起到的作用和lambda function一样,比如:

    >>> from operator import itemgetter
    >>> max(people, key=itemgetter("age"))
    {'gender': 'male', 'age': 40, 'home': 'beijing'}

    对比一下我还是更喜欢lambda function的定义,简洁明了

    Decorator 装饰器

    最长见的decorator的user case是什么?答:retry。 比如网络restful request碰到不稳定的server或者说server给你返回了5XX,你要不要retry。

    一开始可能你的code长这样:

    import requests
    URL = "https://example.com/api"
    
    def get_items():
        return requests.get(URL + "/items")

    当然你还会有很多get function,比如get_apple, get_banana, get_orange, ...

    实际部署之后发现server不稳定,不定期返回500,你就要加retry

    如果只有一个get_items,你可能会这么写:

    #第二版,加入retry
    def get_items():
        NUM_RETRY = 3
        current_retry = 0
        resp = None
        while True:
            resp = requests.get(URL + "/items")
            if rest.status_code/100 == 5 and current_retry < NUM_RETRY:
                current_retry += 1
                continue
            break
        return resp

    可是每一个fucntion都要改,是不是很累。。。

    下面decorator隆重登场,decorator的本质是一个function。这个function的parameter有且仅有一个就是一个function object,返回值则是另一个不同的function

    #比如已经有了一个普通function
    def some_function(arg1,arg2,arg3):
        #此处省略20行
    
    some_function = some_decorator(some_function)

    等效于

    @some_decorator
    def some_function(arg1,arg2,arg3):
          #......

    下面举一个decorator的例子,logging decorator

    def logfuncname(func):
        def wrapper(*args, **kwargs):
            print("function name: " + func.__name__)
            return func(*args, **kwargs)
        return wrapper
    >>> @logfuncname
    ... def some_func(n):
    ...     return n+1
    ... 
    >>> print some_func(3)
    function name: some_func
    4

    如上所示,logfuncname就是一个decorator,它的input是func,return了一个wrapper function。

    下面我们回到一开始retry那个例子:

    #第三版,定义decorator
    def retry(func):
        def wrapper(*args, **kwargs):
            NUM_RETRY = 3
            current_retry = 0
            resp = None
            while True:
                resp = func(*args, **kwargs)
                if rest.status_code/100 == 5 and current_retry < NUM_RETRY:
                    current_retry += 1
                    continue
                break
            return resp
        return wrapper
    
    @retry
    def get_items():
        return requests.get(URL + "/items")

    然后get_apple, get_banana, get_orange 什么的上面加上@retry就可以了

    接下来问题来了,如果有另一个decorator也想用上怎么办?

    decorator是可以叠加的,比如下面的例子,注意上下顺序就是decorator从左到右的顺序

    @add2
    @multi3
    def foo(n):
        return n + 1
    
    #相当于foo = add2(multi3(foo))
    #那么foo(3)就是14
    
    @multi3
    @add2
    def foo(n):
        return n + 1
    
    #相当于foo = multi3(add2(foo))
    #那么foo(3)就是18

    如果想要改变retry的次数怎么办,比如get_apple想要retry 3次,但是get_banana想要retry 5次怎么办?

    #第四版,定义带参数的decorator
    def retry(num_retry):
        def decorator(func):
            def wrapper(*args, **kwargs):
                current_retry = 0
                resp = None
                while True:
                    resp = func(*args, **kwargs)
                    if rest.status_code/100 == 5 and current_retry < num_retry:
                        current_retry += 1
                        continue
                    break
                return resp
            return wrapper
        return decorator
        
    
    @retry(3)
    def get_items():
        return requests.get(URL + "/items")

    这里其实用到了一个closure的概念,就是外层函数的参数在里层函数里是可见的,而里层函数的参数在外层不可见(当然这里也不需要)。

    Decorator在flask中的实现原理

    下面我们来看看flask中decorator是怎么实现的,简而言之:

    class WebApp:
        def __init__(self):
    #初始化routes self.routes
    = {} def route(self, param): def decorator(func):
    #定义decorator时为routes赋值key/value self.routes[param]
    = func def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper return decorator def get(self, param): try:
    #get时根据key返回value
    return self.routes[param]() except KeyError: return "ERROR - no such page" >>> app = WebApp() >>> @app.route("/") ... def index(): ... return 'Index Page' ... >>> @app.route("/contact/") ... def contact(): ... return 'Contact Page' ... >>> app.get("/") 'Index Page' >>> app.get("/contact/") 'Contact Page' >>> app.get("/no-such-page/") 'ERROR - no such page'
  • 相关阅读:
    SQL基础 union的用法
    VSCore的使用方法
    k8s——了解kubernetes机理
    电子科大啦
    19款程序员最爱的开发框架
    Exp1 PC平台逆向破解(5)M
    电子阅读
    Python学习记录:基础知识1
    WCF使用小结:(1)WCF接收HTTP POST数据的处理方法
    Linux配置经验总结:(3)禁用笔记本触摸屏
  • 原文地址:https://www.cnblogs.com/huashao1985/p/8548470.html
Copyright © 2020-2023  润新知