None和doctoring的秘密

简介: None和doctoring的秘密

None和doctoring的秘密
用None和docstring来描述默认值会变的参数

有时,我们想把那种不能够提前固定的值,当作关键字参数的默认值。例如,记录日志消息时,默认的时间应该是出发事件的那一刻。所以,如果调用者没有明确指定时间,那么就默认把调用函数的那一刻当成这条日志的记录时间。现在试试下面的这种写法,假定它能让when参数的默认值随着这个函数每次的执行时间而发生变化。

from time import sleep
from datetime import datetime

def log(message, when=datetime.now()):
    print(f'{when}: {message}')

log('Hi there!')
sleep(0.3)
log('Hello again!')

>>>
2024-05-23 20:11:10.224560: Hi there!
2024-05-23 20:11:10.224560: Hello again!

为什么会出现上面的情况,两个时间戳一样。因为datetime.now只执行了一次。参数的默认值只会在系统加载这个模块的时候,计算一遍,而不会在每次执行时都重新计算,这通常意味着这些默认值在程序启动后,就已经定下来了。只要包含这段代码的那个模块已经加载进来,那么when参数的默认值就是加载时计算的那个datetime.now(),系统不会重新计算。

要想在Python里实现这种效果,惯用的办法是把参数的默认值设为None,同时在docstring文档里写清楚,这个参数为None时,函数会怎么运作。给函数写实现代码时,要判断该参数是不是None,如果是,就把它改成相应的默认值。

from time import sleep
from datetime import datetime

def log(message, when=None):
    """Log a message with a timestamp.
    :param message:message (str): The message to log.
    :param when:A datetime object representing the present time to log.
    :return:
    """
    if when is None:
        when = datetime.now()
    print(f'{when}: {message}')

这次,两条日志的时间戳就不同了。

log('Hi there!')
sleep(0.3)
log('Hello again!')
>>>
2024-05-23 20:26:10.020265: Hi there!
2024-05-23 20:26:10.325351: Hello again!

把参数的默认值写成None还有个重要的意义,就是用来表示那种以后可能由调用者修改内容的默认值(例如某个可变的容器)。例如,我们要写一个函数对采用JSON格式编码的数据做解码。如果无法解码,那么就返回调用时所指定的默认结果,假如调用者当时没有明确指定,那就返回空白的字典。

import json

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

这样的写法与前面datetime.now的例子有着同样的问题。系统只会计算一次的default参数(在加载这个模块的时候),所以每次调用这个函数时,给调用者返回的都是一开始分配的那个字典,这就是相当于凡是以默认值调用这个函数的代码都共用同一份字典。这会使程序出现很奇怪的效果。

foo = decode('bad data')
foo['stuff'] = 15
bar = decode('also bad')
bar['meep'] = 23
print('Foo:', foo)
print('Bar:', bar)

>>>
Foo: {
   'stuff': 15, 'meep': 23}
Bar: {
   'stuff': 15, 'meep': 23}

我们本意是想让这两次调用操作得到两个不同的空白字典,每个字典都可以分别用来存放不同的键值。但实际上,只要修改其中一个字典,另一个字典的内容就会受到影响。这种错误的根源在于,foo与bar实际上是同一个字典,都等于系统一开始给default参数确定默认值时所分配的那个字典。它们表示的同一个字典对象。

assert foo is bar

解决这个问题,可以把默认值设成None, 并且在docstring文档里说明,函数在这个值为None时会怎么做。

import json

def decode(data, default=None):
    """Load JSOn data from a string.
    :param data: JSON data to decode.
    :param default: Value to return if decoding fails.Defaults to an empty dictionary.
    :return:
    """
    try:
        return json.loads(data)
    except ValueError:
        if default is None:
            default = {
   }
        return default

这样写,再运行刚才那段测试代码,就可以得出预期的结果了。

foo = decode('{"bad": "data"')
foo['stuff'] = 15
bar = decode('also bad')
bar['meep'] = 23
print('Foo:', foo)
print('Bar:', bar)
assert foo is not bar

>>>
Foo: {
   'stuff': 15}
Bar: {
   'meep': 23}

这个思路不错吧?下面这种写法把when参数标注成可选(Optional)值,并限定其类型为datetime。于是,它的取值就只有两种可能,要么是None, 要么是datetime对象。

from datetime import datetime
from typing import Optional

def log_typed(message: str, when:Optional[datetime]=None) -> None:
    """Log a message with a timestamp.
    Parameters:
        message (str): The message to log.
        when (Optional[datetime]): datetime of when the message occurred.
        Defaults to the present time. 
    """
    if when is None:
        when = datetime.now()
    print(f'{when}: {message}')
相关文章
|
6月前
BUUCTF 文件中的秘密 1
BUUCTF 文件中的秘密 1
140 0
|
6月前
|
数据安全/隐私保护 Python
282: 数字的秘密
282: 数字的秘密
|
安全 Linux 网络安全
花无涯带你走进黑客世界13 揭秘NSA秘密黑客组织方程式
不论你什么心态,我决定还是带着善意科普一下,什么是“方程式”黑客组织? 泄露的工具包并不是 方程式 黑客组织 他们自己发布的,理解源头很重要。 不懂就要学不是吗,上一篇文章我才说了提问了艺术,今天又有人来表演该艺术了…
|
供应链 物联网 数据安全/隐私保护
优衣库 UNIQLO,藏着多少秘密
优衣库 UNIQLO,藏着多少秘密
|
程序员 定位技术 数据库
“404”,你所不知道的秘密?
“404”,你所不知道的秘密?
262 0
|
程序员 Go 数据安全/隐私保护
早恋与加密第一回: 古典加密
早恋与加密第一回: 古典加密