我们已经准备好了,你呢?

2024我们与您携手共赢,为您的企业形象保驾护航!

修饰符也是 中很重要的内容,它可以在不做任何代码改动的情况下给其他函数增加额外的功能,相当于一个语法糖,对于新手来说可能比较难理解或者未知,但是以后或多或少都会遇到或者用到它。

在这里插入图片描述

一、修饰语的概念和作用 1、什么是修饰语?

修饰符(也称为装饰器)本身是一个函数,它可以在原有的函数或方法上添加一些额外的功能。

2. 修饰语的作用

一般来说,装饰器的作用是给现有的对象添加额外的功能。

比如这个函数是一个注册函数,但是有时候用户执行这个操作的时候就已经是一个注册用户了,这个函数我已经写好了,不想改,那么我们就可以使用装饰器给这个函数添加一个登录函数。

它经常用在有切面需求的场景,比如插入日志、性能测试、事务处理、缓存、权限验证等。装饰器就是解决这类问题的极佳设计。通过装饰器,我们可以把大量与功能本身无关的相同代码抽取出来继续复用。

在这里插入图片描述

下面我们来慢慢学习修改器的具体操作。

2. 修饰语的使用 1. 使用说明

在使用修饰符之前,我们要记住几条关于修饰符使用的说明:

(1)修饰符的关键字是@。只要它出现在代码中,就可以将其视为修饰符。

(2)装饰器修饰的是函数或者方法,而不是类。

(3)装饰器必须出现在所修饰的函数或方法的前一行,装饰器不能和函数定义在同一行。

例子:

在这里插入图片描述

装饰器本身虽然是一个函数,但是它的出现是受到规范的,上面的装饰器并没有出现在被修饰的函数或者方法的第一行,所以连 print("Uncle Long") 这行代码都执行不了。

(4)装饰器本身是一个函数,被装饰的函数作为参数传递给装饰器,执行装饰器中的函数,返回传递的函数对象,并调用返回的函数。

以上几点非常重要,下面我们通过修饰符的各种使用方式来加深理解。

在这里插入图片描述

我隐藏了很多技术知识,粉丝可以免费获取(点击这里)

2.基本用法

若没有调用被修饰的函数,则执行@后面的函数,并将被修饰的函数作为参数传递。装饰器函数的返回值可以是任意值。

例子:

def dec(a):		#修饰器函数
    print(a)	#看一下形参传了什么
    print("helloworld")
    return None
@dec	#使用修饰器
def funA():		#被修饰的函数
    pass

运行结果:

<function funA at 0x0000018D849BB670>
helloworld

首先我们可以看到这是一个非常简单的装饰器使用示例,@dec用于调用dec()装饰器函数,而被装饰函数funA()不执行任何操作。

其次我们看一下,修饰后的函数 funA() 并没有调用任何东西,而是作为参数传递给了装饰器函数 dec(),所以 dec() 需要一个形参来接受传递的值。我这里用了 a 作为形参,如果去掉 a 的话,系统会报错,因为修饰后的函数 funA() 返回的值没有任何东西可以接受。大家可以试一下。

最后还有一点,你有没有注意到,修饰函数 funA() 没有被调用?所以修饰函数 dec() 可以返回任何东西。上面它返回 None。你也可以返回“sick”。你可以返回任何东西。我们来看看修饰函数 funA() 被调用的情况。

在这里插入图片描述

如果调用被修饰的函数,则直接执行装饰器,并将被修饰的函数作为参数传递给装饰器,但是装饰器的返回值必须是当前参数。

例子:

def dec(a):
    print(a)
    print("helloworld")
    return "有病"
@dec	#使用修饰器函数
def funA():
    pass
funA()	#调用被修饰函数

运行结果:

<function funA at 0x000001F78A48B670>
helloworld
Traceback (most recent call last):
  File "E:\Python-learn\可迭代对象\修饰器\修饰器_test.py", line 11, in <module>
    funA()
TypeError: 'str' object is not callable

出现错误,说字符串 str 无法迭代。为什么?其实原因是当修饰符存在并且被使用时,再次调用被修饰的函数,此时返回值不能是任意值,修饰符函数只能返回传入的值。你可以试试把“有病”改成 a,输出就正常了。或者你可以把 funA() 这行代码去掉,不调用该函数,输出也正常。

def dec(a):
    print(a)
    print("helloworld")
    return a
@dec
def funA():
    pass
funA()

运行结果:

<function funA at 0x0000020F5664B670>
helloworld

一切正常。

我们看一下装饰器的执行逻辑:

def dec(a):
    print(a)
    print("修饰器函数")
    return a
@dec
def funA():
    print("被修饰函数")
funA()
print("龙叔")

运行结果:

<function funA at 0x000001D90E75B670>
修饰器函数
被修饰函数
龙叔

从这个运行结果我们可以看出装饰器的运行逻辑是:调用被装饰函数 funA(),但是并不直接执行,而是直接执行装饰器 dec,并将被装饰函数 funA() 作为参数传递给装饰器 dec,执行被装饰函数 dec() 中的代码。只有返回传递的值 a 后,才会执行被装饰函数 funA() 中的代码,最后在装饰函数 funA() 完成后再执行剩余的代码。

我用一张比较粗略的图来说明一下:

在这里插入图片描述

3.其他用法:函数嵌套

前面我们讲了装饰器的基本用法,装饰器的使用方式有很多种,还可以使用函数嵌套。

我们用一个简单的例子来演示一下:

def A(x):	#修饰器函数
    def B():	#修饰器函数里面嵌套的函数
        print("B")
    B()
    return x
@A	#使用修饰器
def C():	#被修饰函数
    print("C")
C()	#调用被修饰函数

运行结果:

B
C

从运行结果来看,修改的函数里面还可以嵌套函数。

4. 其他用法:闭包

闭包也是函数的一种,但是比较特殊。关于闭包的知识我就不在这里重复了,我们在【基础篇】里已经讲过了,如果忘记了可以去看看或者百度一下。我们来看看闭包在修饰符中是怎么用的。

def A(x):
    def B():
        print("B")
        return x()  # C 无差别调用只能一层,这里是无法通过x调用
    return B
@A
def C():
    print("C")
C()

运行结果:

B
C

可见,在装饰器函数中闭包也可以正常使用。

在这里插入图片描述

我隐藏了很多技术知识,粉丝可以免费获取(点击这里)

5.其他用法:修饰函数有参数形式

如果被修饰的函数有传递参数,则只能将参数传递给装饰器函数中的内嵌函数。

def A(x):	#修饰器函数
    print(x)
    def B(aa, bbb):  # 内嵌函数,接收被修饰函数传递的参数
        print("B")
        print(aa, bbb)
        return x(aa, bbb)  # C
    return B
@A
def C(n, nn):	#被修饰函数
    print("C")
C("10", "20")

运行结果:

<function C at 0x00000206BED6B670>
B
10 20
C

可以看出,虽然被修饰的函数C()向装饰器函数A()传递了参数,但是传递的默认对象依然是C(),而装饰器函数A()仍然接受了被修饰的函数C(),参数在装饰器函数A()内部传递给了函数B()。

即便你在 A() 中添加了两个形参,它也无法接受,只会报错。我们在装饰器的使用说明中已经说了“被修饰的函数作为参数传递给装饰器”,所以装饰器函数只接受被修饰函数的对象。如果需要传递其他参数,那么装饰器函数中必须有其他函数来接受传递的参数。

6.其他用法:带参数的修饰符,不带参数的函数,使用嵌入函数收集参数

如果装饰器有参数,被装饰的函数没有参数,那么只能使用嵌入函数来收集参数。

def fun(a=20):
    print(a)
    def c(bb):
        print(bb)
        return bb  # 可以无差别调用,因为是在第二层才接收的funB,相当于第一层
    return c
@fun(30)
def funB():
    print("xixixi")
funB()

运行结果:

30
<function funB at 0x0000025DAE4DD0D0>
xixixi

3. 内置装饰器

前面讲的都是我们自定义的装饰器,在 中有内置的装饰器,我们来了解一下常见的三种内置装饰器: 、 、 。

它们的作用就是将类中的方法变成静态方法,包括类的类属性和类方法,下面我们来简单了解一下它们的具体用法。

1.

方法可以转为属性,修饰的方法名必须和下面的方法名相同,只能用于私有属性。

我们来做个对比,我们创建一个类,在不使用它的情况下使用类中的方法,是这样的:

class A:
    def __init__(self):
        self.age = 20   #实例属性
    def getattr(self):  #打印实例属性
        print(self.age)
    def setattr(self,newage):	#给实例属性赋值
        self.age = newage
a = A()
a.setattr(30)
a.getattr()

运行结果:

30

没有问题,运行结果正常,那我们再尝试使用内置的装饰器看看是否有区别。

在使用内置装饰器之前,我们要补充一点:私有属性。在函数内部创建的属性都是私有属性,不经过特殊处理是不能在函数外部使用的。私有属性在前面加两个下划线表示,例如类中的私有属性就表示私有属性。我们来看一个简单的例子:

在这里插入图片描述

可以看出私有属性在类内部是可以访问的,但是类外部是不能访问的(需要经过一些处理才能访问,这里就不介绍了,大家可以上网查一下)。

回到我们的内置装饰器,在之前没有用到的例子中,我们进行一些修改,添加内置装饰器。

class A:
    def __init__(self):
        self.__age = 20
    @property
    def age(self):	#被修饰的方法
        return self.__age
    @age.getter
    def age(self):	#被修饰的方法
        return self.__age
    @age.setter
    def age(self, newage):	#被修饰的方法
        self.__age = newage
a = A()
a.age = 200
print(a.age)

运行结果:

200

从这个例子我们可以看出,内置装饰器可以用于私有属性,方法也可以变成属性。例如 a.age 就是调用属性而不是方法。虽然 age() 方法在类中出现多次,但是并没有因为方法覆盖而报错。这也是因为修饰符的存在,它规定了修饰的方法名必须和下面的方法名一致。

在这里插入图片描述

2. – 静态方法

内置装饰器是一个静态方法,其作用是将被装饰的方法从类中抽离出来,成为一个独立的函数,该函数不能访问类的属性。

我们先写一个简单的类,对比一下使用和不使用的情况。

class B:
    def __init__(self, name):
        self.name = name
    def eat(self):  # 打印传递的值
        print(self.name)
b = B("龙叔")
b.eat()

运行结果:

龙叔

这样是没有问题的,我们看一下它的使用,直接添加:

在这里插入图片描述

报错是因为加上@之后eat()方法就变成了一个普通的函数,虽然位于类中,但是其实相当于一个普通的函数,并不是类方法,所以得给它传值,不然形参self就没有值传进来,会报错。

正确的写法应该是:

在这里插入图片描述

我们来总结一下这个静态方法:

1. 这个函数是一个普通函数,只有这个类能用
2. 静态方法可以设置参数,也可以不需要参数了(self)
3. 该函数不能访问类的属性

3.

修饰方法和实例方法的区别在于接收的第一个参数不是self,而是cls(当前类的具体类型),修饰方法不能访问实例属性,但是可以访问类属性。

class B:
    age = 10
    def __init__(self, name):
        self.name = name
    def sleep(self):    #打印
        print(self)
    @classmethod
    def eat(cls):  # 被修饰的函数
        print(cls)  #看看传递的是类还是值
        print(cls.age)  #访问类的属性
        print(self.name)	#访问实例对象的属性
b = B("龙叔")
b.sleep()
b.eat()

运行结果:

<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
  File "D:\pythonProject1\专题2.py", line 21, in <module>
    b.eat()
  File "D:\pythonProject1\专题2.py", line 14, in eat
    print(self.name)
NameError: name 'self' is not defined

从结果中我们可以看出,从sleep()和eat()打印出的对象来看,sleep()传递的对象是创建的实例对象b,而经过装饰器修饰的函数eat()传递的则是类;从eat()访问类属性和实例属性来看,访问类属性没有问题,但是访问实例对象的属性时就会出错。

所以验证了之前说的:修饰方法和实例方法的区别在于接收的第一个参数不是self,而是cls(当前类的具体类型),修饰方法不能访问实例属性,但是可以访问类属性。

运行结果:

<__main__.B object at 0x0000024FD7B3CFA0>
<class '__main__.B'>
10
Traceback (most recent call last):
  File "D:\pythonProject1\专题2.py", line 21, in <module>
    b.eat()
  File "D:\pythonProject1\专题2.py", line 14, in eat
    print(self.name)
NameError: name 'self' is not defined

从结果中我们可以看出,从sleep()和eat()打印出的对象来看,sleep()传递的对象是创建的实例对象b,而经过装饰器修饰的函数eat()传递的则是类;从eat()访问类属性和实例属性来看,访问类属性没有问题,但是访问实例对象的属性时就会出错。

所以验证了之前说的:修饰方法和实例方法的区别在于接收的第一个参数不是self,而是cls(当前类的具体类型),修饰方法不能访问实例属性,但是可以访问类属性。

二维码
扫一扫在手机端查看

本文链接:https://by928.com/2053.html     转载请注明出处和本文链接!请遵守 《网站协议》
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。

项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13761152229

上班时间

周一到周五

公司电话

二维码
微信
线