Python Functions


Function Arguments

如果一个函数的positional arg定义了default value,那么它后面的参数都需要定义default value

def my_func(a, b=6, c=10):
    # code..

如果我们只想指定ac,则需要用到keyword arguments

my_func(a=1, c=2)

需要注意,如果某个参数使用了keyword argument,它后的参数必须也指定名字

def my_func(a, b, c)
my_func(c=1, 2, 3) # wrong
my_func(1, b=2, 3) # wrong
my_func(1, b=2, c=3) # corret
my_func(1, c=2, b=3) # corret

使用默认参数有一些需要注意的地方,他们在函数创建时就是求值,比如

def log(s, dt=datetime.utcnow()):
    #code

log('hello')

此时,如果我们不传入date,那么date的时间将永远是函数创建的时间,而不是函数调用的时间。解决这个问题,可以将默认参数生命为None

def log(s, dt=None):
    dt = dt or datetime.utcnow()
    # code

另一个需要注意的地方是尽量不要用mutable的数据结构,比如

args = [1,2,3]
def my_func(list = args)
    print(args)

args.append(4)
my_func()

此时函数的默认值list会被mutate,导致结果不符合预期。和上面例子类似的另一个例子是空数组做默认值

def add(name, name_list=[]):
    name_list.add(name)
    return name_list

l1 = add('jon')
l2 = add('kate')

上述代码中,l1l2指向的实际上是同一个数组,因此l1l2中的内容是一样的,当第二次调用add的时候,name_list已经不是空的了。解决办法和上面一样

def add(name, name_list=None):
    if not name_list:
        name_list = []
    name_list.add(name)
    return name_list

Pack与Unpack

所谓Packed Values是指一些值以某种方式被pack到一起,最常见的有tuple, list, string, set, 和map这些集合类。对于这些集合类,Python提供了一种展开的方式,即将集合类中的元素以tuple的形式展开

a,b,c = [1,2,3] #a->1, b->2, c->3
a,b,c = 10,20,'hello' #a->10, b->20, c->hello
a,b,c = 10, {1,2}, ['a','b'] #a->10, b->{1,2}, c->['a','b']
a,b,c = 'xyz' #a->x, b->y, c->z

上述代码中,等号左边定义了一个tuple,右边是一个集合对象,unpacking的方式是按照位置一一对应。看起来所谓unpacking,实际上就是对集合类对象进行for循环为变量依次赋值。

但是对于哈希表,for循环只得到key,因此unpacking的结果也是key,且由于哈希表是无序的,unpacking出来的结果也是不确定的,对于set同理。

# unpacking a map object
d = {'key1':1, 'key2':2, 'key3':3}
a,b,c = d #a->'key2' b->'key3', c='key1'

# unpacking a set object
s = {'x','y','z'}
a,b,c = s #a->'z' b->'x', c='y'

在Python中,unpacking对于实现swap功能很方便,只需要unpack一次即可

#swap a,b
a,b = b,a

上面代码的执行顺序是先进行RHS求值,然后将得到的值再进行LHS赋值给a,b

使用***进行unpack

在Python 3.5后,可使用*做局部的unpack,比如一个集合,我只想unpack第一个元素,然后将剩下部分unpack给另一个变量

l = [1,2,3,4,5,6]

#using slicing
a = l[0]
b = l[1:]

#using simple unpacking
a,b = l[0]:l[1:]

#using * operator
a, *b = l

slicing只适用于数组,而*可适用于任何iterable的集合变量,对于有序集合,* unpack的结果为为数组

a, *b = (1,2,3) #a = 1, b = [2,3]
a, *b = "abc" #a = 'a', b = ['b','c']  
a, *b, c = 1,2,3,4 #a = 1, b = [2,3], c = 4

对于无序集合,比如dict和set,unpack的结果是无序的

d1 = {'p':1, 'y':2}
d2 = {'t':3, 'h':4}
d3 = {'o':5, 'n':6}

d = [*d1, *d2, *d3] # unpack key

对于dict来说,* 只能unpack key,如果想要unpack pair,需要用**,但是只能用在等号右边

d = {**d1, **d2, **d3}
x = {'a':5, 'b':6, **d1}

*args and *kwargs

*args可以unpack 任意多个positional arguments,如果作为函数参数,则它后面不能再有其它的positional args

def func1(a,b,c):
    #code

l = [10, 20 ,30]
func(l) # wrong, func1 expects three positional args
func(*l) # works

def func2(a, b, *args, d):
    # code

func2(10, 20, 'a', 'b', 100) #wrong

前面提到,我们也可以用keyword argument来给函数传参

func1(a=1, c=2, b=3)

如果想要强制用keyword argument传参,那么keyword argument需要定义在*args的后面,并且调用时必须传入

def func1(a,b,*args, d):
    # code
func1(1, 2, 'x', 'y',d = 100) # correct
func1(1, 2, d = 100) # correct
func1(1, 2) # wrong d must be assigned

如果在keyword argument之前不需要任何positional arguments,则可以用*代替

def func(*, d): 
    # code 
func(1, 2, d=100) # wrong
func(d = 100) #correct

*说明d前面没有任何的positional argument,注意这个和*args不同,后者表示有任意多个positional arg

*args类似,**kwargs可以指定任意数量的keyword arguments,但是**kwargs后面不能再定义参数

def func(*, d, **kwrags):
    # code
func(d=1, a=2, b=3)
func(d=1)

上面例子中,*表示函数不接受任何positional arguments,d是一个keyword-only argument

Resources