Python 中的键值(具名)参数:如何使用它们

2023-10-29

键值参数是 Python 的一个特性,对于从其他编程语言转到 Python 的人来说,不免看起来有些奇怪。人们在学习 Python 的时候,经常要花很长时间才能理解键值参数的各种特性。

在 Python 教学中,我经常希望我能三言两语就把键值参数丰富的相关特性讲清楚。但愿这篇文章能够达到这个效果。

在这篇文章中我会解释键值参数是什么和为什么要用到它。随后我会细数一些更为深入的使用技巧,就算老 Python 程序员也可能会忽略,因为 Python 3 的最近一些版本变动了许多东西。如果你已经是一个资深的 Python 程序员,你可以直接跳到结尾。

什么是键值参数?
让我们来看看到底什么是键值参数(也叫做具名参数)。

先看看下面这个 Python 函数:

from math import sqrt

def quadratic(a, b, c):
x1 = -b / (2a)
x2 = sqrt(b**2 - 4
ac) / (2a)
return (x1 + x2), (x1 - x2)
当我们调用这个函数时,我们有两种不同的方式来传递这三个参数。

我们可以像这样以占位参数的形式传值:

quadratic(31, 93, 62)
(-1.0, -2.0)
或者像这样以键值参数的形式:

quadratic(a=31, b=93, c=62)
(-1.0, -2.0)
当用占位方式传值时,参数的顺序至关重要:

quadratic(31, 93, 62)
(-1.0, -2.0)

quadratic(62, 93, 31)
(-0.5, -1.0)
但是加上参数名就没关系了:

quadratic(a=31, b=93, c=62)
(-1.0, -2.0)

quadratic(c=62, b=93, a=31)
(-1.0, -2.0)
当我们使用键值/具名参数时,有意义的是参数的名字,而不是它的位置:

quadratic(a=31, b=93, c=62)
(-1.0, -2.0)

quadratic(c=31, b=93, a=62)
(-0.5, -1.0)
所以不像许多其它的编程语言,Python 知晓函数接收的参数名称。

如果我们使用帮助函数,Python 会把三个参数的名字告诉我们:

help(quadratic)
Help on function quadratic in module main:

quadratic(a, b, c)
注意,可以通过占位和具名混合的方式来调用函数:

quadratic(31, 93, c=62)
(-1.0, -2.0)
这样确实很方便,但像我们写的这个函数使用全占位参数或全键值参数会更清晰。

为什么要使用键值参数?
在 Python 中调用函数的时候,你通常要在键值参数和占位参数之间二者择一。使用键值参数可以使函数调用更加明确。

看看这段代码:

def write_gzip_file(output_file, contents):
with GzipFile(None, ‘wt’, 9, output_file) as gzip_out:
gzip_out.write(contents)
这个函数接收一个 output_file 文件对象和 contents 字符串,然后把一个经过 gzip 压缩的字符串写入输出文件。

下面这段代码做了相同的事,只是用键值参数代替了占位参数:

def write_gzip_file(output_file, contents):
with GzipFile(fileobj=output_file, mode=‘wt’, compresslevel=9) as gzip_out:
gzip_out.write(contents)
可以看到使用键值参数调用这种方式可以更清楚地看出这三个参数的意义。

我们在这里去掉了一个参数。第一个参数代表 filename,并且有一个 None 的默认值。这里我们不需要 filename,因为我们应该只传一个文件对象或者只传一个文件名给 GzipFile,而不是两者都传。

我们还能再去掉一个参数。

还是原来的代码,不过这次压缩率被去掉了,以默认的 9 代替:

def write_gzip_file(output_file, contents):
with GzipFile(fileobj=output_file, mode=‘wt’) as gzip_out:
gzip_out.write(contents)
因为使用了具名参数,我们得以去掉两个参数,并把余下 2 个参数以合理的顺序排列(文件对象比『wt』获取模式更重要)。

当我们使用键值参数时:

我们可以去除有默认值的参数
我们可以以一种更为可读的方式将参数重新排列
通过名称调用参数更容易理解参数的含义
哪里能看到键值函数
你可以在 Python 中的很多地方看到键值参数。

Python 有一些接收无限量的占位参数的函数。这些函数有时可以接收用来定制功能的参数。这些参数必须使用具名参数,与无限量的占位参数区分开来。

内置的 print 函数的可选属性 sep、end、file 和 flush,只能接收键值参数:

print(‘comma’, ‘separated’, ‘words’, sep=’, ')
comma, separated, words
itertools.zip_longest 函数的 fillvalue 属性(默认为 None),同样只接收键值参数:

from itertools import zip_longest
list(zip_longest([1, 2], [7, 8, 9], [4, 5], fillvalue=0))
[(1, 7, 4), (2, 8, 5), (0, 9, 0)]
事实上,一些 Python 中的函数强制参数被具名,尽管以占位方式可以清楚地指定。

在 Python 2 中,sorted 函数可以以占位或键值的方式接收参数:

sorted([4, 1, 8, 2, 7], None, None, True)
[8, 7, 4, 2, 1]

sorted([4, 1, 8, 2, 7], reverse=True)
[8, 7, 4, 2, 1]
但是 Python 3 中的 sorted 要求迭代器之后的所有参数都以键值的形式指定:

sorted([4, 1, 8, 2, 7], None, True)
Traceback (most recent call last):
File “”, line 1, in
TypeError: must use keyword argument for key function

sorted([4, 1, 8, 2, 7], reverse=True)
[8, 7, 4, 2, 1]
不仅仅是 Python 的内置函数,标准库和第三方库中键值参数同样很常见。

使你的参数具名
通过使用 * 操作符来匹配所有占位参数然后在 * 之后指定可选的键值参数,你可以创建一个接收任意数量的占位参数和特定数量的键值参数的函数。

这儿有个例子:

def product(*numbers, initial=1):
total = initial
for n in numbers:
total *= n
return total
注意:如果你之前没有看过 * 的语法,*numbers 会把所有输入 product 函数的占位参数放到一个 numbers 变量指向的元组。

上面这个函数中的 initial 参数必须以键值形式指定:

product(4, 4)
16

product(4, 4, initial=1)
16

product(4, 5, 2, initial=3)
120
注意 initial 有一个默认值。你也可以用这种语法指定必需的键值参数:

def join(*iterables, joiner):
if not iterables:
return
yield from iterables[0]
for iterable in iterables[1:]:
yield joiner
yield from iterable
joiner 变量没有默认值,所以它必须被指定:

list(join([1, 2, 3], [4, 5], [6, 7], joiner=0))
[1, 2, 3, 0, 4, 5, 0, 6, 7]

list(join([1, 2, 3], [4, 5], [6, 7], joiner=’-’))
[1, 2, 3, ‘-’, 4, 5, ‘-’, 6, 7]

list(join([1, 2, 3], [4, 5], [6, 7]))
Traceback (most recent call last):
File “”, line 1, in
TypeError: join() missing 1 required keyword-only argument: ‘joiner’
需要注意的是这种把参数放在 * 后面的语法只在 Python 3 中有效。Python 2 中没有要求参数必须要被命名的语法。

只接收键值参数而不接收占位参数
如果你想只接收键值参数而不接收任何占位参数呢?

如果你想接收一个键值参数,并且不打算接收任何 * 占位参数,你可以在 * 后面不带任何字符。

比如这儿有一个修改过的 Django 的 django.shortcuts.render 函数:

def render(request, template_name, context=None, *, content_type=None, status=None, using=None):
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
与 Django 现在的 render 函数实现不一样,这个版本不允许以所有参数都以占位方式指定的方式来调用 render。context_type、status 和 using 参数必须通过名称来指定。

render(request, ‘500.html’, {‘error’: error}, status=500)
<HttpResponse status_code=500, “text/html; charset=utf-8”>

render(request, ‘500.html’, {‘error’: error}, 500)
Traceback (most recent call last):
File “”, line 1, in
TypeError: render() takes from 2 to 3 positional arguments but 4 were given
就像带有无限制占位参数时的情况一样,这些键值参数也可以是必需的。这里有一个函数,有四个必需的键值参数:

from random import choice, shuffle
UPPERCASE = “ABCDEFGHIJKLMNOPQRSTUVWXYZ”
LOWERCASE = UPPERCASE.lower()
DIGITS = “0123456789”
ALL = UPPERCASE + LOWERCASE + DIGITS

def random_password(*, upper, lower, digits, length):
chars = [
*(choice(UPPERCASE) for _ in range(upper)),
*(choice(LOWERCASE) for _ in range(lower)),
*(choice(DIGITS) for _ in range(digits)),
*(choice(ALL) for _ in range(length-upper-lower-digits)),
]
shuffle(chars)
return “”.join(chars)
这个函数要求所有函数都必须以名称指定:

random_password(upper=1, lower=1, digits=1, length=8)
‘oNA7rYWI’

random_password(upper=1, lower=1, digits=1, length=8)
‘bjonpuM6’

random_password(1, 1, 1, 8)
Traceback (most recent call last):
File “”, line 1, in
TypeError: random_password() takes 0 positional arguments but 4 were given
要求参数具名可以使函数的调用更加清楚明白。

这样调用函数的意图:

password = random_password(upper=1, lower=1, digits=1, length=8)
要比这样调用更为清楚:

password = random_password(1, 1, 1, 8)
再强调一次,这种语法只在 Python 3 中适用。

匹配通配键值参数
怎样写出一个匹配任意数量键值参数的函数?

举个例子,字符串格式化方法接收你传递给它的任意键值参数:

“My name is {name} and I like {color}”.format(name=“Trey”, color=“purple”)
‘My name is Trey and I like purple’
怎么样才能写出这样的函数?

Python 允许函数匹配任意输入的键值参数,通过在定义函数的时候使用 ** 操作符:

def format_attributes(**attributes):
“”“Return a string of comma-separated key-value pairs.”""
return “, “.join(
f”{param}: {value}”
for param, value in attributes.items()
)
** 操作符允许 format_attributes 函数接收任意数量的键值参数。输入的参数会被存在一个叫 attributes 的字典里面。

这是我们的函数的使用示例:

format_attributes(name="Trey", website="http://treyhunner.com", color="purple")
'name: Trey, website: http://treyhunner.com, color: purple'

用通配键值参数调用函数
就像你可以定义函数接收通配键值参数一样,你也可以在调用函数时传入通配键值参数。

这就意味着你可以基于字典中的项向函数传递键值参数。

这里我们从一个字典中手动提取键/值对,并把它们以键值参数的形式传入函数中:

items = {‘name’: “Trey”, ‘website’: “http://treyhunner.com”, ‘color’: “purple”}
format_attributes(name=items[‘name’], website=items[‘website’], color=items[‘color’])
‘name: Trey, website: http://treyhunner.com, color: purple’
这种在代码函数调用时将代码写死的方式需要我们在写下代码的时候就知道所使用的字典中的每一个键。当我们不知道字典中的键时,这种方法就不奏效了。

我们可以通过 ** 操作符将字典中的项拆解成函数调用时的键值参数,来向函数传递通配键值参数:

items = {‘name’: “Trey”, ‘website’: “http://treyhunner.com”, ‘color’: “purple”}
format_attributes(**items)
‘name: Trey, website: http://treyhunner.com, color: purple’
这种向函数传递通配键值参数和在函数内接收通配键值参数(就像我们之前做的那样)的做法在使用类继承时尤为常见:

def my_method(self, *args, **kwargs):
print(‘Do something interesting here’)
super().my_method(*args, **kwargs) # 使用传入的参数调用父类的方法
注意:同样地我们可以使用 * 操作符来匹配和拆解占位参数。

顺序敏感性
自 Python 3.6 起,函数将会保持键值参数传入的顺序(参见 PEP 468)。这意味着当使用 ** 来匹配键值参数时,用来储存结果的字典的键将会与传入参数拥有同样的顺序。

所以在 Python 3.6 之后,你将不会再看到这样的情况:

format_attributes(name=“Trey”, website=“http://treyhunner.com”, color=“purple”)
‘website: http://treyhunner.com, color: purple, name: Trey’
相应地,使用 Python 3.6+,参数会永远保持传入的顺序:

format_attributes(name=“Trey”, website=“http://treyhunner.com”, color=“purple”)
‘name: Trey, website: http://treyhunner.com, color: purple’
概括 Python 中的键值参数
一个参数的位置传达出来的信息通常不如名称有效。因此在调用函数时,如果能使它的意义更清楚,考虑为你的参数赋名。

定义一个新的函数时,不要再考虑哪个参数应该被指定为键值参数了。使用 * 操作符把这些参数都指定成键值参数。

牢记你可以使用 ** 操作符来接受和传递通配键值参数。

重要的对象应该要有名字,你可以使用键值参数来给你的对象赋名!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 中的键值(具名)参数:如何使用它们 的相关文章

  • Django:按钮链接

    我是一名 Django 新手用户 尝试创建一个按钮 单击该按钮会链接到我网站中的另一个页面 我尝试了一些不同的例子 但似乎没有一个对我有用 举个例子 为什么这不起作用
  • python 相当于 R 中的 get() (= 使用字符串检索符号的值)

    在 R 中 get s 函数检索名称存储在字符变量 向量 中的符号的值s e g X lt 10 r lt XVI s lt substr r 1 1 X get s 10 取罗马数字的第一个符号r并将其转换为其等效整数 尽管花了一些时间翻
  • 根据列值突出显示数据框中的行?

    假设我有这样的数据框 col1 col2 col3 col4 0 A A 1 pass 2 1 A A 2 pass 4 2 A A 1 fail 4 3 A A 1 fail 5 4 A A 1 pass 3 5 A A 2 fail 2
  • 是否可以忽略一行的pyright检查?

    我需要忽略一行的pyright 检查 有什么特别的评论吗 def create slog group SLogGroup data Optional dict None SLog insert one SLog group group da
  • 以编程方式停止Python脚本的执行? [复制]

    这个问题在这里已经有答案了 是否可以使用命令在任意行停止执行 python 脚本 Like some code quit quit at this point some more code that s not executed sys e
  • Python pickle:腌制对象不等于源对象

    我认为这是预期的行为 但想检查一下 也许找出原因 因为我所做的研究结果是空白 我有一个函数可以提取数据 创建自定义类的新实例 然后将其附加到列表中 该类仅包含变量 然后 我使用协议 2 作为二进制文件将该列表腌制到文件中 稍后我重新运行脚本
  • 如何加速Python中的N维区间树?

    考虑以下问题 给定一组n间隔和一组m浮点数 对于每个浮点数 确定包含该浮点数的区间子集 这个问题已经通过构建一个解决区间树 https en wikipedia org wiki Interval tree 或称为范围树或线段树 已经针对一
  • 如何使用 OpencV 从 Firebase 读取图像?

    有没有使用 OpenCV 从 Firebase 读取图像的想法 或者我必须先下载图片 然后从本地文件夹执行 cv imread 功能 有什么办法我可以使用cv imread link of picture from firebase 您可以
  • Python 的“zip”内置函数的 Ruby 等价物是什么?

    Ruby 是否有与 Python 内置函数等效的东西zip功能 如果不是 做同样事情的简洁方法是什么 一些背景信息 当我试图找到一种干净的方法来进行涉及两个数组的检查时 出现了这个问题 如果我有zip 我可以写这样的东西 zip a b a
  • Pygame:有没有简单的方法可以找到按下的任何字母数字的字母/数字?

    我目前正在开发的游戏需要让人们以自己的名义在高分板上计时 我对如何处理按键有点熟悉 但我只处理过寻找特定的按键 有没有一种简单的方法可以按下任意键的字母 而不必执行以下操作 for event in pygame event get if
  • 在f字符串中转义字符[重复]

    这个问题在这里已经有答案了 我遇到了以下问题f string gt gt gt a hello how to print hello gt gt gt f a a gt gt gt f a File
  • 如何在Python中对类别进行加权随机抽样

    给定一个元组列表 其中每个元组都包含一个概率和一个项目 我想根据其概率对项目进行采样 例如 给出列表 3 a 4 b 3 c 我想在 40 的时间内对 b 进行采样 在 python 中执行此操作的规范方法是什么 我查看了 random 模
  • 为字典中的一个键附加多个值[重复]

    这个问题在这里已经有答案了 我是 python 新手 我有每年的年份和值列表 我想要做的是检查字典中是否已存在该年份 如果存在 则将该值附加到特定键的值列表中 例如 我有一个年份列表 并且每年都有一个值 2010 2 2009 4 1989
  • 解释 Python 中的数字范围

    在 Pylons Web 应用程序中 我需要获取一个字符串 例如 关于如何做到这一点有什么建议吗 我是 Python 新手 我还没有找到任何可以帮助解决此类问题的东西 该列表将是 1 2 3 45 46 48 49 50 51 77 使用
  • 有没有办法检测正在运行的代码是否正在上下文管理器内执行?

    正如标题所述 有没有办法做到这样的事情 def call back if called inside context print running in context else print called outside context 这将
  • 类型错误:预期单个张量时的张量列表 - 将 const 与 tf.random_normal 一起使用时

    我有以下 TensorFlow 代码 tf constant tf random normal time step batch size 1 1 我正进入 状态TypeError List of Tensors when single Te
  • 如何修改现有表以添加时区

    我有一个包含 500 多个表的大型应用程序 我必须将应用程序转换为时区感知 当前应用程序使用new java util Date GETDATE 与服务器的时区 即没有任何时区支持 我已将这项任务分为几个步骤 以便于开发 我确定的第一个步骤
  • 从列表指向字典变量

    假设你有一个清单 a 3 4 1 我想用这些信息来指向字典 b 3 4 1 现在 我需要的是一个常规 看到该值后 在 b 的位置内读写一个值 我不喜欢复制变量 我想直接改变变量b的内容 假设b是一个嵌套字典 你可以这样做 reduce di
  • Python 类继承 - 诡异的动作

    我观察到类继承有一个奇怪的效果 对于我正在处理的项目 我正在创建一个类来充当另一个模块的类的包装器 我正在使用第 3 方 aeidon 模块 用于操作字幕文件 但问题可能不太具体 以下是您通常如何使用该模块 project aeidon P
  • NotImplementedError:无法将符号张量 (lstm_2/strided_slice:0) 转换为 numpy 数组。时间

    张量流版本 2 3 1 numpy 版本 1 20 在代码下面 define model model Sequential model add LSTM 50 activation relu input shape n steps n fe

随机推荐

  • 交叉编译eigen3.2.10至ARM架构

    交叉编译eigen3 2 10至ARM架构 1 下载交叉编译链 PC机为x86架构 目标平台为ARM架构 首先需要安装x86至ARM平台的交叉编译链 需要注意的是 编译链上C库的版本需要和目标平台上的C库版本兼容 我起初参考其他博客直接ap
  • 运放分析--虚短与虚断

    虚短与虚断 1 虚短 如图1所示 虚短是指运放的输入端V 和V 可视为电压差很小 即近似相等 V V 由于并没有实际的物理连接 故我们称其为虚短 以区别物理连接的短路 若其中一端接地 则另一端在必要时 可认为虚地 2 虚断 由于运放是高阻抗
  • ScheduledThreadPoolExecutor 线程池例子

    ScheduledThreadPoolExecutor 线程池例子 一 ScheduledThreadPoolExecutor 使用 1 使用示例 提交任务 简单例子 二 ScheduledThreadPoolExecutor 原理 1 D
  • android状态栏一体化(沉浸式状态栏)

    Android 沉浸式状态栏 状态栏一体化 透明状态栏 仿ios透明状态栏 http blog csdn net jdsjlzx article details 50437779 注 状态栏的字体颜色位白色 如果状态栏背景为白色 上面的博客
  • Easyui入门(二)

    Easyui入门之Tree后台实现 tree的组件简介 案例1 运行结果 2 tree组件工具类的实现思路 预热 方案 代码 链接 代码2 正式从数据库拿数据写 代码 代码2 总结 tree的组件简介 静态的html方式 缺点 如果树形结构
  • C++中类的静态成员变量

    在C语言中 我们知道有static静态变量 生命周期与作用域都跟普通变量有所不同 而在C 的类中 也有静态成员变量同时还有静态成员函数 先来看看C 中静态成员变量与静态成员函数的语法 include
  • 润和软件推出HarmonyOS物联网系列模组Neptune,助力Harmony生态

    在2020 第十七届 中国物联网产业大会上 HarmonyOS首批官方合作伙伴润和软件宣布推出HarmonyOS智能硬件新品 支持HarmonyOS的物联网系列模组Neptune HH SLNPT10x 该系列模组使用的芯片由润和软件HiH
  • C语言,打印杨辉三角

    include
  • 【编译原理】三地址码

    三地址码 编译器构造 编译器的结构 中间语言 中间语言表达式 逆波兰 RPN 形式 图形 语义树 三地址码表达形式 四地址码表达形式 三地址码 三地址码 TAC 指令 三地址码的使用和特点 文字表 优化阶段 编译器构造 编译器的结构 语义检
  • powershell get-date计算指定日期及格式化

    get date format yyyyMMdd 获取当天日期并格式化为20200107的格式 get date UFormat V 获取当天是本年度的第几周 这里有一个bug 就是每周一获取到的还是上周 get date adddays
  • Ant-Design-Pro小试:react开发步骤(mock数据)

    1 router config js path train name train icon profile routes profile path train list name list component Train List 2 me
  • object-c万能解决bug思路

    有关运算符重载 C 支持运算符重载 但 Objective C 中不支持 然而 Objc 中可以看到下面的用法 id obj dict keyStr 它和 id obj dict objectForKey keyStr 等价 这里的 的用法
  • java综合技术分享

    1 心跳机制 1 1心跳包机制 跳包之所以叫心跳包是因为 它像心跳一样每隔固定时间发一次 以此来告诉服务器 这个客户端还活着 事实上这是为了保持长连接 至于这个包的内容 是没有什么特别规定的 不过一般都是很小的包 或者只包含包头的一个空包
  • stack queue free-lock implate

    https github com kayaklee libhalog blob master test clib hv sample lifo cpp https github com kayaklee libhalog blob mast
  • thrift源码解析之server

    文章目录 前言 概述 TSimpleServer serve 1 listen 2 accept 3 newlyConnectedClient TNonblockingServer serve 1 registerEvents 1 赋值us
  • Java中Thread类的基本用法

    目录 一 创建线程的方式 1 继承Thread类 2 实现Runnable接口 3 匿名内部类中创建Thread子类对象 4 匿名内部类中创建Runnable子类对象 5 lambda表达式创建Runnabl子类对象 二 Thread的常见
  • netty 系列之:java 中的 base64 编码器

    简介 什么是 Base64 编码呢 在回答这个问题之前 我们需要了解一下计算机中文件的分类 对于计算机来说文件可以分为两类 一类是文本文件 一类是二进制文件 对于二进制文件来说 其内容是用二进制来表示的 对于人类是不可立马理解的 如果你尝试
  • 驱动移植学习心得

    系统移植 把操作系统 Linux 能够在芯片 板子 上运行 目标 在开发板上运行操作系统 嵌入式系统 linux 以应用为中心 把软硬件进行裁剪 适用于应用的专用计算机系统 1 交叉编译环境搭建 开发主机 编译工具 针对开发板的编译工具 a
  • C语言库编译时添加编译时间和svn版本号

    1 功能应用背景和开发思路 1 应用背景 当程序出错时 我们想要知道库的版本信息 这样有助于定位错误是哪个版本引入的 缩小排查的范围 就算程序没有出错 能知道库的版本 也方便知道程序的版本 2 思路 在编译源码时 获取svn版本和编译时间并
  • Python 中的键值(具名)参数:如何使用它们

    键值参数是 Python 的一个特性 对于从其他编程语言转到 Python 的人来说 不免看起来有些奇怪 人们在学习 Python 的时候 经常要花很长时间才能理解键值参数的各种特性 在 Python 教学中 我经常希望我能三言两语就把键值