zoukankan      html  css  js  c++  java
  • Python 函数和装饰器

    函数的定义和调用

    定义:def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个英文冒号":"。

    函数名:在Python中函数即变量,所以函数名也同样遵循变量的命名约束。数字字母下划线组成,不能以数字开头且应具有描述函数功能的作用。

    括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!

    注释:每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面第一行。以增强代码的可读性。

    调用:就是函数名() 函数名加括号 !不加不执行

    来个函数

    >>> def bar():
    ...   print("hello function!
    ")
    ... 
    >>> bar   # 不加括号给的是这个函数的地址,
    <function bar at 0x10da670d0>
    >>> bar()  # 加了括号才会被执行
    hello function!
    

    我们现在想让一个函数完成两个数的加和操作,即我们自己来实现sum()这个内建函数的功能。

    def mysum(a,b):
        sum = a+b
    result = mysum(1,2)
    print(result)
    

    结果为:None

    怎么样才能让他能像sum()一样呢? 现在的函数功能已经完美的实现了,但是没人知道这个函数已经把任务完成了! 怎么证明自己呢,函数要把执行的结果返回让大家看到!

    在函数的最后加上 return关键字

    def mysum(a,b):
        sum = a+b
        return sum
    
    result = mysum(1,2)
    print(result)
    

    结果为:3

    详细讨论这个返回值

    1、没有return关键字

    这样的情况在Python中是被允许的,默认的Python返回了 None

    2、有return但是后面没有值

    def bar():
        print(“I’m bar!”)
        return
    print(bar)
    

      结果为:None

    3、有return有返回值,当然上述例子mysum就是例子,显然是被允许的,并且这个返回值能被一个变量名引用到。

    4、有return 有多个返回值

    def mysum(a,b):
        sum = a+b
        return a,b,sum
    
    result = mysum(1,2)
    print(result)
    

    结果为:(1, 2, 3)

    当返回值为多个值的时候Python会把几个对象封装成一个元组,同时我们可以用多个变脸分别一一对应的取到每一个返回值,也可以用一个变量取到这个元组

    注意 在Python中,将用逗号 "," 分割的连续的几个值,认为是一个元组

    >>> 1,2,3
    (1, 2, 3)
    

    序列解压

    >>> a,_,_,_,b = [i for i in range(5)]
    >>> a
    0
    >>> b
    4
    >>> a,*_,b,c = 'unpack string'
    >>> a
    'u'
    >>> b
    'n'
    >>> c
    'g'
    >>> _
    ['n', 'p', 'a', 'c', 'k', ' ', 's', 't', 'r', 'i']
    >>> type(_)
    <class 'list'>
    >>> a,_,c = {1:'a',2:'b',3:'c'}
    >>> a
    1
    >>> c
    3
    >>> *_,end = (1,2,3,4,5,6)
    >>> end
    6
    

    return的作用:

    当函数被调用时,Python解释器会由上到下的执行函数体中的语句,当执行到 return关键字时即终止函数并返回结果:之后的代码不会被执行!

    def mysay():
        print("hello first!")
        print('我是华丽的分割线'.center(30, "*"))
        return
        print("hello second!")
    mysay()
    

      结果为:

    hello first!
    ***********我是华丽的分割线***********

    由此可见,return 什么都不写跟 直接不写return 还是有区别的,return可以指明函数在哪里结束,如果没有return 也没用中途报错 函数将执行完整个函数体里的代码。

    函数参数

    在两数字之和的时候我们看到在函数名后的括号里有两个参数a,b。这两个数称为函数的形参,当函数调用时传入具体的参数称为函数执行时候的实参。

    参数可以有多个,定义和调用时,参数都需要用逗号分割。

    函数的参数分为:位置参数,关键字参数,默认参数,动态参数

    注意:函数传参必须是一个不可变对象的引用或对象本身。因为Python的传参遵循了主流面向对象的传参方式:形参实参共享对象。

    就实参而言

    位置传参

    这种方式,必须严格的按照形参的位置来传递实参,不然,结果将不是我们期望的样子,参数多了少了都会报错

    def func(arg1,arg2,arg3)
        pass
    func('name','age','salary')     # 此时 :arg1 = 'name'  arg2 = ’age'  arg3 = ’salary'
    

    关键字传参数

    这种方式,丝毫不需要在意位置,并且有默认值,即使我们不传入实参,也不会报错。

    def func(arg1,arg2,arg3)
        pass
    func(arg3=’name',arg2 = ’age', arg1 = ’salary')     # 此时 :arg1 = 'salary’  arg2 = ’age'  arg3 = ’name’
    

    混用

    在两种方式混用时,必须遵循,位置传入的参数在前,关键字传入的参数在后,并且,一个形参只能被传入唯一的一个值。

    def func(arg1,arg2,arg3)
        pass
    func('name',arg3 = ’age',arg2 = ’salary')     # 此时 :arg1 = 'name'  arg2 = ’salary’  arg3 = ’age’
    

    就形参而言

    位置参数:位置参数必须被传入单一的对应值,无论用何种方式传参。

    关键字参数:形参的关键字参数,也就是形参的默认值。多数时候不需要修改的值我们多设为默认值。

    def style(id, color="blue"):
        """这是一个样式控制函数,大多数时候背景颜色为蓝色
            此时 将color 设置为 蓝色
        """
        return id, color
     
    print(style(3,'red'))
    print(style(3))
    

    注意:尽可能的避开可变类型称为默认值的情况(避开容器类型),因为它们是对象的引用的集合,当参数被传入函数时候,所做的一切操作都不会修改容器本身,而是修改了其中的对象。造成了变化的累积效应。

    def init_data(name,lst =[]):
        lst.append(name)
        return lst
     
    print(init_data('monkey'))
     
    print(init_data('JIAJIA'))
     
    ['monkey']
    ['monkey', 'JIAJIA']
    # 不是想象中的初始化两个列表,而是在同一个表上累积
    

    形参和实参是共享对象的,lst 是一个可变对象的引用,函数每次调用时默认传的是同一个可变的对象,是同一个地址,每次修改的都是相同的可变对象。

    动态参数

    def bar(id,age=18,*args,**kwargs):
        print(id,age,args,kwargs)
     
    bar(1608,'谁收留我1','谁收留我2','看看谁要我3',sex='famale')
    
    # 1608
    # 谁收留我1
    # ('谁收留我2', '看看谁要我3')
    # {'sex': 'famale'}
    

    位置参数必须要按照位置传参,多余的位置参数会被封装成元组的形式保存在args中,多余的关键字参数会被打包成字典封装在kwargs中。

    函数嵌套

    函数的嵌套调用就是在函数中调用另一个函数。

    def func():
        print('func')
     
    def bar():
        print('The bar')
        func()
     
    bar()     # 在bar中调用func()
    

    定义

    def func():
        funcname = 'func'
        print(funcname)
        def bar():
            funcname = 'bar'
            print(funcname)
            def foo():
                funcname = 'foo'
                print(funcname)
            foo()
        bar()
    func()
    

    nonlocal 在嵌套函数中使用 nonloacl 关键字 声明变量为上层函数变量,而非本层函数。

    nonlocal的使用规则:

    1、他必须在某函数的内嵌函数中使用。

    2、在他声明的变量之前当前函数中不能有同名的变量存在。

    注意 : 无法绑定到全局变量,只能是局部变量。

    def outfunc():
        name = 'outfunc'
        def infunc():
            nonlocal name
            name = 'infunc'
            print(name)
        infunc()
        print(name)
    outfunc()
    

    作用链域

    name = 'error! '
    def outfunc():
        print('Outfunc name ="{}"'.format(name))
     
    def func0():
        name = 'monkey'
        def func1():
            print('In func1 name ="{}"'.format(name))
            def func2():
                outfunc()
                print('In func2 name ="{}"'.format(name))
            func2()
        func1()
        outfunc()
    func0()
    
    # In func1 name ="monkey"
    # Outfunc name ="error! "
    # In func2 name ="monkey"
    # Outfunc name ="error! "
    

    注意

    函数会在执行前检查函数体中变量的定义,如果在本层没有找到,就往它的外一层找,还没找到就再往外层,直到找到全局,还没找到就报错,找到了就使用。

    高阶函数

    定义:函数的参数或返回值是函数的函数,就称为高阶函数(当返回值为函数本身,则称为函数的递归)。

    函数的参数是另一个函数

    def bar():
        print("  This is bar!")
     
    def foo(func):
        print("This is foo:")
        func()
     
    foo(bar)
    

    将 bar 作为函数 foo 的参数传入,可以在foo中执行bar 即 foo 中的func。

    函数的返回值是另一个函数

    def foo():
        print("This is foo:")
        def foo_inner():
            print("This is foo_inner")
        return foo_inner
    ret = foo()
    ret()
    
    

    将 foo_inner 作为返回值传出,在函数外执行。

    函数闭包

    嵌套函数中,内部函数调用外部函数的变量

    def outer():
        name = 'outer'
        def inner():
            print(name)
        print(inner.__closure__)
    outer()
    
    # (<cell at 0x100f28408: str object at 0x100e51538>,)
    

    closure方法来判断是否为闭包

    以cell 开头,这就是一个闭包 返回的是一个 None 则不是闭包

    0x100e51538 是inner的地址

    str是使用外层变量的类型 后面是其内存地址

    闭包的正确姿势

    def outer():
        name = 'outer'
        def inner():
            print(name)
        return inner
    func = outer()
    func()
    

    这样子来,name 会被长久的保存下来,因为func接受了outer内部函数的地址,而内部函数使用了outer下的变量 name 所以,这个变量得以在内存中长久的保留。

    如果在一个内部函数里对在外部作用域(但不是在全局作用域)的变量进行引用,但不在全局作用域里,则这个内部函数就是一个闭包。

    实际上,闭包的用处/优点有两条:

    从函数外可以读取函数内部的变量,或直接执行内层函数

    让这些变量的值始终保持在内存中(也可以理解为保留当前运行环境)

    装饰器

    装饰器是什么?完成怎么样的功能?

    装饰器要求再不修改函数代码,不修改函数调用方式的前提下,为函数添加新的功能。

    装饰器的本质是函数

    在Python中函数和变量其实本质上是一样的,变量可以指向一个函数对象。函数即变量!

    初级版本装饰器

    定义一个被装饰的函数:

    def func():
        print("被装饰的函数")
    

    版本一

    我们想到定义一个 decrator_v1 函数 接受被装饰的函数,加上新功能后返回这个函数的地址。然后再将func变量指向 decrator_v1 函数,参数为 func 貌似大功告成。

    def decrator_v1(foo):
        print('这是新功能')
        foo()
        return foo
    func = decrator_v1(func)
    func()
    
    # 这是新功能
    # 被装饰的函数
    # 被装饰的函数
    

      哎呀~ 为什么 跟预期的结果不一样??? 从头到尾的捋了一遍!卧槽 func 函数被执行了两边,并且 第二遍压根就没鸟我们的新功能!

    1 当执行func = decrator_v1(func)的时候,调用了decratoe_v1这时执行了我们想要的结果。

    2 但是当返回foo 被变量func接受时,我们执行func,就执行了foo,此时的foo就是原来我们定义时候的func函数。因此,出现了两次执行,第二次只执行了func。

    3 我们希望把 新功能和func本身封装在一起,要执行一起执行,就不会出现这样扯淡的情况了很显然,很自然我们想到了函数的闭包,封装到一个函数 那么 来吧 让我们封装一下。

    版本二

    def add_way(func):               #1
        def wrapper():                          #3
            print('这是新功能')                          #6
            func()                                          #7
        return wrapper                              #4
     
    func = add_way(func)                 #2
    func()  
    
    # 这是新功能
    # 被装饰的函数
    

    完美~ 终于完成了使命~ !

    解释一下代码:

    相对于版本一来说,定义的wrapper函数就是用来解决版本一中调用就立即执行add_way()的问题的。

    现在基本的满足了功能上的需求,没有改变调用方式,也没有改变原函数的代码。Python提供了一个 语法糖 "@" 来帮我们完成func = add_way(func)这件事情!

    语法糖版本

    def add_way(func):
        def wrapper():
            print('这是新功能')
            func()
        return wrapper
     
    @add_way
    def func():
        print("语法糖版本:被装饰函数")
     
    func()
    

    到现在为止,装饰器就基本上成型了~!

    回顾一下我们都做了什么:

    1、利用高阶函数把 被装饰函数当成参数出入装饰器对象,然后 (函数的嵌套)用函数封装 新增方法(功能)之后把 封装后的对象
    2、作为装饰器对象(函数)的返回值,然后将 被装饰函数名作为装饰器对象的引用,这样 就实现了装饰器
    不改变函数的代码
    不改变方法的调用方式
    实现新增功能

    再来打磨一下:

    被装饰函数属性变化,依靠某些属性工作的模块可能无法工作

    print('装饰之后的函数文档',func.__doc__)
    print('装饰之后的函数名',func.__name__)
    print('装饰之后的函数哈希值',func.__hash__)
    结果与被装饰之前是不一样的~ 那么依靠__name__ 属性工作的模块就无法正常工作了~ 
    

    被装饰函数如何传参?

    终极版本装饰器

    打磨后的装饰器:

    注意

    1、from functions import wraps 用wraps装饰器 来 装饰 传入我们自定义装饰器要装饰的函数,即接收的那个函数。

    2、wrapper 要接受func(被装饰函数的)的所有参数

    3、wrapper函数要返回func(被装饰函数的)返回值。

    #!/usr/bin/env python3
    #_*_ coding: utf-8 _*_
    __author__ = "monkey"
     
     
    from functools import wraps
    # 真正完备的装饰器
    # 先来看一下解释器对wraps函数的注释
    """Decorator factory to apply update_wrapper() to a wrapper function
     
       Returns a decorator that invokes update_wrapper() with the decorated
       function as the wrapper argument and the arguments to wraps() as the
       remaining arguments. Default arguments are as for update_wrapper().
       This is a convenience function to simplify applying partial() to
       update_wrapper()."""
     
     
    def func( *args,**kwargs):
        '''
        :param args:接受多余的位置参数
        :param kwargs: 接受多余的关键字参数
        :return: 返回拿到的所有参数
        '''
        print('我是func')
        return args,kwargs
     
    print('装饰之前的函数文档',func.__doc__)
    print('装饰之前的函数名',func.__name__)
    print('装饰之前的函数哈希值',func.__hash__)
     
     
    def decrator_end(func):               #1
        '''
        :param func: 接受被装饰函数对象,以便与在装饰器内部执行
        :return: 一个封装新功能的对象的地址
        '''
        @wraps(func)        #在这里会把wrap 函数的性质完完整整的转变成func函数的样子
        def wrapper(*args,**kwargs):                          #3
            '''
            wrapper 因为这里的wrapper要接受func传入的所有参数
            :param args: 我的含义大家都知道
            :param kwargs: 我跟上面一样
            :return: 被装饰函数的执行结果
            '''
            print('这是新功能')                          #6
            ret = func(*args,**kwargs)                                          #7
            return ret
     
        return wrapper                              #4
     
    func = decrator_end(func)                 #2
    x = func('hook','monkey',name='monkey')
    print(x)
     
    print('装饰之后的函数文档',func.__doc__)
    print('装饰之后的函数名',func.__name__)
    print('装饰之后的函数哈希值',func.__hash__)
    

    结果:

    装饰之前的函数文档
     
        :param args:接受多余的位置参数
        :param kwargs: 接受多余的关键字参数
        :return: 返回拿到的所有参数
         
    装饰之前的函数名 func
    装饰之前的函数哈希值 <method-wrapper '__hash__' of function object at 0x10969ce18>
    这是新功能
    我是func
    (('hook', 'monkey'), {'name': 'monkey'})
    装饰之后的函数文档
     
        :param args:接受多余的位置参数
        :param kwargs: 接受多余的关键字参数
        :return: 返回拿到的所有参数
         
    装饰之后的函数名 func
    装饰之后的函数哈希值 <method-wrapper '__hash__' of function object at 0x1098bf7b8>
    

    注意观察 装饰之后的属性变化,虽然貌似完全是被装饰函数本身,但是通过__hash__方法我们知道那并不是原来的__hash__事情总不是那么尽善尽美,达到需求即可!这样就可以了,真的要修改__hash__ 可以自己在函数中 重写 类的__hash__方法即使这样 他仍然不是原来的函数,内存不一样,怎么改 都只是很像 改变不了他们是两个对象的事实,但是对于使用而言,对用户 或 调用者透明,封装之后他们是不是一个对象这个问题没人会去关注!所以不必要非苛求完美!

  • 相关阅读:
    小程序用户拒绝授权地理位置的处理办法
    云开发小程序数据库权限有限,通过云函数修改数据库评论信息
    小程序仿照微信朋友圈点击评论键盘输入
    小程序wx.previewImage查看图片再次点击返回时重新加载页面问题
    js手机端判断滑动还是点击
    Proxy
    Reflect.has检测对象是否拥有某个属性
    简单的axios请求返回数据解构赋值
    为windows terminal 配置 conda
    git clone 遇到问题:fatal: unable to access 'https://github.comxxxxxxxxxxx':
  • 原文地址:https://www.cnblogs.com/monkey-code/p/13159351.html
Copyright © 2011-2022 走看看