zoukankan      html  css  js  c++  java
  • Python自学之路——自定义简单装饰器

      看了微信公众号推送的一道面试题,发现了闭包的问题,学习时间短,从来没有遇到过这种问题,研究一下。

    Python函数作用域

      global:全局作用域

      local:函数内部作用域

      enclosing:函数与嵌套函数之间的作用域

      build-in:内置作用域

     1 #global
     2 age = 60#全局变量
     3 
     4 def func():
     5     age = 40
     6     #对于func来说是函数内部变量,对于in_func来说是enclosing变量
     7     print('func-->',age)
     8     age = age-40
     9     print("func-->",age)
    10     print('%x' % id(age))
    11     def in_func():
    12         print('in_func-->',age)
    13     return in_func
    14 f = func()
    15 f()
    16 print(f.__closure__)
    17 age = age-40#全局变量
    18 print('global-->',age)
    1 func--> 40
    2 func--> 0
    3 65d0c690
    4 in_func--> 0
    5 (<cell at 0x000001C4545F0C78: int object at 0x0000000065D0C690>,)
    6 global--> 20 

      对于enclosing作用域,f = func() 就相当于f = in_func() 然后in_func()会运行,但是我之前的func函数已经运行完了按道理系统会回收func函数所定义的多有东西,为什么还会打印输出呢。

      因为in_func函数被返回,不会被回收,引用计数不为零。在infunc函数输出时会查找本地,然后嵌套,然后全局。当查找到嵌套的变量是发现了age。

    由于发现了enclosing变量,infunc函数就会把这个变量放到自己的属性中,代码运行结果的输出可以证明,infunc函数的闭包变量的地址和func函数的age属性的地址一模一样。

    这个就可以称为闭包。也就是内部函数中对enclosing作用域的变量进行引用。

      那么Python怎么创建闭包:

      1、函数必须有内嵌函数

      2、函数必须返回内嵌函数

      3、内嵌函数必须引用enclosing作用域 的变量

      那么接下来的装饰器就是闭包的一个典型的实现。。

      装饰器的作用就装饰一个函数,通俗的讲就是对已有的函数进行功能上的拓展,但是不能改变被装饰函数的源代码,不能改变被装饰函数的调用方式。

    因为可能你的代码已经被放到线上运行,或者你的函数已经被别的部门调用......种种原因吧

      先看看简单例子:

    1 def foo():
    2     print("in the foo")
    3 
    4 
    5 
    6 foo()
    deco_1

      举例很简单就是运行了一个函数,输出为“in the foo”

      假设我们现在有了一个需求,我们需要知道这个函数运行了多长时间,怎么办?

      很简单嘛!加一个函数 timer ,将 foo 函数作为参数传进去就好了啊。

      (为了体现运行效果,加入了睡眠3秒,所有这个不能算改变源代码哈)

     1 import  time
     2 
     3 
     4 def foo():
     5     time.sleep(3)
     6     print("in the foo")
     7 def timer(func):
     8     start_time = time.time()
     9     func()
    10     stop_time = time.time()
    11     print ("run time :%s"%(stop_time-start_time))
    12 
    13 #foo()
    14 timer(foo)
    方法一

      但是这个方法也不好啊,因为你改变了函数的调用方式啊

      原来是   foo()   ,现在是  timer()  这个就有问题了。

      于是我们就模仿上边的方法,写了嵌套函数,然后最终调用还是foo()

      如下啦:

     1 import  time
     2 
     3 def timer(func):
     4     def wrapper():
     5         start_time = time.time()
     6         func()
     7         stop_time = time.time()
     8         print ("run time : %s"%(stop_time-start_time))
     9     return wrapper
    10 def foo():
    11     time.sleep(3)
    12     print("in the foo")
    13 
    14 
    15 
    16 foo = timer(foo)
    17 foo()

      这段代码就是一个简单的没有参数的装饰器了。。。

      逐行解读一下,当我们运行  foo = timer(foo) 时 timer函数先运行def wrapper 把wrapper函数刷入内存,但是并不会执行,然后return   wrapper函数名给foo,那么此时函数名wrapper就和函数名foo指向同一个内存地址了。这句话就结束了。

      下边一句foo()就相当于wrapper()。然后运行wrapper函数。此时那个func参数,就是内存地址为之前的foo的那个地址,所以这句话就是wrapper函数运行调用了以前的foo函数。。。。

      有点绕,当时学的时候可以说的一脸XX啊。在pycharm中加断点调试会看的更清晰。

      但是Python觉得这么写代码还是多,于是搞出了一个叫  语法糖  的东西。。就是下边这个样子

     1 import  time
     2 
     3 def timer(func):
     4     def wrapper():
     5         start_time = time.time()
     6         func()
     7         stop_time = time.time()
     8         print ("run time : %s"%(stop_time-start_time))
     9     return wrapper
    10 
    11 
    12 @timer#语法糖 其实就是相当于在调用foo函数前加了foo = timer(foo)这个代码
    13 def foo():
    14     time.sleep(3)
    15     print("in the foo")
    16 
    17 
    18 
    19 #foo = timer(foo)
    20 foo()

      顿时高大上了不少哈

      简单装饰器就写完了,但是这个装饰器基本上没有什么卵用......

      函数运行正常都有参数吧。。。

      所以上边的都是铺垫,接下来进入正戏了:

      模拟一个用户登录验证的小程序

     1 def verify(func):
     2     def wrapper(username,passwd):
     3         if username == "sunqi" and passwd == "123456":
     4             print ('verify successful')
     5             func(username,passwd)
     6         else:
     7             print ('verify faild...')
     8             exit()
     9     return wrapper
    10 
    11 @verify
    12 def login(username,passwd):
    13     print ("welcome!")
    14 
    15 if __name__ == '__main__':
    16 
    17     user = input("username >>:")
    18     password = input('password >>:')
    19     login(user,password)

     后续还有带参数的装饰器

    还有那个没懂得面试题……

      

  • 相关阅读:
    P1440 求m区间内的最小值
    P1569 Generic Cow Protests
    P3252 [JLOI2012]树
    P3009 [USACO11JAN]Profits S
    <二分查找+双指针+前缀和>解决子数组和排序后的区间和
    常见算法技巧之——双指针思想
    设计模式——单例模式
    操作系统实验——读者写者模型(写优先)
    操作系统——内存管理学习笔记
    Altium Designer 16下载与安装教程
  • 原文地址:https://www.cnblogs.com/SunQi-Tony/p/8591079.html
Copyright © 2011-2022 走看看