Python函数参数定义中的这两个分隔符,还有人不知道吗?

简介: python 函数的参数定义想必大家应该是非常熟悉的,有两种:• 位置参数(positional argument):根据函数在参数列表中的位置传递给函数的参数。• 关键词参数(keyword argument):通过指定参数名称及其对应值传参的参数。

python 函数的参数定义想必大家应该是非常熟悉的,有两种:

  • 位置参数(positional argument):根据函数在参数列表中的位置传递给函数的参数。
  • 关键词参数(keyword argument):通过指定参数名称及其对应值传参的参数。
def foo(a, b=1, c=2):
    print(a, b, c)

这里的a是位置参数,b 、c是关键词参数。


但是,调用时,我们可以通过多种方式传参,貌似没有明确的位置(positional)或关键字(keyword)界限:

foo(1, 2, 3)
foo(1, b=2, c=3)
foo(a=1, b=2, c=3)
foo(a=1, c=3, b=2)
foo(c=3, a=1, b=2)

请注意:所有位置参数都必须首先出现,然后是关键字参数


在Python函数中,参数默认可以按位置(positional)或按关键字(keyword)传入,这意味着调用者可以基于参数的位置或名称来传递值。


然而,在某些情况下,我们可能需要对参数的传入方式进行限制,以确保函数调用的明确性和正确性。

  • 具体来说,我们可以将某些参数指定为仅位置参数(positional only),这意味着它们只能通过位置来传入,而不是keyword传参;
  • 同时,也可以将其他参数指定为仅关键字参数(keyword only),这要求它们必须通过关键字来指定。

首先,要怎么确定位置参数或关键字参数呢?


以下是确定这一点的简单经验法则:

  • 核心参数通常是位置参数。核心参数是什么?通常指函数运行所必需的参数。
  • 选项、标志和配置通常是关键字。这些参数通常不是函数的核心参数,而是修改函数的行为方式


这看起来有点抽象,所以让我们看一个具体的例子。以下是标准库中shutil模块中copyfile函数的函数签名。

def copyfile(src, dst, *, follow_symlinks=True)

顾名思义,此功能将文件从一个地方复制到另一个地方。因此,src和dst参数是函数的核心。


此外,还有一个标志follow_symlinks。

1720510278591.jpg

此标志不是函数操作的核心部分,但它改变了函数的行为方式。因此,此参数应为关键字参数。


这样的区分有助于避免参数的混淆,并提供了更精确的函数调用传参方式。


我们发现,copyfile函数签名中有个*参数,这又是什么呢?这将强制follow_symlinks只能关键字传参(keyword only)。

特殊参数

python3.8 之后,python 引入了一个新的函数定义语法,你可以使用Special parameters(/和*)将你函数的位置参数和关键字参数分开,即可以强制执行位置参数或强制关键字参数。

一图胜千言。


简而言之就是:

  • 位于斜杠/之前的参数被指定为仅位置参数(positional only),这意味着它们必须按照在函数定义中出现的顺序传入,不能使用关键字参数的形式。
  • 位于星号*之后的位置则被保留给仅关键字参数(keyword only),调用者必须使用关键字来指定这些参数。
  • 至于/和*之间的参数,它们遵循默认的行为,既可以通过位置传入,也可以通过关键字传入。
  • 当然,在函数的定义中,这二者你也可以只用一个,或者都不用,或者两个都用。

用法示例

Python 中的强制位置函数参数/的用法


所有/之前的参数都是positional-only参数,这意味着它们只能作为位置参数传递给函数,而不能作为关键字参数传递。


一般,核心参数,或在参数顺序比参数名称更重要的情况下,或者参数的名字本身没什么语义(开发者希望在未来可以随时修改这个参数的名字),例如在处理图像或执行几何计算时,此功能非常有用。通过使用仅位置参数,仅需确保以正确的顺序传参调用函数,从而提高代码的清晰度和可维护性。

def positional_only(a, b, /):
    print(a + b)
positional_only(1, 2)

对于上面这个函数而言,调用positional_only函数时,参数a、b只能是位置参数,即:positional_only(1, 2)执行正确。


而positional_only(1, b=2)和positional_only(a=1, b=2)将执行错误。

# 执行将报错:TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'b'
# positional_only(1, b=2)
# 执行将报错:TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'a, b'
# positional_only(a=1, b=2)

Python 中的强制关键字函数参数*的用法


所有*之后的参数都是keyword-only参数,它们只能作为关键字参数传递给函数,不能作为位置参数传递。


当您想要强制对某些参数(例如具有默认值的可选参数)使用关键字参数时,或者当您想要明确区分必需参数和可选参数时,又或者这些参数在未来的位置也随时可能发生变化(在前面加入新的参数之类),此功能非常有用。

def keyword_only(a, b, *, c):
    print(a + b + c)
keyword_only(1, 2, c=7)
keyword_only(1, b=2, c=7)
keyword_only(a=1, b=2, c=7)

对于上面这个函数而言,调用keyword_only函数时,强制参数c只能是关键字(keyword-only)参数传参;a、b不做限制,属于positional or keyword传参。


/和*都出现在函数参数中

def f(a, /, b, *, c):
    print(a + b + c)
f(1, 2, c=7)  # 10
f(1, b=2, c=7)  # 10
# f(a=1, b=2, c=7)  # wrong, TypeError: f() got some positional-only arguments passed as keyword arguments: 'a'

从上面可知,强制a是positional_only参数,b(不做限制)是positional or keyword参数,强制c属于keyword-only参数。


内置函数中的强制位置参数和强制关键字参数


此外,这种语法也在 Python 内置函数的定义和文档说明中得到了应用,体现了其在官方实践中的规范性和重要性。如我们熟知的exec内置函数的函数签名:

1720510367432.jpg

exec(object, globals=None, locals=None, /, *, closure=None)
 Python

对于此函数,参数object和globals、locals必须作为位置传递,仅因为它们出现在/的左侧。另一方面,closure是一个仅关键字参数,因为它出现在*的右侧。

def keyword_only(*, a, b, c):
    print(a + b + c)
exec("keyword_only(a=1, b=2, c=7)")

关于函数签名, exec需要注意的一件有趣的事情是globals和locals具有默认值。因此,传递这些选项不是强制性的。但是,如果您确实为这些参数传入了值,那么它必须是位置的。显然,python 核心开发人员认为这两个参数是exec函数的核心部分。


再比如sorted函数:

1720510398782.jpg

sorted(iterable, /, *, key=None, reverse=False)

iterable必须以positional-only的形式传入,而两个可选参数key和reverse,若传必须以keyword-only的形式传入。

data = {'apple': 2, 'orange': 4, 'banana': 1}
# 按字典的值排序
sorted_data_by_value = sorted(data.items(), key=lambda item: item[1], reverse=True)
print(sorted_data_by_value)  # 输出: [('orange', 4), ('apple', 2), ('banana', 1)]

小结

要指定函数调用者仅能使用位置参数,您可以将这些参数放置在斜杠/之前。这样,调用者在传递这些参数时必须按照它们在函数定义中出现的顺序,而不能使用关键字参数。


相反,如果您希望确保调用者在使用函数时必须显式地指定某些参数,您可以将这些参数放置在星号*之后。这要求调用者在调用函数时使用关键字参数来传递这些值,从而避免了依赖于参数的位置,提高了代码的可读性和明确性。


在设计函数时,如果您面临需要明确区分参数传入方式的语义需求,不妨考虑采用这两个分隔符/和 *。这种明确的参数定义不仅有助于减少因参数误用而导致的错误,还能增强程序的健壮性和可靠性,从而提升整体的代码质量和维护性。

相关文章
|
27天前
|
搜索推荐 Python
利用Python内置函数实现的冒泡排序算法
在上述代码中,`bubble_sort` 函数接受一个列表 `arr` 作为输入。通过两层循环,外层循环控制排序的轮数,内层循环用于比较相邻的元素并进行交换。如果前一个元素大于后一个元素,就将它们交换位置。
126 67
|
20天前
|
Python
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
Python中的函数是**一种命名的代码块,用于执行特定任务或计算
44 18
|
12天前
|
数据可视化 DataX Python
Seaborn 教程-绘图函数
Seaborn 教程-绘图函数
41 8
|
19天前
|
Shell Python
[oeasy]python049_[词根溯源]locals_现在都定义了哪些变量
本文介绍了Python中`locals()`函数的使用方法及其在调试中的作用。通过回顾变量赋值、连等赋值、解包赋值等内容,文章详细解释了如何利用`locals()`函数查看当前作用域内的本地变量,并探讨了变量声明前后以及导入模块对本地变量的影响。最后,文章还涉及了一些与“local”相关的英语词汇,如`locate`、`allocate`等,帮助读者更好地理解“本地”概念在编程及日常生活中的应用。
29 9
|
21天前
|
Python
Python中的函数
Python中的函数
34 8
|
28天前
|
监控 测试技术 数据库
Python中的装饰器:解锁函数增强的魔法####
本文深入探讨了Python语言中一个既强大又灵活的特性——装饰器(Decorator),它以一种优雅的方式实现了函数功能的扩展与增强。不同于传统的代码复用机制,装饰器通过高阶函数的形式,为开发者提供了在不修改原函数源代码的前提下,动态添加新功能的能力。我们将从装饰器的基本概念入手,逐步解析其工作原理,并通过一系列实例展示如何利用装饰器进行日志记录、性能测试、事务处理等常见任务,最终揭示装饰器在提升代码可读性、维护性和功能性方面的独特价值。 ####
|
1月前
|
Python
Python中的`range`函数与负增长
在Python中,`range`函数用于生成整数序列,支持正向和负向增长。本文详细介绍了如何使用`range`生成负增长的整数序列,并提供了多个实际应用示例,如反向遍历列表、生成倒计时和计算递减等差数列的和。通过这些示例,读者可以更好地掌握`range`函数的使用方法。
53 5
|
1月前
|
中间件 Docker Python
【Azure Function】FTP上传了Python Function文件后,无法在门户页面加载函数的问题
通过FTP上传Python Function至Azure云后,出现函数列表无法加载的问题。经排查,发现是由于`requirements.txt`中的依赖包未被正确安装。解决方法为:在本地安装依赖包到`.python_packages/lib/site-packages`目录,再将该目录内容上传至云上的`wwwroot`目录,并重启应用。最终成功加载函数列表。
|
7月前
|
算法 Python 容器
Python编程 - 不调用相关choose库函数,“众数“挑选器、随机挑选器 的源码编程实现
Python编程 - 不调用相关choose库函数,“众数“挑选器、随机挑选器 的源码编程实现
96 0
|
3月前
|
算法 Python
Python编程的函数—内置函数
Python编程的函数—内置函数
22 0