流畅的python装饰器、变量的作用范围

2022-08-20 12:19:53

关于main函数

  • 一个程序自带一个__name__内置的属性,它在python中准确来说不是主函数的入口,主函数入口只是它功能的一个部分,它核心的功能是为了测试。
  • 在python中,是靠着代码缩进对各个部分进行的判定的,凡是在python代码中直接靠在最左面没有缩进的都是主函数的一部分,无论是直接运行这个代码文件还是将这个代码文件导入其它的文件内部,这部分代码都会立即执行。
  • __name__这个内置的属性值一般就2种,第一种是'__main__',每一个文件默认的__name__属性值;第二种是当前文件的文件名;当直接运行当前程序时,__name__就等于'__main__',所以if __name__ = '__main__' :后面的代码就会执行,此时这部分就相当于主函数;但是当在另外的一个文件中导入当前的文件时,当前文件的__name__值就由'__main__'被更改为导入的文件名,这样的话被当前文件的if __name__ = '__main__'就不成立了,就变成if __name__ = '__文件名__',故这部分代码就不执行了,此时if这部分代码就不是主函数了。
  • 要适应python没有main()方法的特点。所谓的主函数main其实也就是个 《  if条件语句  + 缩进部分 》,判断成功就执行一些代码,失败就跳过,主要用于测试。没有java等其他语言中那样会有特定的内置函数去识别main()方法入口,在main()方法中从上而下执行。
  • https://blog.csdn.net/Mart1nn/article/details/81077234
  • 导入模块 / 直接运行时,会执行的部分就是主函数main(本质上就是主函数)  +    装饰器(注意不是被装饰函数,是装饰器) ,其中运行程序是被装饰函数定义后装饰器立即执行,而导入是导入模块后立即执行,这里的立即执行就是马上立刻,紧接着。

变量的作用范围

  • 在python里面如果要想使用全局变量,即允许在主程序和函数里面使用,且允许在主程序和函数里面修改的变量,必须用global在定义的时候声明,声明不管在函数里面声明还是在主程序声明,其余位置都可以调用,如果在函数里啊申明,主程序可以调用,如果在主程序声明,函数里面也可以调用。         如果是局部变量,python判断局部变量的标准就是在函数里面给某一个变量赋值,就把该变量视为局部变量,局部变量在当前函数调用时创建,在函数执行完毕后销毁,即局部变量的声明周期只在函数内部。如果一个名字既在外面又在函数里面,二者的输出互不影响,虽然同名,函数里面输出函数里面的值,函数外面输出函数外面的值。
  • 以上就是全局变量和局部变量的判定方法和使用方法。
  • 但是python给了一种特殊的调用,即允许在函数里面使用主程序定义的变量。这一点非常重要,允许使用的定义是允许直接调用,而不允许做出修改。因为一旦做出修改,根据上面局部变量的定义,修改就是重新赋值,此时就变成了局部变量,就和外面那个变量没关系了,虽然是同名,在函数内部调用就是输出函数内部的值,在函数外部调用输出函数外的值。所以如果只是想对外面的变量进行调用,那么就要注意了。如果外部的变量是一个可变的数据类型,可变的数据类型因为变量保存的是地址,所以对于可变数据类型里面的每一个元素在函数里面是确又是可以修改的,这个修改影响外部的值,但是如果直接修改这个变量名,那么就变成了局部变量,和外面的值没关系了。对于不可变的数据类型,如果想实现调用这个功能,那么就是不允许修改,否则就变为了局部变量。综上所述,对于在函数外面定义的不可变的数据类型,如果仅仅想使用,那么函数里面不允许修改,如果更改就变为局部变量;在函数外面定义的可变的数据类型,函数里面变量值是不允许修改,否则也是变为了局部变量,但是里面的元素可以修改,此时此变量没有变成局部变量,且影响外面。
  • 由于函数的一等性,我们将函数视为一个普通的变量,可以当作一个参数传递,也可以当作一个值返回,可以函数里面调用另外的一个函数(这个是C++等其它语言都有的),也可以函数里面嵌套函数,这三个是其它语言里面没有的,当然函数的一等性还有其它,这里就不一一罗列了。作为参数的函数往往是高阶函数那里讨论的,函数作为返回值一定要注意只返回函数名字,不返回函数名字带(),因为函数如果带括号就表示函数执行了,就是调用了,所以函数作为返回值不带括号返回,这就是原因。
  • 这里我们重点讨论一下函数里面且套函数,外面函数相当于上述的主程序,里面的函数相当于上述的内部函数,唯一不同的就是上述的主程序变量一旦定义了之后一直都会在,而这里的外部函数当函数执行完毕后变量就释放了,就不存在了。而装饰器这种东西却又是函数嵌套函数,且返回嵌套的函数名字。

装饰器

  • 装饰器为函数套函数,且返回内部嵌套的函数的函数名字,不带括号注意,带括号就直接调用了。
  • 为什么装饰器要函数套函数且返回内部的嵌套函数名,而不是函数直接调用另外的一个函数,而是非要在函数内部定义一个函数?那是因为装饰器本身就是为了将方法进行扩展,并且将扩展了的整体的方法进行返回,所以装饰器的参数必须要是扩展的函数的形参func,而装饰器要返回一个扩展了的函数,只要装饰器将被定义的函数放在内部是为了将数据和方法整个放在装饰器内部,类似于类的意思,将其集成到一起,便于处理。所以装饰器的构成就是  :
  • def zhuang(func):
        一些变量
        def kuozhan():
            对func进行扩展的操作
            func()
        return kuozhan
    可以发现上述代码的一些问题:当执行该函数的时候,直接return kuozhan,zhuang里面的变量按照局部变量就直接销毁了,那么在kuozhan函数里面就不能对这些变量进行访问,这个和函数使用主程序变量不一致,主程序变量一直都在,而这里却不能了。为了客服这种问题,在python里面提出了一种闭包的概念,目的就是为了函数里面嵌套函数如果外层函数不在,依旧能访问里面变量的一种机制。即闭包是一种函数,会保留定义函数时存在的自由变量的一些绑定,这样调用函数时,即使变量的作用域不在了,仍然能使用这些绑定。自由变量就是未在本地作用于绑定的变量叫做自由变量,虽然未在本地定义,但是我们进行了绑定,所以仍然能使用。所以自由变量是相对于上述非本地函数kuozhan而言的,这点要明白。自由变量名称存储在kuozhan.__code__.freevars里面,而值存储在kuozhan.__closure__[i].cell_cintents。
  • 还有一点是要想在函数里面直接使用外面函数的变量,我们这里不使用global,global定义的变量是全局变量,不仅仅各个函数能使用,主函数也能使用,我们为了外部函数定义内部函数使用且可以更改且主函数不能调用,我们定义了nonlocal关键字,将变量标记为自由变量即可,目的就是为外部函数的变量内部函数也能访问。
  • 所以要对主程序和函数、外部函数和内部函数参数直接的相互调用弄明白。相当于是global  <=> nonlocal 、  局部变量使用一致、仅仅想调用前者是直接调用,后者是闭包机制调用,使用的表现是一样的。nonlocal本质上是将变量变化为自由变量,最终达到和global一样的目的。、

多个装饰器装饰

  • def h(func):
        def H(j=0):
            func(j=1)
            print('j=', j)
        print('h函数')
        return H
    def s(func):
        def S(j=0):
            func()
            print('j=', j)
        print('s函数')
        return S
    @h
    @s
    def print_msg():
        print('nima')
    print_msg(3)
    
    # result
    s函数
    h函数
    nima
    j= 1
    j= 3

    注意多个装饰器装饰的执行流程:我们说过装饰器执行的时间是装饰函数被定义的时候,这个执行就是让被装饰函数名称指向装饰器的返回值这个函数名,导包也是,导入立即执行,但这个是执行时机,真正的执行需要将程序运行起来在解释器将代码解释到上述的位置式运行,是这个意思,不是代码写了之后就执行,是解释器运行到指定位置立即执行。

  • 那么多个装饰器装饰要注意相当于p = h( s(p)),要注意这个,最后一点要注意的是p()最后调用传入的参数是h装饰器染回函数的参数,不是p函数在代码里面的参数,是p函数被装饰了之后的参数。

参数化装饰器(即装饰器就是函数

  • 装饰器可以有参数,这个参数就是和传入的func一致,都是由于闭包机制使得里面的函数可以使用,这个参数类似于类的属性,目的就是将属性和方法集成到一起,所以装饰器也是将一些属性和被装饰完了的函数集成到一起,体现一个封装性。不仅仅一些自带的函数装饰器可以有参数,自定义的一些装饰器也可以有参数。
  • 注意装饰器函数的参数除了func(所以装饰器的本质就是一个函数,同时还是一个高阶函数)之外,理论上也可以放其它的参数,在func的右侧,但是我们一般不这么将其余所需要的参数这样传,因为如果这样传,下面写 @装饰器  这种简写就会报错,因为简写默认就是  被装饰函数 = 装饰器(被装饰的函数),所以就只能显示的  被装饰函数 = 装饰器(被装饰的函数,需要传入的参数)这样调用,这就烦琐了。当然如果采用缺省值传入也是可以的,不过采用缺省值传入就和直接在装饰器里面定义固定的参数一样了,本质上没有必要。
  • 所以我们给装饰器初始化一些参数都是采用第一:直接先在装饰器内直接写好默认值,下面直调用。第二就是我们常用的一种方法,即创建装饰器工厂函数,返回一个被参数化了的装饰器,在流畅的python173页详细笔记。工厂装饰器传参是可变的,直接在装饰器固定参数或者使用缺省参数是不可变的,非常方便且时用,在流畅的python175页,比直接在装饰器中固定好多了。

闭包机制

  • 上述装饰器只是闭包机制的一种用途,闭包机制还在回调式异步编程、函数是编程方面有应用。

系统内置的和外部导如的装饰器

  • 系统内置的装饰器有3个,property、classmothod、staticmethon。导入的装饰器主要在functools这个包里面,有3个。
  • 一个是functools.lru_catch(maxsize, type=None),这个装饰器详细的作用在流畅的python169页,它的其它作用就不说了,我们这里只探究关于这个装饰器的一个问题,那就是它是按照字典查询来减少重复计算,那么字典里面存储的是什么?key是什么?下面是源码:
  • # one of decorator variants from source:
    def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
        sentinel = object()      # unique object used to signal cache misses
    
        cache = {}                                # RESULTS SAVES HERE
        cache_get = cache.get    # bound method to lookup a key or return None
        # ...
    
        def wrapper(*args, **kwds):
            # Simple caching without ordering or size limit
            nonlocal hits, misses
            key = make_key(args, kwds, typed)     # BUILD A KEY FROM ARGUMENTS
            result = cache_get(key, sentinel)     # TRYING TO GET PREVIOUS CALLS RESULT
            if result is not sentinel:            # ALREADY CALLED WITH PASSED ARGUMENTS
                hits += 1
                return result                     # RETURN SAVED RESULT
                                                  # WITHOUT ACTUALLY CALLING FUNCTION
            result = user_function(*args, **kwds) # FUNCTION CALL - if cache[key] empty
            cache[key] = result                   # SAVE RESULT
            misses += 1
            return result
        # ...
    
        return wrapper

    这里说明一下:装饰器函数的形参是被装饰的函数以及传入的一些共享参数,而真正被装饰函数调用的参数是里面函数的形参,二者调用时机不同,具体时机在流畅的python169页中间那段自述。

  • 由上面的源码可以看到,这个装饰器的key是由  传入被装饰函数的参数以及type构成的,上述调用make__key构成的,这个函数具体不用管,所以只要传入make_key里面的这三个参数相同,那么最后得到的结果就相同,就不用再次计算,直接得到上次的结果,注意这个结果是函数的返回值即return值,不是print值,程序不在次运行了。可以看到result就是真正的函数的value,这就是源码得到的结论。由于make_key构造字典,所以要求传入的该函数的参数必须是可散列的。

  • functools.singledispacth这个装饰器的功能就是类似于函数的重载。对于python里面是否有必要使用函数重载,我认为有必要,但是python本身不支持函数重载(不过他有其它的方法变相重载,这里的不支持重载主要是个其它语言对比,不支持直接重载)。本质上其它语言里面函数重载的目的就是为不同的参数个数不同的参数类型实现相同的功能,只要实现功能相同就可以重载,功能不停直接定义不同的函数即可,就没必要使用函数的重载了。对于不同的数据类型,python是不管数据类型的,好多的数据类型都有相对一样的操作语句,所以不同的数据类型在python中可能执行的代码非常有可能是一致的,从这个角度而言python没必要继承。从不同参数个数的角度而言,python采用缺省值传参或者采用*/**传参,所以有时候考虑也比要重载函数,基于上述两条有人认为python函数没必要重载。函数重载的表现就是调用同一个函数名字,不同的参数进行不同的输出。

  • 但是上述忽略了两个事实:第一函数功能完全相同,但是有时候函数功能大体相同,局部不同,这个时候想使用同名函数按照不同的参数输出不同的结果,这个时候我认为python有必要实现重载。流畅的python在171页提出的对于不使用函数重载进行本部分操作给出不适用if/esle/elif/if。。 if的原因。基于此我们提出了functools.singledispacth这个装饰器实现函数变相的重载。这个重载基本上是将每个函数都写了一回,但是唯一的好处就是能按照不同的参数调用同名不同函数,这个是重载的表现形式(在重载的代码量上没有缩短,在使用上还是按照重载使用的)。

  • 作者:WSL-WLL
  • 原文链接:https://blog.csdn.net/weixin_40500427/article/details/103211303
    更新时间:2022-08-20 12:19:53