写好Python之函数


避免使用可变对象作为参数德默认值
当Python解释器对函数定义时,通过默认参数表达式来判断他们的值。该表达式仅发生一次。调用该函数不会触发其他的参数表达式值。由于计算的值用于所有函数调用序列,使用可变对象作为默认值会发生一些未期待的结果。
 
可变对象意味值可以直接进行修改。list, dict, set或大部分类实例。我们可以使用调用append来追加list元素。
 
不可变对象,如其名,意为创建后不可修改,string, int, 和tuple对象都是不可变对象。我们不能直接修改string值,例如,所有对string操作都会返回一个新的string对象。
 
那么,为什么会影响到默认参数呢。重复调用函数通过使用默认参数来初始化参数值,使用像string这样的类型没有问题,因为我们不能直接修改其值。但对于可变对象,改变其值将会影响后序函数调用。在以下实例中,空的list作为默认参数。如果函数中给list添加了元素,那么list参数将持续保存该元素。list参数不会重置为空list,相同list对象作为每次函数调用使用。
 
糟糕的
def f(a, L=[]):  
    L.append(a)
    return L
 
print(f(1))
print(f(2))
print(f(3))
 
# This will print
#
# [1]
# [1, 2]
# [1, 2, 3]
推荐的
def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
 
    print(f(1))
    print(f(2))
    print(f(3))
 
# This will print
#
# [1]
# [2]
# [3]
使用args和*kwargs来接受任意参数
经常,函数需要接受任意位置参数或者关键字参数,使用args和*kwargs作为参数允许接受任意位置和关键参数。
 
这种方法对创建试用向后兼容的api也非常有用。在函数中接受任意参数,我们就可以任意为新版本添加参数而不会影响已经存在并且参数少于当前参数代码。
 
当然,这也不时说我们不该在函数中使用命名参数,实际上,这应该是我们首选。然而在很多场景args和*kwargs也是非常有用的。
 
糟糕的
def make_api_call(foo, bar, baz):
    if baz in ('Unicorn', 'Oven', 'New York'):
        return foo(bar)
    else:
        return bar(foo)   
 
# I need to add another parameter to `make_api_call`
# without breaking everyone's existing code.
# I have two options...
 
def so_many_options():
    # I can tack on new parameters, but only if I make
    # all of them optional...
 
def make_api_call(foo, bar, baz, qux=None, foo_polarity=None
                    baz_coefficient=None, quux_capacitor=None,
                    bar_has_hopped=None, true=None, false=None
                    file_not_found=None):
    # ... and so on ad infinitum
    return file_not_found
 
def version_graveyard():
    # ... or I can create a new function each time the signature
    # changes.
    def make_api_call_v2(foo, bar, baz, qux):
    return make_api_call(foo, bar, baz) - qux
 
def make_api_call_v3(foo, bar, baz, qux, foo_polarity):
    if foo_polarity != 'reversed':
        return make_api_call_v2(foo, bar, baz, qux)
    return None
 
def make_api_call_v4(foo, bar, baz, qux, foo_polarity, baz_coefficient):
    return make_api_call_v3(foo, bar, baz, qux, foo_polarity) * baz_coefficient
 
def make_api_call_v5(foo, bar, baz, qux, foo_polarity,baz_coefficient, quux_capacitor):
    # I don't need 'foo', 'bar', or 'baz' anymore, but I have to
    # keep supporting them...
    return baz_coefficient * quux_capacitor
 
def make_api_call_v6(foo, bar, baz, qux, foo_polarity, baz_coefficient,quux_capacitor, bar_has_hopped):
    if bar_has_hopped:
        baz_coefficient *= -1
    return make_api_call_v5(foo, bar, baz, qux,foo_polarity, baz_coefficient,quux_capacitor)
 
def make_api_call_v7(foo, bar, baz, qux, foo_polarity, baz_coefficient,quux_capacitor, bar_has_hopped, true):
    return true
 
def make_api_call_v8(foo, bar, baz, qux, foo_polarity, baz_coefficient,
    quux_capacitor, bar_has_hopped, true, false):
    return false
 
def make_api_call_v9(foo, bar, baz, qux, foo_polarity, baz_coefficient,quux_capacitor, bar_has_hopped,true, false, file_not_found):
    return file_not_found  
推荐的
def make_api_call(foo, bar, baz):
    if baz in ('Unicorn', 'Oven', 'New York'):
        return foo(bar)
    else:
        return bar(foo)
 
# I need to add another parameter to `make_api_call`
# without breaking everyone's existing code.
# Easy..
.
def new_hotness():
    def make_api_call(foo, bar, baz, *args, **kwargs):
        # Now I can accept any type and number of arguments
        # without worrying about breaking existing code.
        baz_coefficient = kwargs['the_baz']
        # I can even forward my args to a different function without
        # knowing their contents!
        return baz_coefficient in new_function(args)

评论关闭