小 tips

写递归函数的章节提到过,当函数不断调用自身,直到被pycharm发现抛出异常。实际上是因为栈溢出。
什么是栈溢出呢?
python中只要调用一个函数,函数中自己调用自己,每次调用都会放到内存中反复递归,但是当原函数没结束时(就是说没有设置递归结束标示),递归就不会结束,直到内存被用完,栈溢出。
另外递归的性能是特别不好的,所以一般是不使用递归函数去编写功能。

闭包函数

在进入装饰器之前,先讲解闭包函数的概念。
函数中调用函数本身,那么在函数中可不可以定义一个函数呢?
需求,如何函数外部调用内部定义的函数
问题引入,什么是闭包?
闭包的概念:

  1. 函数中嵌套一个函数
  2. 外层函数返回内层函数的变量名
  3. 内层函数对外部作用域有一个非全局变量的引用

来一个最简单的闭包案例

  1. def login():
  2. print("登录"
  3. num = 100 # 这是个全局变量
  4. def func():
  5. print("-----func被调用")
  6. num =101 # 非全局变量,因为命名空间在外层函数中(相较于内层被嵌套函数而言)
  7. def count_book():
  8. print(num) # 内部函数对外部作用域有一个非全局的引用
  9. print("函数内部嵌套的函数")
  10. return count_book # 外层函数返回的是内层函数的变量名
  11. # 进行调用
  12. res = func() # 通过return返回函数内部的函数,然后使用
  13. res()
  14. # 或者
  15. func()() # 这样的方式也可以访问到内部函数

函数里面定义的函数,相较于外部而言是一个局部变量,外部是无法访问函数内部的函数的。

闭包函数的作用

闭包函数会将传入的局部变量存储在closure中,记录的是非全局变量,且从外部函数的clourse是访问不到的。

  1. #闭包有什么作用?
  2. def func(num,b):
  3. def count_book():
  4. print(num)
  5. print(b)
  6. print("这个是闭包函数")
  7. return count_book
  8. res = func([1,2,3],"python")
  9. print(res.__closure__) # 闭包函数会将传入的局部变量存储在__closure__中
  10. # 记录的是非全局变量,且从外部函数的__closure__是访问不到的
  11. print(func.__closure__)
  12. # 达成数据锁定 从而不管外部环境发生任何变化,内部的变量都不会发生变化

装饰器

接下来开始今天的硬菜,装饰器的学习。

开放封闭原则

所谓开放封闭原则,是一种程序设计规范,软件实体应该是可扩展,而不可修改的,也就是说,对扩展是开放的,而对内部原码的修改是封闭的。

装饰器的作用

装饰器则是这一理念的实现,即在不修改原方法的前提下进行功能的扩展

装饰器的应用场景

登录验证,函数运行时间统计,执行函数之前进行的操作,执行函数之后做的清理操作(有点类似unittest,pytest的前置后置方法)
来看一个实例

  1. # 实现一个装饰器
  2. username ="python"
  3. pwd = "123"
  4. def login(func): # 将闭包函数改造成装饰器,需要往函数内传入一个函数 装饰那个函数就传入那个函数
  5. def fun():
  6. user = input("请输入账号")
  7. password = input("请输入密码")
  8. if username == user and pwd==password:
  9. func() # 账号和密码都正确的时候才调用被装饰的函数
  10. else:
  11. print("账号或者密码错误")
  12. return fun
  13. # 装饰器中的形参func 就相当于被装饰的函数对象,
  14. # 希望给被装饰的函数添加什么功能(或者什么时候想要调用被装饰函数,
  15. @login
  16. def index():
  17. print("这个是网站的首页")
  18. # index() # 加入需求是登入之前先进行验证账号,要求不能修改登录原函数,添加一个验证
  19. # closure__ = index.__closure__
  20. # 装饰器其实就是一个闭包

调用原理:
@login 添加在index()方法上—->login(index) 即将方法作为一个参数传入了login方法中
然后进入login函数中的内部函数执行,首先进行验证,验证通过则调用index函数,即func形参代表的传入的index方法。
不通过则输出对应字符串,最后将内部函数返回,以供调用者使用

有参装饰器

需求:打印两个数相加,然后打印相乘,相除的结果,要求不能修改原方法代码

  1. def add_num(a,b): # 原函数
  2. print('相加:',a+b)
  3. def add(func):
  4. def fun(a, b): # 传参先进入这里
  5. print('相乘',a*b)
  6. print('相除',a/b)
  7. func(a,b) # 此处为被修饰的原函数的调用 如果忘记此处,原函数将不会被运行
  8. return fun
  9. @add
  10. add_num(1,2)

调用原理:
@add 加在add_num头上等价于——>add(add_num(1,2))—->进入add装饰器方法中的内部函数
然后执行内部函数中的逻辑,通过形参func去调用传进来的实参add_num去执行原函数。
但是原函数有参,所以func(a,b)传入a,b形参最后返回内部函数给调用者,完成整个调用。
clipboard.png

再进阶,可传参和不可传参我全都要

clipboard1.png

再再进阶,装饰一个类

clipboard2.png

编写类装饰器

  • 本文前面的装饰器都是借助闭包的特性来实现的装饰器,最近在补基础的同时学习到一种编写类装饰器的方式。

    导入

    ```python

    首先编写一个类

    class Deco: pass

@Deco def target(): print(“被装饰的目标函数”)

target()

  1. - 猜猜上面的代码运行出来会是什么报错信息
  2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1608527/1652710754600-00261e84-a135-454d-b6ed-adc016a5801f.png#clientId=ufd074008-d476-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=321&id=u3c49d53d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=321&originWidth=987&originalType=binary&ratio=1&rotation=0&showTitle=false&size=25613&status=done&style=none&taskId=u9b7ef572-36bd-47b3-8f98-5da5149820a&title=&width=987)
  3. - 报错信息解析,为什么是 no arguments 呢?
  4. - 说回装饰器原理 ,当写下 @Deco 这个注解的时候,程序层面等价于 Deco(target) 即是,你向Deco这个类中传了一个函数进去。而Deco那个类下不需要任何参数。
  5. - 那么接下来,进行一些修改
  6. <a name="QtZOh"></a>
  7. ## 2 given报错
  8. ```python
  9. class Deco:
  10. # def __init__(self,func):
  11. def __init__(self):
  12. print("deco 类的 init方法得到了执行")
  13. # self.func = func
  14. @Deco
  15. def target():
  16. print("准备装饰函数的方法")
  17. target()
  • 这次报错信息不一样了

image.png

  • 为什么2 given? Deco(target) 这个过程调用了类的init构造方法,而构造方法 只定义了一个形参,为实例本身,即 self,没有预留函数的 形参位置。 所以是2 given
  • 再次进行修改

    object not callable报错

    ```python

class Deco: def init(self,func): print(“deco 类的 init方法得到了执行”) self.func = func

@Deco def target(): print(“准备装饰函数的方法”)

target()


- 这次报错是什么呢?

![image.png](https://cdn.nlark.com/yuque/0/2022/png/1608527/1652711062436-876de729-ca14-4443-931e-36b6758ba881.png#clientId=ufd074008-d476-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=330&id=u25338563&margin=%5Bobject%20Object%5D&name=image.png&originHeight=330&originWidth=1081&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30850&status=done&style=none&taskId=u6d5aedc5-3a8f-4eb0-9cec-cf252c0bf09&title=&width=1081)

- 但是已经可以看到, deco类的init方法得到了执行,但是 提示 Deco 对象不可调用,
- 那么思考一下,为什么是不可调用? 答案当然是很简单,因为没有实现call方法,但是为什么呢?
- #   原因为   @Deco 等价于  Deco(target)   将参数传入装饰器类,生成一个对象,对象无法进行函数式调用,如果需要调用,则要在对象对应的实体类下实现call方法
- 再次进行修改之后,便可以了
```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2022/5/16 21:51
# @Author  : Addicated
# @File    : class_deco.py
# @Software: PyCharm



# 使用一个类来作为装饰器装饰某个函数


class Deco:
    def __init__(self,func):
    # def __init__(self):
        print("deco 类的 init方法得到了执行")
        self.func = func

    def __call__(self, *args, **kwargs):
        print("在这里可以进行任意增强函数的编写,,,,亦或者是调用类内的其他实例方法")
        self.improve()
        self.static_method()   # 静态方法也可
        self.func()  # 传进来的是一个函数,所以可以直接进行调用

    def improve(self):
        print("这是一个增强方法,,,,")

    @staticmethod
    def static_method():
        print("类内静态方法~~~~")

# @Deco 等价于。  Deco(target)   TypeError: __init__() takes 1 positional argument but 2 were given
#  报错原因为,目标函数作为一个参数传到了 装饰器类中,而装饰器类方法的init中没有接收这个函数的形参  def __init__(self): 修改为 def __init__(self,func)
#   再次修改之后报错原因为  TypeError: 'Deco' object is not callable 。 类没有实现call方法
#   原因为   @Deco 等价于  Deco(target)   将参数传入装饰器类,生成一个对象,对象无法进行函数式调用,如果需要调用,则要在对象对应的实体类下实现call方法
@Deco
def target():
    print("准备装饰函数的方法")

target()
  • 整个探究的过程是十分的有意思,故此记录,与君共勉。