鉴于不同运行环境差异,示例输出结果会有所不同。尤其是 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 位置参数 位置参数按排列顺序,又可细分为:
收集参数将多余的参数值收纳到一个元组对象里。所谓多余,是指对普通参数和有默认值参数全部赋值以后的结余。 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 的键值参数类型。
无默认值的 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 形参赋值 总结解释器对形参赋值过程。
收集参数 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' : '|' } 参数列表还不宜过长,可尝试将部分参数重构为复合对象。 复合参数变化与函数分离。添加字段,或修改缺省值,不影响已有用户。 |
|