分享

函数所有的知识点都在此篇文章里,函数就相当于建房子的基石!

 北书房2014 2017-09-11

鉴于不同运行环境差异,示例输出结果会有所不同。尤其是 id,以及内存地址等信息。 请以实际运行结果为准。这里还是要推荐下我自己建的Python学习群:643692991,如果你正在学习python,小编欢迎你加入,大家都是Python党,不定期分享干货(只有Python爬虫,框架,零基础都有),包括我自己整理的一份2017最新的Python资料和零基础入门教程,欢迎初学和进阶中的小伙伴。

函数所有的知识点都在此篇文章里,函数就相当于建房子的基石!

2. 参数

如果动态语言是妖精,那么 Python 对参数的处理方式就是鼓动她去魅惑世人。我相信,再难找到如这般功能丰富得让人 “头疼” 的语言。

按定义和传参方式,参数可分作位置(positional)和键值(keyword)两类。允许设置默认值和余参收集,但不支持参数嵌套。

函数所有的知识点都在此篇文章里,函数就相当于建房子的基石!

形参出现在函数定义的参数列表中,可视作函数局部变量,仅能在函数内部使用。而实参由调用方提供,通常以复制方式将值传递个形参。形参在函数调用结束后销毁,而实参则受调用方作用域影响。不同于形参以变量形式存在,实参可以是变量、常量、表达式等,总之须有确定值可供复制传递。

不管实参是名字、引用,还是指针,都以值复制方式传递,随后形参变化不会影响实参。当然,对该指针或引用目标的修改,与此无关。

形参如普通局部变量出现在函数名字空间内。实参按顺序传递,也可以星号展开。

def test ( a , b , c = 3 ) :

print ( locals ( )

)

>> > test ( 1 , 2 ) # 忽略有默认值参数。

{ 'c' : 3 , 'b' : 2 , 'a' : 1 }

>> > test ( 1 , 2 , 30 ) # 为默认值参数显式提供实参。

{ 'c' : 30 , 'b' : 2 , 'a' : 1 }

>> > test ( * ( 1 , 2 , 30 ) ) # 星号展开。

{ 'c' : 30 , 'b' : 2 , 'a' : 1

}

使用命名方式传递时,无需理会参数顺序。这对于字典展开非常方便。

>> > test ( b = 2 , a = 1 )

{ 'c' : 3 , 'b' : 2 , 'a' : 1 }

>> > test ( ** { "b" : 2 , "a" : 1 } ) # 键值展开后,等同命名传递。

{ 'c' : 3 , 'b' : 2 , 'a' : 1

}

如混用两种方式,须确保顺序在命名之前。

>> > test ( 1 , c = 30 , b = 2 )

{ 'c' : 30 , 'b' : 2 , 'a' : 1 }

>> > test ( c = 30 , 1 , 2 )

SyntaxError

:

positional argument follows keyword argument

位置参数

位置参数按排列顺序,又可细分为:

  1. 普通位置参数,零到多个。

  2. 有默认值的位置参数,零到多个。

  3. 单星号收集参数,仅一个。

函数所有的知识点都在此篇文章里,函数就相当于建房子的基石!

收集参数将多余的参数值收纳到一个元组对象里。所谓多余,是指对普通参数和有默认值参数全部赋值以后的结余。

def test ( a , b , c = 3 , d = 4 , * args ) :

print ( locals ( )

)

>> > test ( 1 , 2 , 33 ) # 不足以填充普通参数和默认值参数。

{ 'args' : ( ) , 'd' : 4 , 'c' : 33 , 'b' : 2 , 'a' : 1

}

>> > test ( 1 , 2 , 33 , 44 , 5 , 6 , 7 ) # 填充完普通和默认值参数后,收集剩余参数值。

{ 'args' : ( 5 , 6 , 7 ) , 'd' : 44 , 'c' : 33 , 'b' : 2 , 'a' : 1

}

不能对收集参数命名传参。

def test ( a , * args ) :

pass

>> > test ( a = 1 , args = ( 2 , 3 ) )

TypeError : test ( )

got an unexpected keyword argument

'args'

键值参数

最简单做法,是在位置参数列表后放置一个键值收集参数。

def test ( a , b , * args , ** kwargs ) :

print (

kwargs

)

>> > test ( 1 , 2 , 3 , x = 1 , y = 2 ) # 多余位置参数被 args 收集。

{ 'x' : 1 , 'y' : 2 }

>> > test ( b = 2 , a = 1 , x = 3 , y = 4 ) # kwargs 收集多余的键值参数。

{ 'x' : 3 , 'y' : 4

}

键值收集仅针对命名传参,对多余的位置参数没兴趣。

def test ( a , b , ** kwargs ) : pass

>> > test ( 1 , 2 , 3 , x = 1 )

TypeError : test ( ) takes 2

positional arguments but

3

were given

在此基础上,Python 3 新增了一种名为 keyword-only 的键值参数类型。

函数所有的知识点都在此篇文章里,函数就相当于建房子的基石!

  1. 与位置参数列表分隔边界,星号。

  2. 普通 keyword-only 参数,零到多个。

  3. 有默认值的 keyword-only 参数,零到多个。

  4. 双星号键值收集参数,仅一个。

无默认值的 keyword-only 必须显式命名传参,否则视为普通位置参数。

def test ( a , b , * , c ) :

print ( locals ( )

)

>> > test ( 1 , 2 , 3 )

TypeError : test ( ) takes 2 positional arguments but 3 were given

>> > test ( 1 , 2 )

TypeError : test ( ) missing 1 required keyword - only argument :

'c'

>> > test ( 1 , 2 , c = 3 )

{ 'c' : 3 , 'b' : 2 , 'a' : 1

}

即便没有位置参数,keyword-only 也须按规则传递。

def test ( * , c ) : pass

>> > test ( 1 )

TypeError : test ( ) takes 0 positional arguments but 1 was given

>> > test ( c = 1

)

除单星号外,位置收集参数(*args)也可作为边界。但只取其一,不能同时出现。

def test ( a , * args , c , d = 99 , ** kwargs ) :

print ( locals ( )

)

>> > test ( 1 , 2 , 3 , c = 88 , x = 10 , y = 20 )

{

a : 1 ,

args : ( 2 , 3 ) ,

c : 88 ,

d : 99 ,

kwargs : { 'x' : 10 , 'y' : 20 } ,

}

同样,不应对键值收集参数命名传参。其结果是弄巧成拙,被当作普通参数收集。

def test ( ** kwargs ) :

print (

kwargs

)

>> > test ( kwargs = { "a" : 1 , "b" : 2 } ) # 被当作普通键值参数收集,kwargs["kwargs"]。

{ 'kwargs' : { 'a' : 1 , 'b' : 2 } }

>> > test ( ** { "a" : 1 , "b" : 2 } ) # 这才是正确的收集姿势。

{ 'a' : 1 , 'b' : 2

}

默认值

参数默认值允许省略实参传值,让函数调用更加灵活。尤其是那些参数众多,或具有缺省设定的函数。

但需注意,默认值在函数创建时生成,保存到特定属性(__defaults__),为每次调用所共享。如此,其行为类似静态局部变量,会 “记住” 以往调用状态。

def test ( a , x = [ 1 , 2 ] ) :

x . append ( a )

print (

x

)

>> > test . __defaults__

( [ 1 , 2 ] ,

)

默认值对象作为函数构建参数存在。

>> > dis . dis ( compile ( "def test(a, x = [1, 2]): pass" , "" , "exec" ) )

1 0 LOAD_CONST 0 ( 1 )

2 LOAD_CONST 1 ( 2 )

4 BUILD_LIST 2 # 构建默认值对象。

6 BUILD_TUPLE 1 # 作为构建参数。

8 LOAD_CONST 2 ( < code object test > )

10 LOAD_CONST 3 ( 'test' )

12 MAKE_FUNCTION 1

# 参数 1 表示包含缺省参数。

如默认值为可变类型,且在函数内做了修改(比如本节示例)。那么后续调用会观察到本次改动,导致默认值失去原本含义。

>> > test ( 3 )

[ 1 , 2 , 3 ]

>> > test ( 4 ) # 在上次调用基础上,添加。

[ 1 , 2 , 3 , 4

]

故建议默认值选用不可变类型,或以 None 表示可忽略。

def test ( a , x = None ) :

x = x or [ ] # 忽略时,主动新建。

x . append ( a )

return

x

>> > test ( 1 )

[ 1 ]

>> > test ( 2 )

[ 2 ]

>> > test ( 3 , [ 1 , 2 ] ) # 提供非默认值实参。

[ 1 , 2 , 3

]

要说静态局部变量还是有实际用处的,可在不用外部变量的情况下维持函数状态。比如,用来设计调用计数等。但相比参数默认值,正确做法是为函数创建一个状态属性。毕竟变量为函数内部使用,而参数属对外接口。所创建属性等同函数对象生命周期,不会随函数调用结束而终结。

def test ( ) :

test . __x__ = hasattr ( test , "__x__" ) and test . __x__ + 1 or 1

print ( test .

__x__

)

>> > test ( )

1

>> > test ( )

2

>> > test ( )

3

形参赋值

总结解释器对形参赋值过程。

  1. 按顺序对位置参数赋值。

  2. 按命名方式对指定参数赋值。

  3. 收集多余的位置参数。

  4. 收集多余键值参数。

  5. 为没有赋值的参数设置默认值。

  6. 检查参数列表,确保非收集参数都已赋值。

收集参数 args、kwargs 属习惯性命名,非强制。

对应形参顺序,实参也有些基本规则。

  • 无默认值参数,必须有实参传入。

  • 键值参数总是以命名方式传入。

  • 不能对同一参数重复传值。

无论是以位置和命名两种不同方式,还是多个星号展开里有重复主键,都不能导致对同一参数重复传值。

def test ( a , b ) : pass

>> > test ( 1 , 2 , a = 1 )

TypeError : test ( ) got multiple values for argument 'a'

>> > test ( ** { "a" : 1 , "b" : 2 } , ** { "a" : 1 } )

TypeError : test ( ) got multiple values for keyword argument 'a'

>> > test ( * ( 1 , 2 ) , ** { "a" : 1 } )

TypeError : test ( ) got multiple values for

argument

'a'

键值收集参数会维持传入顺序(PEP468)。如键值参数存在次序依赖,那么此功能就有实际意义。还有,收集参数并不计入 __code__.co_argcount 中。

签名设计

设计一个 print 函数,那么其理想签名应该是:

def printx ( * objects , sep = "," , end = "\n" ) :

参数分作两部分:待显示对象为主,可忽略的显示设置(options)为次。从设计角度讲,待显示对象是外部资源,而设置项用于控制函数自身行为,分属不同范畴。如同卡车所装载货物,与车自身控制系统的差别。

据此接口,对象数量未定,设置除默认值外,还可显式调整。如使用 Python 2,作为收集参数的 objects 就只能放在参数列表尾部。

def printx ( sep = "," , end = "\n" , * objects ) :

print ( locals ( )

)

除主次不分导致不美观外,最大麻烦是不能绕开默认设置,单独为 objects 传值。加上收集参数无法命名传参,直接导致默认配置项毫无意义。

>> > printx ( 1 , 2 , 3 )

{ 'objects' : ( 3 , ) , 'end' : 2 , 'sep' : 1 }

>> > printx ( objects = ( 1 , 2 ) )

TypeError : printx ( )

got an unexpected keyword argument

'objects'

幸好,Python 3 提供 keyword-only 参数方式可完美解决此问题。

def printx ( * objects , sep = "," , end = "\n" ) :

print ( locals ( )

)

>> > printx ( 1 , 2 , 3 )

{ 'objects' : ( 1 , 2 , 3 ) , 'end' : '\n' , 'sep' : ',' }

>> > printx ( 1 , 2 , 3 , sep = "|" )

{ 'objects' : ( 1 , 2 , 3 ) , 'end' : '\n' , 'sep' : '|'

}

参数列表还不宜过长,可尝试将部分参数重构为复合对象。 复合参数变化与函数分离。添加字段,或修改缺省值,不影响已有用户。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多