Python学习之装饰器与类的装饰器详解
作者:渴望力量的哈士奇 发布时间:2023-11-23 20:04:07
通过学习装饰器可以让我们更好更灵活的使用函数,通过学会使用装饰器还可以让我们的代码更加优雅。
在我们的实际工作中,很多场景都会用到装饰器,比如记录一些日志、或者屏蔽一些不太合法的程序执行从而使我们的代码更加安全。
装饰器
什么是装饰器?虽然对这个次感到陌生,但是完全不需要担心。
首先,装饰器也是一种函数;只不过装饰器可以接收 函数 作为参数来传递。
并且可以通过 return 可以返回一个函数,装饰器通过接收一个函数,对它在装饰器内部进行处理、调用,并返回一个新的函数,同时还可以动态增强传入函数的功能。
装饰器整个流程是这样的:
A函数是装饰器,B函数是A函数传入的参数。
将B函数在A函数中执行,在A函数中可以选择执行或不执行,也可以对B函数的结果进行二次加工处理。
接下来我们看看 装饰器长什么样子
def a():
def b():
print(helloworld)
b()
a()
b()
a() 函数中书写了一个 b() 函数,并在 a() 函数中调用 b() 函数。是不是非常类似在类中定义一个局部函数并调用的例子?其实装饰器就是有些类似这样的操作,只不过被装饰器调用的函数是通过 参数 的形式传进去,并在 b() 函数中执行。
我们在定义完 a() 函数之后进行调用,可以正常处理。但是 b() 函数 是 a() 函数的局部函数 如果在外部调用会报错。(如上文中的第十行,就会报错)
装饰器的定义
示例如下:
def out(func_args): # 装饰器的第一层函数被称为 外围函数 , 'func_args' 为要处理的函数
def inter(*args, **kwargs): # 外围函数 的函数体内定义的函数被称为 内嵌函数 ;传入的参数为要处理的 func_args 函数的参数
# 这里我们并不知道 func_args 函数需要传入进来的参数是什么,所以目前写传入可变参数是比较合理的
return func_args(*args, **kwargs) # 在 内嵌函数 的函数体内调用 func_args 函数,并将可变参数传入
# 其实这里我们可以处理更多的逻辑;
# 我们可以选择执行或者不执行,甚至可以func_args 函数的执行结果进行二次处理
return inter # 书写完 内嵌函数的业务之后,我们在 外围函数体内返回 内嵌函数
# 需要注意的是,这里是不执行的(因为没有加括号),这是装饰器的定义规则,是必不可少的
# 只有外围函数返回内嵌函数,才可以被之后的代码执行;(因为所有的业务都在内嵌函数中,不返回就无法执行调用)
装饰器的用法
在我们日常工作中,装饰器的使用方法有两种。
第一种:将被调用的函数直接作用为参数传入装饰器的外围函数括弧内;示例如下:
def a(func):
def b(*args, **kwargs):
return func(*args, **kwargs)
return b
def c(name):
print(name)
a(c)('Neo')
# >>> 执行结果如下:
# >>> Neo
第二种:将装饰器与被调用函数绑定在一起, @ 符号 + 装饰器函数放在被调用函数的上一行,被调用的函数正常定义,只需要直接调用被执行函数即可。示例如下:
def a(func):
def b(*args, **kwargs):
return func(*args, **kwargs)
return b
@a
def c(name):
print(name)
c('Neo')
# >>> 执行结果如下:
# >>> Neo
最常用的装饰器用法为第二种。
现在我们构建一个 检查字符串类型的装饰器,加深一下对装饰器的理解。
def check_ok(func):
def inner(*args, **kwargs):
result = func(*args, **kwargs)
if result == 'OK':
return '传入的参数数据为:\'%s\'' % result
else:
return '传入的参数数据不为:\'OK\''
return inner
@check_ok
def test_str(data):
return data
result = test_str('OK')
print(result)
# >>> 执行结果如下:
# >>> 传入的参数数据为:'OK'
result = test_str('NO')
print(result)
# >>> 执行结果如下:
# >>> 传入的参数数据不为:'OK'
类中的装饰器
类的装饰器 - classmethod
classmethod 的功能:可以将类函数不经过实例化即可直接被调用
classmethod 的用法:示例如下
@classmethod
def func(cls, ...):
todo
# >>> cls 替代普通类函数中的 self ;
# >>> 变更为 cls ,表示当前的类
# >>> self 代表的是 实例化对象,所以只有通过实例化后,才可以调用
# >>> cls 代表的是 类 ,所以即使不通过实例化,也可以调用
# *****************************************************
class Test(object):
@classmethod
def add(cls, a, b):
return a + b
print(Test.add(1, 3))
# >>> 执行结果如下:
# >>> 4
演示案例:
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
@classmethod
def work(cls):
print('会抓老鼠')
dragonLi = Cat('狸花猫')
print(dragonLi.eat(), dragonLi.work())
# >>> 执行结果如下:
# >>> 狸花猫 喜欢吃鱼
# >>> 会抓老鼠
接下来我们不使用 类的实例化 ,直接使用 类 调用 eat() 函数 与 work() 函数
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
@classmethod
def work(cls):
print('会抓老鼠')
dragonLi = Cat('狸花猫')
Cat.eat()
# >>> 执行结果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 报错缺少重要参数 'self' (没有进行实例化的类,类无法直接调用类函数)
Cat.work()
# >>> 执行结果如下:
# >>> 会抓老鼠
# >>> 绑定了 classmethod 装饰器 的 work() 函数,即使没有实例化,也可以直接被 类 调用
再尝试一下看看 没有装饰器的 eat() 函数 与 使用了 classmethod 装饰器 work() 之间可不可以互相调用
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
@classmethod
def work(cls):
print('会抓老鼠')
cls.eat()# 在 classmethod 装饰器的 work() 函数内 调用 eat() 函数
dragonLi = Cat('狸花猫')
dragonLi.work()
# >>> 执行结果如下:
# >>> TypeError: Cat.eat() missing 1 required positional argument: 'self'
# >>> 同样报错缺少重要参数 'self'
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
self.work()
@classmethod
def work(cls):
print('会抓老鼠')
dragonLi01 = Cat('狸花猫')
dragonLi01.eat()
# >>> 执行结果如下:
# >>> 执行结果如下:
# >>> 狸花猫 喜欢吃鱼
# >>> 会抓老鼠
综合以上两个场景,我们得出以下结论:
在带有 classmethod 装饰器 的 函数 内,是无法调用普通的 带有 self 的函数的
但是在普通的带有 self 的类函数内,是可以调用带有 classmethod 装饰器 的 函数的
类的装饰器 - staticmethod
staticmethod 的功能:可以将 类函数 不经过实例化而直接被调用,被该装饰器调用的函数不需要传入 self 、cls 参数,并且无法在该函数内调用其他 类函数 或 类变量
staticmethod 的用法:参考如下
@staticmethod
def func(...):
todo
# >>> 函数内无需传入 cls 或 self 参数
# *****************************************************
class Test(object):
@staticmethod
def add(a, b):
return a + b
print(Test.add(1, 3))
# >>> 执行结果如下:
# >>> 4
接下来我们在上文的 Cat() 类基础演示一下 staticmethod 装饰器 (新建一个 color() 函数,使用 staticmethod 装饰器 )
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
self.work()
@classmethod
def work(cls):
print('会抓老鼠')
@staticmethod
def color():
print('黄棕色')
dragonLi = Cat('狸花猫')
print(dragonLi.eat(), dragonLi.color())
# >>> 执行结果如下:
# >>> 狸花猫 喜欢吃鱼
# >>> 会抓老鼠
# >>> 黄棕色
# >>> 从执行结果来看, staticmethod 装饰器的 color() 函数可以被实例化后的对象 dragonLi 调用。
# >>> 那么可以被 Cat() 类 直接调用么?我们往下看
print(Cat.color())
# >>> 执行结果如下:
# >>> 黄棕色
# >>> 可以看到,staticmethod 装饰器构造的 color() 函数,即使没有被实例化,依然可以直接被 类 调用
同样的,也尝试一下 staticmethod 装饰器构造的 color() 函数 是否能够在类函数中互相调用。
class Cat(object):
def __init__(self, name):
self.name = name
def eat(self):
print(self.name, '喜欢吃鱼')
self.work()
self.color()
@classmethod
def work(cls):
print('会抓老鼠')
@staticmethod
def color():
print('黄棕色')
dragonLi = Cat('狸花猫')
dragonLi.eat()
# >>> 执行结果如下:
# >>> 狸花猫 喜欢吃鱼
# >>> 会抓老鼠
# >>> 黄棕色
# >>> 结合执行结果得出结论:staticmethod 装饰器构造的 color() 函数 可以在 eat() 类函数中被调用
与带有 classmethod 装饰器 的 函数 一样,staticmethod 装饰器构造的 函数也是无法调用普通的 带有 self 的函数的,这里就不再书写演示代码进行演示了。(staticmethod 装饰器构造的 函数也是无法调用普通的 带有 self 的函数会报错 : NameError: name 'self' is not defined )
类的装饰器 - property
property 的功能:可以将类函数的执行免去小括号,类似于直接调用类的变量(属性)
staticmethod 的用法:参考如下
@property
def func(self):
todo
# >>> 不能传入参数,无重要函数说明
# *************************示例如下*************************
class Test(object):
def __init__(self, name):
self.name = name
@property
def call_name(self):
return 'Hello {}'.format(self.name)
test = Test('Neo')
result = test.call_name# 不需要添加 小括号 即可调用 call_name 函数;
# 关于 staticmethod 不可传入参数,其实并不是不可以传入参数,而是传入的方法比较另类,我们继续往下看
print(result)
# >>> 执行结果如下:
# >>> Hello Neo
重新创建一个 Dog 类 ,然后我们继续演示。
class Dog(object):
def __init__(self, name):
self.__name = name
@property
def type(self):
if self.__name in ['哈士奇', '萨摩耶', '阿拉斯基']:
return self.__name, '是雪橇犬,\'雪橇三傻\'之一'
elif self.__name in ['吉娃娃', '博美犬', '约克夏']:
return self.__name, '是小型犬'
else:
return self.__name, '我暂时不知道这是什么犬种,也许它是\'泰日天\'的亲戚'
dog = Dog(name='哈士奇')
print(dog.type)# 这里我们并不需要 dog.type + () 小括号,即可调用 type() 函数
# >>> 执行结果如下:
# >>> ('哈士奇', "是雪橇犬,'雪橇三傻'之一")
# >>> 这里我们看到 当 Dog 类 实例化 dog 变量之后,我们传入的 '哈士奇' 参数是不可更改的,如果我们尝试利用赋值的方式修改传入的参数呢?
dog = Dog(name='哈士奇')
dog.type = '约克夏'
print(dog.type)
# >>> 执行结果如下:
# >>> AttributeError: can't set attribute
# >>> 报错:属性错误,不可以设置这个属性
# >>> 其实,property 装饰器绑定的函数的参数并不是不可以更改,只是更改的方式比较特殊,并不是不能通过赋值的形式传入参数,我们继续往下看。
首先,我们已经使用了 @property 绑定了我们的 type 函数,这是一个返回值的方法。 所以我们要如何给 type() 函数赋值呢?其实很简单,我们可以通过 @type 对应上 type() 函数,在它的函数内部有一个函数 setter ;然后再定义一个 type 函数,在这个新定义的 type() 函数内定义一个值 value (可以是任意的名字,但这里需要注意,只能定义一个值)。然后再通过设置一个 self.__name = value ,如此就可以达到修改传入参数的目的。废话不多说了,看下方的示例:
class Dog(object):
def __init__(self, name):
self.__name = name
@property
def type(self):
if self.__name in ['哈士奇', '萨摩耶', '阿拉斯基']:
return self.__name, '是雪橇犬,\'雪橇三傻\'之一'
elif self.__name in ['吉娃娃', '博美犬', '约克夏']:
return self.__name, '是小型犬'
else:
return self.__name, '我暂时不知道这是什么犬种,也许它是\'泰日天\'的亲戚'
@type.setter
def type(self, value):
self.__name = value
dog = Dog(name='哈士奇')
dog.type = '约克夏'
print(dog.type)
# >>> 执行结果如下:
# >>> ('约克夏', '是小型犬')
附:使用最广泛的装饰器为 classmethod
来源:https://blog.csdn.net/weixin_42250835/article/details/123412325
猜你喜欢
- 目前很多软件都限制单实例,大多数软件都是用Mutex来实现的 而这个东西咱们可以用handle去干掉它,并且不影响使用。 钉钉也是一样的步骤
- 详情查看下面的代码:如果被识别就要添加一个cookie如果没有被识别的话就要一个user—agent就好了。如果出现乱码就设置编码格式为ut
- 什么是pyc文件pyc是一种二进制文件,是由py文件经过编译后,生成的文件,是一种byte code,py文件变成pyc文件后,加载的速度有
- 前言这两天帮一个朋友处理了些 nc 数据,本以为很简单的事情,没想到里面涉及到了很多的细节和坑,无论是“知难行易”还是“知易行难”都不能充分
- 一、自定义MyComboBox# MyComboBox.pyfrom PyQt5.QtWidgets import QComboBoxfro
- 平时工作过程中,git在push代码的时候有时会遇到如下的错误错误原因文件冲突,本地的代码和远程Repository中的文件个数不一致(即远
- 今天想说的是内容和容器的关系,顺便把之前设计中碰到的问题和大家一起探讨下。我们从软件的设置说起。(这里以QQ的设置举例)一个软件的设置(常称
- 背景介绍Expect 程序主要用于人机对话的模拟,就是那种系统提问,人来回答 yes/no ,或者账号登录输入用户名和密码等等的情况。因为这
- 1.什么是局部视图局部视图是在其他视图中呈现的视图。通过执行局部视图生成的HTML输出呈现在调用视图中。与视图一样,局部视图使用 .csht
- 公司里很多部门,每个部门可以发多条信息,但每条信息只对应一个部门部门类:class Dep(models.Model): nam
- 导语梦想还是要有的,万一实现了呢?!今天小编就来用代码实现自己专属的城市——特大都市:梦想小镇启航。
- 本文实例为大家分享了python统计序列中元素的具体代码,供大家参考,具体内容如下问题1: &
- 本节讲解了 flask 的请求,如果想在没有请求的情况下获取上下文,可以使用test_request_context()或者request_
- mysql 下载安装配置 5.7.20 / 5.7.21,供大家参考,具体内容如下1、下载mysql,下载地址选择操作系统和版本,我是64位
- 今天写了点东西,要计算时间差,我记得去年写过,于是今天再次mark一下,以免自己忘记In [27]: from datetime impor
- 先上代码,主要语句为np.where(b[c]==1),详细解释如下:import numpy as np b = np.array([[-
- 我国移动互联网进入了飞速发展阶段,互联网人才日益受到企业的重视,其中PHP开发人才便是其中之一,在互联网旅游、金融、餐饮、娱乐、社交等一些新
- 这篇文章主要介绍了如何基于Python实现数字类型转换,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 问题:将文件夹a下任意命名的10个文件修改为如下图所示文件?代码:#coding:utf-8import ospath = "./
- python tkinter按钮Button的使用创建和设置窗口from tkinter import *#创建窗口对象root = Tk(