首页 > 编程语言 >【Effective Python教程】(90个有效方法)笔记——第3章:函数——24:用None和docstring来描述默认值会变的参数(函数默认参数值只在定义函数时计算)函数默认参数为None

【Effective Python教程】(90个有效方法)笔记——第3章:函数——24:用None和docstring来描述默认值会变的参数(函数默认参数值只在定义函数时计算)函数默认参数为None

时间:2024-06-30 23:29:22浏览次数:3  
标签:None 函数 when 默认 datetime 参数 默认值

文章目录


Effective Python——编写高质量Python代码的90个有效方法

本书介绍:https://effectivepython.com/

代码demo:https://effectivepython.com/

第3章:函数

在这里插入图片描述

第24条 用None和docstring来描述默认值会变的参数

函数默认值的坑(函数的默认参数值只在定义函数时计算)

有时,我们想把那种不能够提前固定的值,当作关键字参数的默认值。

例如,记录日志消息时,默认的时间应该是触发事件的那一刻。

所以,如果调用者没有明确指定时间那么就默认把调用函数的那一刻当成这条日志的记录时间。

现在试试下面这种写法,假定它能让 when 参数的默认值随着这个函数每次的执行时间而发生变化。

在这里插入图片描述
在这里插入图片描述

这样写不行。

因为 datetime.now只执行了一次,所以每条日志的时间戳(timestamp)相同。

参数的默认值只会在系统加载这个模块的时候,计算一遍,而不会在每次执行时都重新计算,这通常意味着这些默认值在程序启动后,就已经定下来了。

函数的默认参数值在定义函数时只计算一次

只要包含这段代码的那个模块已经加载进来,那么when 参数的默认值就是加载时计算的那个datetime.now(),系统不会重新计算。

解决方法:将函数默认参数值设为None,然后再在函数体中判断并初始化

要想在Python里实现这种效果,惯用的办法是把参数的默认值设为None,同时在docstring 文档里面写清楚,这个参数为None 时,函数会怎么运作(参见第84条)。

给函数写实现代码时,要判断该参数是不是None,如果是,就把它改成相应的默认值。

在这里插入图片描述

from time import sleep
from datetime import datetime

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

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

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

在这里插入图片描述
在这里插入图片描述

函数默认参数值设置为None的其他应用示例

把参数的默认值写成None还有个重要的意义,就是用来表示那种以后可能由调用者修改内容的默认值(例如某个可变的容器)。

例如,我们要写一个函数对采用JSON格式编码的数据做解码。

如果无法解码,那么就返回调用时所指定的默认结果,假如调用者当时没有明确指定,那就返回空白的字典。

在这里插入图片描述

这样的写法与前面 datetime.now 的例子有着同样的问题。

系统只会计算一次 default参数(在加载这个模块的时候),所以每次调用这个函数时,给调用者返回的都是一开始分配的那个字典,这就相当于凡是以默认值调用这个函数的代码都共用同一份字典。

这会使程序出现很奇怪的效果。

在这里插入图片描述

我们本意是想让这两次调用操作得到两个不同的空白字典,每个字典都可以分别用来存放不同的键值。

但实际上,只要修改其中一个字典,另一个字典的内容就会受到影响。这种错误的根源在于,foo和bar 实际上是同一个字典,都等于系统一开始给default 参数确定默认值时所分配的那个字典。

它们表示的是同一个字典对象。

在这里插入图片描述

import json


def decode(data, default={}):
    try:
        return json.loads(data)
    except ValueError:
        print(f'Caught a exception: [{ValueError}]')
        return default


foo = decode('bad data')
foo['stuff'] = 5
print('Foo:', foo)

bar = decode('also bad')
bar['meep'] = 1
print('Bar:', bar)

assert foo is bar

在这里插入图片描述

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

在这里插入图片描述
这样写,再运行刚才那段测试代码,就可以得出预期的结果了

在这里插入图片描述

import json


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

    Args:
        data: JSON data to decode.
        default: Value to return if decoding fails.
                 Defaults to an empty dictionary.
    """
    try:
        return json.loads(data)
    except ValueError:
        print(f'Caught a exception: [{ValueError}]')
        if default is None:
            default = {}
        return default


foo = decode('bad data')
foo['stuff'] = 5
print('Foo:', foo)

bar = decode('also bad')
bar['meep'] = 1
print('Bar:', bar)

assert foo is not bar

在这里插入图片描述

“函数默认参数值设置为None”与“函数参数类型注解”的结合应用

这个思路可以跟类型注解搭配起来(参见第90条)。

下面这种写法把when参数标注成可选(0ptional)值,并限定其类型为datetime。

于是,它的取值就只有两种可能,要么是None,要么是 datetime 对象。

在这里插入图片描述

from typing import Optional
from datetime import datetime


def log_typed(message: str, when: Optional[datetime] = None) -> None:
    """Log a message with a timestamp.

    Args:
        message: Message to print.
        when: datetime of when the message occurred.
              Defaults to the present time.
    """
    if when is None:
        when = datetime.now()
    print(f'{when}: {message}')

注意:when: Optional[datetime] = None也可以写成when: datetime | None = None,这种写法更加清晰,但是要python3.10之后才支持。参考文章:python函数注释 typing模块函数类型注解(函数注解)(指定函数参数和返回值类型)Union注解、Callable注解、Optional可变参数注解。类型提示、类型注解、参数注释(参数类型)

要点

1. 参数的默认值只会计算一次,也就是在系统把定义函数的那个模块加载进来的时候。所以,如果默认值将来可能由调用方修改(例如{}[])或者要随着调用时的情况变化(例如datetime.now()),那么程序就会出现奇怪的(贬义)效果。

2. 如果关键字参数的默认值属于这种会发生变化的值,那就应该写成None,并且要在docstring 里面描述函数此时的默认行为。

3. 默认值为None的关键字参数,也可以添加类型注解。

标签:None,函数,when,默认,datetime,参数,默认值
From: https://blog.csdn.net/Dontla/article/details/139839658

相关文章

  • Linux---open和close函数
    open:这是对文件权限的说明。注意:返回上一个工作目录:cd-close函数:关闭文件注意:在对C语言代码进行了修改时,必须要都运行的文件重新编译,然后在重新运行。不然,输出的结果不会发生改变。今日标语“努力不一定成功,但不努力一定不会成功。”......
  • 关于函数指针和结构体一起的用法
    想到单片机中的中断处理不好多样化,一直通过函数指针传递,今天想通过函数指针实现多样化,在中断中放一个要执行的函数指针,在外面可以改变此指针指向的函数。配合结构体的使用,感受到面向对象中class的存在了。typedefint(*pFunc)(int);//定义一个函数指针类型intadd(inta){......
  • c语言malloc、calloc 和 realloc动态分配内存函数的区别
    c语言malloc、calloc和realloc动态分配内存函数的区别malloc、calloc和realloc是C语言中用于动态内存分配的三个重要函数,它们之间有一些关键的区别。以下是这三个函数的区别,以分点表示和归纳的形式进行解释:内存来源和初始化:malloc:在堆上分配指定大小的内存块,但不进行初始化......
  • C++ : 如何用C语言实现C++的虚函数机制?
    前言在 googletest的源码中,看到gtest-matchers.h中实现的MatcherBase 类自定义了一个VTable,这种设计实现了一种类似于C++虚函数的机制。C++中的虚函数机制实质上就是通过这种方式实现的,本文用c语言自定义虚函数表VTable实现了一下virtual的功能,来深刻理解其机制。我们通过创......
  • mysql默认存储引擎--innodb存储引擎(详解)
    官方解释:    InnoDB,是MySQL的数据库引擎之一,现为MySQL的默认存储引擎,为MySQLAB发布binary的标准之一。InnoDB由InnobaseOy公司所开发,2006年五月时由甲骨文公司并购。与传统的ISAM与MyISAM相比,InnoDB的最大特色就是支持了ACID兼容的事务(Transaction)功能,类似于Postgre......
  • NzN的C++之路--拷贝构造函数&&赋值运算符重载
    目录Part1拷贝构造函数一、概念二、特征Part2赋值运算符重载一、运算符重载二、赋值运算符重载三、前置++和后置++重载Part3const成员Part4 取地址及const取地址操作符重载 Part1拷贝构造函数一、概念        拷贝构造函数:只有单个形参,该形参......
  • Java开发者的神经网络进阶指南:深入探讨交叉熵损失函数
    前言今天来讲一下损失函数——交叉熵函数,什么是损失函数呢?大体就是真实与预测之间的差异,这个交叉熵(CrossEntropy)是Shannon信息论中一个重要概念,主要用于度量两个概率分布间的差异性信息。在信息论中,交叉熵是表示两个概率分布p,q的差异,其中p表示真实分布,q表示预测分布,那么\(......
  • 如何利用窗口函数实现精确排名计算?
    前言SQL语句中,聚合函数在统计业务数据结果时起到了重要作用,比如计算每个业务地区的业务总数、每个班级的学生平均分以及每个分类的最大值等。然而,今天小编将为大家介绍窗口函数,与聚合函数相比,它们也是一组函数,但在使用方法和适用场景上有所不同。在本章节中,我将重点介绍窗口函数......
  • isNaN 和 Number.isNaN 函数的区别?
    对于可以被Number转换为数值的值x,那么isNaN(x)就是false如果不可以被Number转换为数值的值y,那么isNaN(y)就是true//Number.isNaN与isNaN最的区别是,Number.isNaN不存在类型转换的行为。console.log(isNaN('测试'))//trueconsole.log(Number.isNaN('测试'))//false上面......
  • 【优化分配】粒子群算法求解机组负荷分配优化问题(目标函数:最优成本)【含Matlab源码 478
    ✅博主简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,Matlab项目合作可私信。......