函数的定义和调用
定义: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 则不是闭包
# 0x103c33fd8 是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() #5
结果:
这是新功能 被装饰的函数
完美~ 终于完成了使命~ !
解释一下代码:
相对于版本一来说,定义的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__方法即使这样 他仍然不是原来的函数,内存不一样,怎么改 都只是很像 改变不了他们是两个对象的事实,但是对于使用而言,对用户 或 调用者透明,封装之后他们是不是一个对象这个问题没人会去关注!所以不必要非苛求完美!