python 类装饰器和函数装饰器区别_Python 函数和装饰器

函数的定义和调用

定义:def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个英文冒号":"。

函数名:在Python中函数即变量,所以函数名也同样遵循变量的命名约束。数字字母下划线组成,不能以数字开头且应具有描述函数功能的作用。

括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!

注释:每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面第一行。以增强代码的可读性。

调用:就是函数名() 函数名加括号 !不加不执行

来个函数

>>> def bar():

... print("hello function!\n")

...

>>> bar # 不加括号给的是这个函数的地址,

>>> bar() # 加了括号才会被执行

hello function!

我们现在想让一个函数完成两个数的加和操作,即我们自己来实现sum()这个内建函数的功能。

def mysum(a,b):

sum = a+b

result = mysum(1,2)

print(result)

结果为:None

怎么样才能让他能像sum()一样呢? 现在的函数功能已经完美的实现了,但是没人知道这个函数已经把任务完成了! 怎么证明自己呢,函数要把执行的结果返回让大家看到!

在函数的最后加上 return关键字

def mysum(a,b):

sum = a+b

return sum

result = mysum(1,2)

print(result)

结果为:3

详细讨论这个返回值

1、没有return关键字

这样的情况在Python中是被允许的,默认的Python返回了 None

2、有return但是后面没有值

def bar():

print(“I’m bar!”)

return

print(bar)

结果为:None

3、有return有返回值,当然上述例子mysum就是例子,显然是被允许的,并且这个返回值能被一个变量名引用到。

4、有return 有多个返回值

def mysum(a,b):

sum = a+b

return a,b,sum

result = mysum(1,2)

print(result)

结果为:(1, 2, 3)

当返回值为多个值的时候Python会把几个对象封装成一个元组,同时我们可以用多个变脸分别一一对应的取到每一个返回值,也可以用一个变量取到这个元组

注意 在Python中,将用逗号 "," 分割的连续的几个值,认为是一个元组

>>> 1,2,3

(1, 2, 3)

序列解压

>>> a,_,_,_,b = [i for i in range(5)]

>>> a

0

>>> b

4

>>> a,*_,b,c = 'unpack string'

>>> a

'u'

>>> b

'n'

>>> c

'g'

>>> _

['n', 'p', 'a', 'c', 'k', ' ', 's', 't', 'r', 'i']

>>> type(_)

>>> a,_,c = {1:'a',2:'b',3:'c'}

>>> a

1

>>> c

3

>>> *_,end = (1,2,3,4,5,6)

>>> end

6

return的作用:

当函数被调用时,Python解释器会由上到下的执行函数体中的语句,当执行到 return关键字时即终止函数并返回结果:之后的代码不会被执行!

def mysay():

print("hello first!")

print('我是华丽的分割线'.center(30, "*"))

return

print("hello second!")

mysay()

结果为:

hello first!

***********我是华丽的分割线***********

由此可见,return 什么都不写跟 直接不写return 还是有区别的,return可以指明函数在哪里结束,如果没有return 也没用中途报错 函数将执行完整个函数体里的代码。

函数参数

在两数字之和的时候我们看到在函数名后的括号里有两个参数a,b。这两个数称为函数的形参,当函数调用时传入具体的参数称为函数执行时候的实参。

参数可以有多个,定义和调用时,参数都需要用逗号分割。

函数的参数分为:位置参数,关键字参数,默认参数,动态参数

注意:函数传参必须是一个不可变对象的引用或对象本身。因为Python的传参遵循了主流面向对象的传参方式:形参实参共享对象。

就实参而言

位置传参

这种方式,必须严格的按照形参的位置来传递实参,不然,结果将不是我们期望的样子,参数多了少了都会报错

def func(arg1,arg2,arg3)

pass

func('name','age','salary') # 此时 :arg1 = 'name' arg2 = ’age' arg3 = ’salary'

关键字传参数

这种方式,丝毫不需要在意位置,并且有默认值,即使我们不传入实参,也不会报错。

def func(arg1,arg2,arg3)

pass

func(arg3=’name',arg2 = ’age', arg1 = ’salary') # 此时 :arg1 = 'salary’ arg2 = ’age' arg3 = ’name’

混用

在两种方式混用时,必须遵循,位置传入的参数在前,关键字传入的参数在后,并且,一个形参只能被传入唯一的一个值。

def func(arg1,arg2,arg3)

pass

func('name',arg3 = ’age',arg2 = ’salary') # 此时 :arg1 = 'name' arg2 = ’salary’ arg3 = ’age’

就形参而言

位置参数:位置参数必须被传入单一的对应值,无论用何种方式传参。

关键字参数:形参的关键字参数,也就是形参的默认值。多数时候不需要修改的值我们多设为默认值。

def style(id, color="blue"):

"""这是一个样式控制函数,大多数时候背景颜色为蓝色

此时 将color 设置为 蓝色

"""

return id, color

print(style(3,'red'))

print(style(3))

注意:尽可能的避开可变类型称为默认值的情况(避开容器类型),因为它们是对象的引用的集合,当参数被传入函数时候,所做的一切操作都不会修改容器本身,而是修改了其中的对象。造成了变化的累积效应。

def init_data(name,lst =[]):

lst.append(name)

return lst

print(init_data('monkey'))

print(init_data('JIAJIA'))

['monkey']

['monkey', 'JIAJIA']

# 不是想象中的初始化两个列表,而是在同一个表上累积

形参和实参是共享对象的,lst 是一个可变对象的引用,函数每次调用时默认传的是同一个可变的对象,是同一个地址,每次修改的都是相同的可变对象。

动态参数

def bar(id,age=18,*args,**kwargs):

print(id,age,args,kwargs)

bar(1608,'谁收留我1','谁收留我2','看看谁要我3',sex='famale')

# 1608

# 谁收留我1

# ('谁收留我2', '看看谁要我3')

# {'sex': 'famale'}

位置参数必须要按照位置传参,多余的位置参数会被封装成元组的形式保存在args中,多余的关键字参数会被打包成字典封装在kwargs中。

函数嵌套

函数的嵌套调用就是在函数中调用另一个函数。

def func():

print('func')

def bar():

print('The bar')

func()

bar() # 在bar中调用func()

定义

def func():

funcname = 'func'

print(funcname)

def bar():

funcname = 'bar'

print(funcname)

def foo():

funcname = 'foo'

print(funcname)

foo()

bar()

func()

nonlocal 在嵌套函数中使用 nonloacl 关键字 声明变量为上层函数变量,而非本层函数。

nonlocal的使用规则:

1、他必须在某函数的内嵌函数中使用。

2、在他声明的变量之前当前函数中不能有同名的变量存在。

注意 : 无法绑定到全局变量,只能是局部变量。

def outfunc():

name = 'outfunc'

def infunc():

nonlocal name

name = 'infunc'

print(name)

infunc()

print(name)

outfunc()

作用链域

name = 'error! '

def outfunc():

print('Outfunc name ="{}"'.format(name))

def func0():

name = 'monkey'

def func1():

print('In func1 name ="{}"'.format(name))

def func2():

outfunc()

print('In func2 name ="{}"'.format(name))

func2()

func1()

outfunc()

func0()

# In func1 name ="monkey"

# Outfunc name ="error! "

# In func2 name ="monkey"

# Outfunc name ="error! "

注意

函数会在执行前检查函数体中变量的定义,如果在本层没有找到,就往它的外一层找,还没找到就再往外层,直到找到全局,还没找到就报错,找到了就使用。

高阶函数

定义:函数的参数或返回值是函数的函数,就称为高阶函数(当返回值为函数本身,则称为函数的递归)。

函数的参数是另一个函数

def bar():

print(" This is bar!")

def foo(func):

print("This is foo:")

func()

foo(bar)

将 bar 作为函数 foo 的参数传入,可以在foo中执行bar 即 foo 中的func。

函数的返回值是另一个函数

def foo():

print("This is foo:")

def foo_inner():

print("This is foo_inner")

return foo_inner

ret = foo()

ret()

将 foo_inner 作为返回值传出,在函数外执行。

函数闭包

嵌套函数中,内部函数调用外部函数的变量

def outer():

name = 'outer'

def inner():

print(name)

print(inner.__closure__)

outer()

# (,)

用closure方法来判断是否为闭包

以cell 开头,这就是一个闭包 返回的是一个 None 则不是闭包

0x100e51538 是inner的地址

str是使用外层变量的类型 后面是其内存地址

闭包的正确姿势

def outer():

name = 'outer'

def inner():

print(name)

return inner

func = outer()

func()

这样子来,name 会被长久的保存下来,因为func接受了outer内部函数的地址,而内部函数使用了outer下的变量 name 所以,这个变量得以在内存中长久的保留。

如果在一个内部函数里对在外部作用域(但不是在全局作用域)的变量进行引用,但不在全局作用域里,则这个内部函数就是一个闭包。

实际上,闭包的用处/优点有两条:

从函数外可以读取函数内部的变量,或直接执行内层函数

让这些变量的值始终保持在内存中(也可以理解为保留当前运行环境)

装饰器

装饰器是什么?完成怎么样的功能?

装饰器要求再不修改函数代码,不修改函数调用方式的前提下,为函数添加新的功能。

装饰器的本质是函数

在Python中函数和变量其实本质上是一样的,变量可以指向一个函数对象。函数即变量!

初级版本装饰器

定义一个被装饰的函数:

def func():

print("被装饰的函数")

版本一

我们想到定义一个 decrator_v1 函数 接受被装饰的函数,加上新功能后返回这个函数的地址。然后再将func变量指向 decrator_v1 函数,参数为 func 貌似大功告成。

def decrator_v1(foo):

print('这是新功能')

foo()

return foo

func = decrator_v1(func)

func()

# 这是新功能

# 被装饰的函数

# 被装饰的函数

哎呀~ 为什么 跟预期的结果不一样??? 从头到尾的捋了一遍!卧槽 func 函数被执行了两边,并且 第二遍压根就没鸟我们的新功能!

1 当执行func = decrator_v1(func)的时候,调用了decratoe_v1这时执行了我们想要的结果。

2 但是当返回foo 被变量func接受时,我们执行func,就执行了foo,此时的foo就是原来我们定义时候的func函数。因此,出现了两次执行,第二次只执行了func。

3 我们希望把 新功能和func本身封装在一起,要执行一起执行,就不会出现这样扯淡的情况了很显然,很自然我们想到了函数的闭包,封装到一个函数 那么 来吧 让我们封装一下。

版本二

def add_way(func): #1

def wrapper(): #3

print('这是新功能') #6

func() #7

return wrapper #4

func = add_way(func) #2

func()

# 这是新功能

# 被装饰的函数

完美~ 终于完成了使命~ !

解释一下代码:

相对于版本一来说,定义的wrapper函数就是用来解决版本一中调用就立即执行add_way()的问题的。

现在基本的满足了功能上的需求,没有改变调用方式,也没有改变原函数的代码。Python提供了一个 语法糖 "@" 来帮我们完成func = add_way(func)这件事情!

语法糖版本

def add_way(func):

def wrapper():

print('这是新功能')

func()

return wrapper

@add_way

def func():

print("语法糖版本:被装饰函数")

func()

到现在为止,装饰器就基本上成型了~!

回顾一下我们都做了什么:

1、利用高阶函数把 被装饰函数当成参数出入装饰器对象,然后 (函数的嵌套)用函数封装 新增方法(功能)之后把 封装后的对象

2、作为装饰器对象(函数)的返回值,然后将 被装饰函数名作为装饰器对象的引用,这样 就实现了装饰器

不改变函数的代码

不改变方法的调用方式

实现新增功能

再来打磨一下:

被装饰函数属性变化,依靠某些属性工作的模块可能无法工作

print('装饰之后的函数文档',func.__doc__)

print('装饰之后的函数名',func.__name__)

print('装饰之后的函数哈希值',func.__hash__)

结果与被装饰之前是不一样的~ 那么依靠__name__ 属性工作的模块就无法正常工作了~

被装饰函数如何传参?

终极版本装饰器

打磨后的装饰器:

注意

1、from functions import wraps 用wraps装饰器 来 装饰 传入我们自定义装饰器要装饰的函数,即接收的那个函数。

2、wrapper 要接受func(被装饰函数的)的所有参数

3、wrapper函数要返回func(被装饰函数的)返回值。

#!/usr/bin/env python3

#_*_ coding: utf-8 _*_

__author__ = "monkey"

from functools import wraps

# 真正完备的装饰器

# 先来看一下解释器对wraps函数的注释

"""Decorator factory to apply update_wrapper() to a wrapper function

Returns a decorator that invokes update_wrapper() with the decorated

function as the wrapper argument and the arguments to wraps() as the

remaining arguments. Default arguments are as for update_wrapper().

This is a convenience function to simplify applying partial() to

update_wrapper()."""

def func( *args,**kwargs):

'''

:param args:接受多余的位置参数

:param kwargs: 接受多余的关键字参数

:return: 返回拿到的所有参数

'''

print('我是func')

return args,kwargs

print('装饰之前的函数文档',func.__doc__)

print('装饰之前的函数名',func.__name__)

print('装饰之前的函数哈希值',func.__hash__)

def decrator_end(func): #1

'''

:param func: 接受被装饰函数对象,以便与在装饰器内部执行

:return: 一个封装新功能的对象的地址

'''

@wraps(func) #在这里会把wrap 函数的性质完完整整的转变成func函数的样子

def wrapper(*args,**kwargs): #3

'''

wrapper 因为这里的wrapper要接受func传入的所有参数

:param args: 我的含义大家都知道

:param kwargs: 我跟上面一样

:return: 被装饰函数的执行结果

'''

print('这是新功能') #6

ret = func(*args,**kwargs) #7

return ret

return wrapper #4

func = decrator_end(func) #2

x = func('hook','monkey',name='monkey')

print(x)

print('装饰之后的函数文档',func.__doc__)

print('装饰之后的函数名',func.__name__)

print('装饰之后的函数哈希值',func.__hash__)

结果:

装饰之前的函数文档

:param args:接受多余的位置参数

:param kwargs: 接受多余的关键字参数

:return: 返回拿到的所有参数

装饰之前的函数名 func

装饰之前的函数哈希值

这是新功能

我是func

(('hook', 'monkey'), {'name': 'monkey'})

装饰之后的函数文档

:param args:接受多余的位置参数

:param kwargs: 接受多余的关键字参数

:return: 返回拿到的所有参数

装饰之后的函数名 func

装饰之后的函数哈希值

注意观察 装饰之后的属性变化,虽然貌似完全是被装饰函数本身,但是通过__hash__方法我们知道那并不是原来的__hash__事情总不是那么尽善尽美,达到需求即可!这样就可以了,真的要修改__hash__ 可以自己在函数中 重写 类的__hash__方法即使这样 他仍然不是原来的函数,内存不一样,怎么改 都只是很像 改变不了他们是两个对象的事实,但是对于使用而言,对用户 或 调用者透明,封装之后他们是不是一个对象这个问题没人会去关注!所以不必要非苛求完美!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值