Python之装饰器的使用


闭包

对于函数内声明的变量一般都为临时变量,其生命周期随函数结束而结束。

但闭包是个例外

闭包满足的条件:
1、必须要有内嵌函数
2、内函数必须引用外函数的变量
3、外函数必须返回的内函数

def outer(x):
    a = 300
    def inner():
        print(x+a)
    return inner	# 这里不能打括号,不然就是返回调用了
d = outer(100)
d()		# 输出400

对于闭包函数对象,其内置属性__closure__非空

print(d.__closure__)	# 会记录其引用了的变量
(<cell at 0x00000256060C2FD0: int object at 0x0000025604DB7F70>, <cell at 0x00000256060C2FA0: int object at 0x00000256757955D0>)

装饰器

装饰器本质就是闭包函数,需要把一个callable(函数、类)对象作为参数传入进去,所以装饰器只能用在函数或类上面

装饰器是一种设计模式,主要用于在不改变函数或者类的源代码的基础上,给其添加一些额外的功能

装饰器并非python独有,只要能实现闭包的语言都能实现装饰器

函数实现

# 装饰器模板,在此基础上添加新功能
def runtime(func):
    def inner(*args,**kwargs):		# 这样可以接收任意个参数
        result = func(*args,**kwargs)
        return result
    return inner
# 装饰器,用于统计函数执行时间
def runtime(func):
    def inner(*args,**kwargs):
        start = time.time()
        result = func(*args,**kwargs)
        end = time.time()
        print(f"执行{func.__name__}花费{end-start}s")
        return result
    return inner

@runtime		# 给函数应用装饰器,去掉该装饰语句后就不会执行装饰器的效果;	func1 = runtime(func1)
def func1():
    time.sleep(2)
    print("func1")

func1()			# 调用的时候没有任何区别,无需改变源代码,然实际调用的是inner()函数
#---输出---
func1
执行func1花费2.012355089187622s

原理

当代码执行到@runtime时,runtime函数就已经被执行(func1函数也被传入到runtime函数中)

此时调用func1函数时,实际调用为inner()函数,证据是print(func1.__name__)为inner

类实现(带参数)

函数实现想带参数也只需在定义时声明和装饰时传入就好

class A:
    def __init__(self, username):	# 通过init函数传参
        self.username=username
	def __call__(self, func):		# 通过call函数进行实现装饰器
	    def inner(*args,**kwargs):
    	    result = func(*args,**kwargs)
        	return result
	    return inner

@A(username="root")		# 通过类进行装饰和传参
def func1():
    pass

装饰类

def runtime(cls):
    def inner(*args,**kwargs):		# 装饰器函数返回的就是类了,而不是返回值
        return cls(*args,**kwargs)
    return inner

@runtime
class A:
    pass

a1 = A()	# 与装饰函数一样,此时A调用的cls这个内部函数

应用

  • 引入日志,用于1.排错、分析和定位故障;2.用户行为分析;3.页面埋点(相当于数据采集)
  • 增加计时逻辑来检测性能
  • 给函数加入事务的能力
  • 权限控制
  • ……
权限控制
# 权限控制
username = input("输入你的用户名:")

def login_require(func):
    def inner(*args,**kwargs):
        if username != "root":		# 当用户为root时才能执行add函数
            print("权限拒绝!")
            return
        result = func(*args,**kwargs)
        return result
    return inner

@login_require
def add(x,y):
    print(x+y)

add(1,2)
计时和添加日志
# 对add函数添加两个装饰器,一个记录花费时间,一个添加日志
import time
def run_time(func):
    def inner(*args,**kwargs):
        start_time=time.time()
        result = func(*args,**kwargs)
        end_time = time.time()
        print(f"{func.__name__}程序运行共花费{end_time-start_time}s")
        return result
    return inner

import logging
def log_add(func):
    def inner(*args,**kwargs):
        LOG_FORMAT = "%(asctime)s - %(filename)s[%(levelname)s] : %(message)s"
        logging.basicConfig(format=LOG_FORMAT)
        logging.warning(f"-->{func.__name__}<--程序被执行!")
        result = func(*args,**kwargs)
        return result
    return inner

@run_time
@log_add
def add(x,y):
    return x+y

result = add(1,2)
print(result)

# 输出效果
inner程序运行共花费0.0009965896606445312s
2023-08-25 11:43:02,963 - 装饰器练习.py[WARNING] : -->add<--函数被执行!
3
系统识别
# 写一个局域网扫描函数,--subprocess,再写一个装饰器,判断当前系统是否为linux,是才能执行

# 实现ping函数,ping成功返回true,否则为false
import platform    # For getting the operating system name
import subprocess  # For executing a shell command

def require_linux(func):
    def inner(*args,**kwargs):
        if platform.system().lower() == 'linux':
            return func(*args,**kwargs)
        else:
            print(f"操作系统不是linux,拒绝执行{func.__name__}!")
            return
    return inner

def ping(host):
    """
    Returns True if host (str) responds to a ping request.
    Remember that a host may not respond to a ping (ICMP) request even if the host name is valid.
    """

    # Option for the number of packets as a function of
    param = '-n' if platform.system().lower()=='windows' else '-c'

    # Building the command. Ex: "ping -c 1 google.com"
    command = ['ping', param, '1', host]

    return subprocess.call(command) == 0

@require_linux
def scan_lan(net_segment):
    list = net_segment.split('.')
    seg = '.'.join(list[0:3])
    for i in range(1,255):
        host = seg + "." + str(i)
        if ping(host):
            with open("actived.txt",'a') as fp:		# 将能到达的服务器ip写入文件
                fp.write(f"{host}\n")
        else:
            with open("unreachable.txt",'a') as fp:
                fp.write(f"{host}\n")
    return

scan_lan(net_segmeng)

# --------效果--------
# Windows:
C:\Users\zhihe>python3 D:\pycharm2020.3\My_Project\装饰器.py
操作系统不是linux,拒绝执行scan_lan!
# Linux:
[root@zh-ali py练习]# tail unreachable.txt
172.26.252.1
172.26.252.2
172.26.252.3
……
redis_require

写一个装饰器,对于要查询的同学信息,在给定同学id后先到redis上查找,查找不到再去mysql查找,并将查找到的值写入到redis。

import redis
import pymysql

'''
对于要查询的同学信息,在给定同学id后先到redis上查找,查找不到再去mysql查找,并将查找到的值写入到redis。
'''

def redis_req(func):
    def inner(id):
        r = redis.Redis(host='192.168.10.21', port=6379, db=1, decode_responses=True)
        if r.hkeys(id):
            print("通过redis查询:")
            print(f"同学姓名:{r.hget(id, 'name')}")
            print(f"同学年龄:{r.hget(id, 'age')}")
            print(f"同学成绩:{r.hget(id, 'grade')}")
            data = (int(id),r.hget(id, 'name'),int(r.hget(id, 'age')),int(r.hget(id, 'grade')))	# 保证data格式与mysql返回格式一致
        else:
            data = func(id)
            print("通过mysql查询:")
            if data:				# data不为空时才输出并写入到redis
                print(data)
                r.hset(id, "name", data[1])
                r.hset(id, "age", data[2])
                r.hset(id, "grade", data[3])
            else:
                print("该id不存在!")
        r.close()
        return data
    return inner

@redis_req
def mysql(id):
    db = pymysql.connect(host='192.168.10.21',
                         # port=3306,
                         user='zh',
                         password='123456',
                         database='zh')
    cursor = db.cursor()
    cursor.execute(f"select * from stu where id={id}")
    data = cursor.fetchone()
    db.close()
    return data

id = input("输入你要查询的同学id:")
data = mysql(id)
print(data)

小结

本节文章讲解了什么是闭包、装饰器的函数和类实现以及几个实用的装饰器例子,希望能帮助大家快速理解并上手,有什么问题欢迎留言,我会及时回复,感谢观看!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只何

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值