函数
函数是代码复用和模块化的基本单位,用于单一逻辑
函数以整体的形式面向调用者,函数名称、参数列表,还有返回值构成的接口原型。函数可以接收任何类型的输入作为参数,并返回任何类型的结果
首先来了了解函数的定义和调用
def
def <func_name>(<formal parameters>):
return <return expression>
函数名命名方式与变量命名一致
一个函数可以接收任何数量(包括0)的任何类型的值作为输入,并且返回任何数量(包括0)的任何类型的结果。如果函数不显示调用return,那么默认返回None
函数的局部变量
函数体的执行会为这和函数的局部变量引入一个新的命名空间。所有函数体中的赋值语句,都会把变量名和值存在这个命名空间中。
函数体中引入一个变量时,首先查看这个函数的命名空间,如果这个函数定义包裹在其他函数定义中,就依次查看外围函数的命名空间,然后查看全局命名空间。
函数的形参也是存在于函数的局部命名空间中。函数的局部命名空间会在每次调用和返回时进行创建初始化和删除。
函数调用的实参传递的是通过赋值语句做的,所以传递的是对象的引用。对于类似序列的可变类型,如果其作为参数按引用传递给函数,在函数体中改变他的值也会影响他在外围命名空间的值
函数的参数
函数在声明时大概有下面4中形式
- 位置参数:
def func(a,b):pass
- 关键字参数:
def func(a,b=1):pass
- 任意位置参数:
def func(a,b=1,*c):pass
- 任意关键字参数:
def func(a,b=1,*c,**d):pass
位置参数(无默认值参数)
按照顺序传入参数的值一次赋值进去
def foo(arg1, arg2):
return arg1, arg2
foo('a', 'b')
#('a', 'b')
关键字参数
调用函数时可以指定对应参数的名字,可以采用与函数定义不同的顺序调用
def foo(arg1='a',arg2='b'):
return arg1,arg2
foo(arg2='c',arg1='d')
#('d','c')
foo(1,2)
#(1,2)
foo(1,arg1='d')
#typeerror
当位置参数和关键字参数同时存在时,关键字参数必须放在位置参数的后面
如果同时使用位置参数和关键字参数两种方式是调用函数,关键字参数也必须放到位置参数之后
调用环输时如果没有提供关键字参数的参数值时,将使用函数定义时的默认参数值
如果调用函数时提供关键字参数值,则将代替默认值
**注意:**函数的关键字参数值在函数定义时已经已经计算出来了,而不是在函数运行时
num = 1
def bar(arg1=num):
print(arg1)
num = 2
bar()-----1
print(num)---------2
所以在定义函数时,不要把可变的数据类型(列表、字典)党走关键字参数的参数值。函数的关键字只会被求值一次,不管函数被怎么调用,当关键字参数的参数值是可变对象时,在函数体中如果其值被改变,在此调用函数时默认值就是改变后的值
xyz_list = ['x', 'y', 'z']
def append_xyz(alist, blist=xyz_list):
alist.extend(blist)
return alist
append_xyz(['a'])
xyz_list.insert(0, 'w')
append_xyz(['a'])
#['a', 'w', 'x', 'y', 'z']
def bar(n, alist=[]):
alist.append(n)
return alist
print(bar(1))
print(bar(2))
print(bar(3))
#[1]
#[1, 2]
#[1, 2, 3]
def bar(n, alist=None):
if alist is None:
alist = []
alist.append(n)
return alist
print(bar(1))
print(bar(2))
print(bar(3))
#[1]
#[2]
#[3]
#None 是一个内置变量,当然不能被改变,每次函数bar()被调用就会用这个值给alist赋值
特殊参数
/
和*
表示可选的,即在此位置上可以传入仅限位置、位置或关键字、仅限关键字参数
-
仅限位置参数:特定形式可以被标记为权限位置。如果是仅限位置的形参,则其位置是重要的,并且是不能作为关键字传入。仅限位置形参要放在
/
之前。这个/
用来从逻辑上分隔仅限位置形参和其他形参。如果函数定义中没有/
,则表示没有仅限位置形参在
/
之后的形参可以为位置或关键字形参或仅限关键字形参 -
仅限关键字参数:要将形参标记为仅限关键字,即指明该形参必须以关键字参数的形式传入,应在参数列表的第一个仅限关键字形参之前放置一个
*
-
位置或关键字参数:如果函数定义中未使用
/
和*
,则参数可以按位置或关键字传递给函数
在函数中定义
def f(pos1,pos2,/,pos_or_kwd,*,kwd1,kwd2):
pass
- 如果希望形参的名称对用户来说不可用,则使用仅限位置形参。这适用于形参名称没有任何实际意义,以及当你希望强调规定调用时的参数顺序,或是需要同时收受一些位置形参和任意关键字形参等情况
- 当形参名称有实际意义,以及显式指定形参名称可使函数定义更易理解,或者当你想要防止用户过于依赖传入参数的位置时,则使用仅限关键字参数
任意位置参数
任意位置参数可以接收任意数量的位置参数。当参数被用在函数内部时,*
将一组可变数量的位置参数集合成参数值的元组
def concat(*lst,sep='|'):
return sep.jion((str(i) for i in lst))
print(concat('G', 30, '@', 'Hz', sep='')) # G30@Hz
print(concat('G', 30, '@', 'Hz')) #G|30|@|Hz
上面的关键词必须明确指明,不能通过位置推断
print(concat('G', 30, '-')) # G|30|-, Not G-30
通过这种方式给函数传入的所有位置参数都会以元组的形式返回输出 :
def print_args(*args):
print(args)
print_args(3, 2, 1, 'a', 'b', ['c', 'd'])
(3, 2, 1, 'a', 'b', ['c', 'd'])
任意关键字参数
使用**
可以将参数收集到一个字典里,参数的名字是字典的键,对应参数的值是字典的值
def print_kwargs(**kwargs):
print(kwargs)
print_kwargs(arg1=1, arg2=2, arg3='a')
#{'arg1': 1, 'arg2': 2, 'arg3': 'a'}
def dconcat(sep=':', **kwargs):
for k in kwargs.keys():
print('{}{}{}'.format(k, sep, kwargs[k]))
dconcat(hello='world', python='rocks', sep='~')
#hello~world
#python~rocks
解包
可以把一个列表或元组解包,对应的值作为位置参数传递,调用的时候要以*args
的形式:
lst = [0,1,2,3]
print(*lst)
#0 1 2 3
print(*range(5))
#0 1 2 3 4
解包为关键字实参:
将字典解包为关键字实参。字典的键作为形参的名字,是字符串,对应键值为传递的实参。语法上调用地时候就以**kwargs
地形式
def f(a, b, c):
print("a =", a, "b =", b, "c =", c)
d = {"a":5, "c":8, "b":2}
f(**d)
#a = 5 b = 2 c = 8
使*args
**kwargs
可以在函数参数外使用:
a = *range(3), # 这里的逗号不能漏掉
print(a)
d = {"hello": "world", "python": "rocks"}
print({**d}["python"])
所谓地解包:实际上可以看作是去掉()地元组或者是去掉{}地字典。
user = {'name': "Google", 'website': "https://www.google.com"}
defaults = {'name': "Anonymous User", 'page_name': "Profile Page"}
# 合并字典的3中方法
print({**defaults, **user})
defaults.update(user)
print(defaults)
{k:v for d in [user, defaults] for k, v in d.items()}
#{'name': 'Google', 'page_name': 'Profile Page', 'website': 'https://www.google.com'}
第一类对象:函数
只可以在执行期间创建并作为参数传递给其他函数或传入一个变数的实体。一般第一类对象具有以下特征:
- 可以被存入变量或其他结构
- 可以被作为参数传递给其他方法/函数
- 可以被作为方法/函数的返回值
- 可以在执行期被创建,而无需再=在设计其全部写出
- 由固定身份:指在实体内部表示,而不是根据名字来识别,比如匿名函数,还可以通过赋值叫任何名字。在py中,函数/方法都是第一类对象,这对于函数式编程语言来说是必须的
我们可以把函数作为列表、元组、集合和字典的元素。另外,由于函数名也是不可变的,可以把函数名作为字典的键。
内部函数
def make_adder(n):
def adder(x):
return x+n
return adder
add1 = make_adder(1)
add1(2)
#3
当函数内部多次执行复杂的任务时,为了避免循环和代码的堆叠重复,也可以使用内部函数
闭包
上面的例子make_adder()函数返回的内部函数可以看作是一个闭包。闭包是一个可以由另外一个函数动态生成的函数,并且可以改变和存储函数外创建的变量的值
lambda函数(匿名函数)
如果需要一个函数,但又不想费神的去命名他的时候,可以使用lambda函数(匿名函数,也是闭包)
sq = lambda x:x*x
sq(2)
#4
(lambda x:x*x)(2)
#4
lambda函数接收一个或多个参数(使用逗号分隔),冒号之后的部分为函数的定义
这个限制主要是为了防止滥用,因为当人们发觉lambda很方便,就比较容易滥用,可用多了会让程序看起来不那么清晰,毕竟每个人对于抽象层次的忍耐/理解程度都有所不同
装饰器
装饰器实质上就是一个函数。他把函数作为一个输入并返回另一个包装(修改)了输入函数之后的函数
需要强调的是,装饰器并不会修改原始函数的参数以及返回值。使用*args
和**kwargs
的目的就是为了确保任何参数都能适用。而返回结果基本都是调用原始函数func(*args,**kwargs)
的返回结果,其中func就是原始函数
带参数的装饰器
命名空间和作用域
上面我们讲的每一个函数都拥有自己的命名空间。每个程序的主要部分定义了全局命名空间,在全局命名空间的变量是全局变量。在一个函数内得到某个全局变量并使用它,但如果在函数内部直接对全局变量进行赋值或尝试修改他会引发异常,因为在函数的命名空间中并不存在这个变量,需要在变量前面显示地加关键字global
animal = 'fruitbat'
def change_and_print_global():
animal = 'wombat'
print(animal)
change_and_print_global()
print(animal)
#wombat
#fruitbat
两个用于获取命名空间内容地函数:
locals()
返回一个局部命名空间内容地字典globals()
返回一个全局命名空间内容地字典
animal = 'fruitbat'
def change_local():
animal = 'wombat'
print(locals())
change_local()
#{'animal': 'wombat'}