函数的定义和调用
定义: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__方法即使这样 他仍然不是原来的函数,内存不一样,怎么改 都只是很像 改变不了他们是两个对象的事实,但是对于使用而言,对用户 或 调用者透明,封装之后他们是不是一个对象这个问题没人会去关注!所以不必要非苛求完美!