使用None和文档说明动态的指定默认参数


有些时候你需要使用一个非静态类型的数据作为关键字参数的默认值。例如:你想打印被日志事件的时间标记的日志信息。默认情况下,当函数被调用的时候你想让欣心中包含时间信息。你可能会尝试下面的方法,假设默认参数每次被调用都会被重新赋值。

def log(message, when=datatime.now()):
    print("%s:%s" %(when, message))

log('Hi there!')
sleep(0.1)
log('Hi again')
>>>
2014-11-15 21:10:10.371432: Hi there
2014-11-15 21:10:10.371432: Hi again

从上面打印的信息来看,时间戳是相同的,这是因为datetime.now仅仅被执行了一次,而且是在函数被定义的时候。默认参数的值仅仅在模块被引入的时候才会被赋值一次,而这通常发生在程序开始运行的时候。当模块已经加载完这段代码后,datetime.now的默认值就不会被改变了。

Python中,为了实现预期的结果,惯用的方式是提供一个值为None的默认值,并且在帮助文档中记录详细的行为和使用方法(详见第49项:为每一个函数,类,模块编写说明文档)。当代码发现一个值为None的参数的时候,就可以为其分配默认值了。

def log(message, when=None):
    """Log a message with a timestamp.

    Args:
        message: Message to print
        when: datetime of when the message occurred.
            Default to the present time.
    """
    when = datetime.now() if when is None else when
    print("%s: %s" %(when, message))

# 测试

log('Hi there!')
sleep(0.1)
log('Hi again!')
>>>
2014-11-15 21:10:10.472303: Hi there!
2014-11-15 21:10:10.473395: Hi again!

当参数容易发生改变的时候使用None作为默认参数值尤其重要。例如:你想加载一个被编码为JSON的数据值,如果解码的时候失败了,你想默认返回一个空字典,你就可以尝试下面的方法。

def decode(data, default={}):
    try:
        return json.loads(data)
    except ValueError:
        return default

但是这段代码出现的问题和上面的那个时间戳是一样的。默认的空字典被所有调用这个函数的函数共用,因为默认值只是在本模块被加载的时候执行了一次,或许这会导致让人吃惊的现象哦。

foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
>>>
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}

本想得到两个不同的单值字典,最后却得到了一致的双值字典结果。修改一个的话很明显也会改变另一个。罪魁祸首就是foobar都等价于默认参数,他们是同一个字典对象。

assert foo is bar

解决办法就是对关键字参数设置默认值None并且记录在该函数的说明文档中。

def decode(data, default=None):
    """Load JSON data from string.

    Args:
        data: JSON data to be decoded.
        default: Value to return if decoding fails.
            Defaults to an empty dictionary.
    """

    if default is None:
        default = {}
    try:
        return json.loads(data)
    except ValueError:
        return default

# 现在测试一下
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
>>>
Foo: {'stuff': 5}
Bar: {'meep': 1}

备忘录

  • 默认参数只会被赋值一次:在其所在模块被加载的过程中,这有可能导致一些奇怪的现象。
  • 使用None作为关键字参数的默认值会有一个动态值。要在该函数的说明文档中详细的记录一下。

results matching ""

    No results matching ""