请问在Python函数中的字符串会影响到函数形参吗

我在学习Python文档的Python教程中的4.8.3.4. 函数示例时,遇到了如下情况:

下面的函数定义中,kwdsname 当作键,因此,可能与位置参数 name 产生潜在冲突:

def foo(name, **kwds):
    return 'name' in kwds

调用该函数不可能返回 True,因为关键字 'name' 总与第一个形参绑定。例如:

>>>foo(1, **{'name': 2})
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>

加上 / (仅限位置参数)后,就可以了。此时,函数定义把 name 当作位置参数,'name' 也可以作为关键字参数的键:

>>>def foo(name, /, **kwds):
...return 'name' in kwds
...
>>>foo(1, **{'name': 2})
True

换句话说,仅限位置形参的名称可以在 **kwds 中使用,而不产生歧义。

我主要不理解这一段:

下面的函数定义中,kwdsname 当作键,因此,可能与位置参数 name 产生潜在冲突:

def foo(name, **kwds):
    return 'name' in kwds

为何在这里函数中的字符串会影响到函数形参?
而又为何在加上 / (仅限位置参数)的限制后,就允许name 当作位置参数,’name’ 也可以作为关键字参数的键了?

最佳答案

首先, def foo(*args)def foo(**kwds) 这个是python的可变参数写法,args 表示传入的多个位置参数, kwds 表示传入的多个关键字参数,正常它是这么用的 :

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
logger.addHandler(handler)
def test_1(*args):
    for key in range(len(args)):
        logger.debug(test_1.__name__ + ': key is ' + str(key) + ", val is " + str(args[key]))

test_1(0, 1, 2, 3, 4)
# test_1: key is 0, val is 0
# test_1: key is 1, val is 1
# test_1: key is 2, val is 2
# test_1: key is 3, val is 3
# test_1: key is 4, val is 4

def  test_1_1(arg_1, arg_2, arg_3):
    logger.debug(test_1_1.__name__ + ': arg_1 is ' + str(arg_1) + ' arg_2 is ' + str(arg_2) + ' arg_3 is ' + str(arg_3))

test_1_1(*(1, 2, 3))
# test_1_1: arg_1 is 1 arg_2 is 2 arg_3 is 3

def test_2(**kwds):
    for key in kwds:
        logger.debug(test_1.__name__ + ': key is ' + str(key) + ", val is " + str(kwds[key]))

test_2(a='a', b='b', c='c', d='d')
# test_2: key is a, val is a
# test_2: key is b, val is b
# test_2: key is c, val is c
# test_2: key is d, val is d

def  test_2_1(kwd_1, kwd_2, kwd_3):
    logger.debug(test_2_1.__name__ + ': kwd_1 is ' + str(kwd_1) + ' kwd_2 is ' + str(kwd_2) + ' kwd_3 is ' + str(kwd_3))

test_2_1(**{'kwd_1':1, 'kwd_2':2, 'kwd_3':3})
# test_2_1: kwd_1 is 1 kwd_2 is 2 kwd_3 is 3

然后 / 是什么意思呢? 通常, python 参数既可以按参数位置传, 也可以按参数名字传. 但是如果加了 / 那么 / 前面的参数就只能按位置传,比如下面这样:

def test_3(arg_1, /, arg_2, arg_3):
    logger.debug(test_3.__name__ + ': arg_1 is ' + str(arg_1) + ' arg_2 is ' + str(arg_2) + ' arg_3 is ' + str(arg_3))
test_3(1, 2, arg_3 = 3)
# test_3: arg_1 is 1 arg_2 is 2 arg_3 is 3

test_3(1, arg_2 = 2, arg_3 = 3)
# arg_1 is 1 arg_2 is 2 arg_3 is 3

test_3(1, arg_3 = 3, arg_2 = 2)
# arg_1 is 1 arg_2 is 2 arg_3 is 3

try:
    test_3(1, 2, arg_2=3)
except Exception as error:
    logger.debug(error)
# test_3() got multiple values for argument 'arg_2'

try:
    test_3(arg_1=1, arg_2=2, arg_3=3)
except Exception as error:
    logger.debug(error) 
# test_3() got some positional-only arguments passed as keyword arguments: 'arg_1'

然后楼主提的问题中, 如果第一个位置参数是 nane, 然后可变参数又传一个 name。 那么相当于调用的时候传了两个 name, 它就会冲突。 之所以这样,是因为这个 name 它既可以是位置参数, 也可以是关键字参数。 那么我们在后面加一个 / 说明 name 只能按位置传,它就不会起冲突了:

def test_4(name, **kwds):
    logger.debug(test_4.__name__ + ': name is ' + str(name))

try:
    test_4('a', name='b')
except Exception as error:
    logger.debug(error) 
# test_4() got multiple values for argument 'name'

def test_5(name, / ,**kwds):
    logger.debug(test_5.__name__ + ': name is ' + str(name))

test_5('a', name='b')
# test_5: name is a

然后正常来说, 为了代码的可读性, 写方法的时候是应该明确的定义方法的参数。 可变参数一般是用来写 decorator 的, 就是修改方法的方法, 比如装饰器。 如果不好理解的话可以看一下下面的例子。

def reverse_order(func):
    def wrapper(*args):
        return func(*args[::-1])
    return wrapper

@reverse_order
def test_6(*args):
    for key in range(len(args)):
        logger.debug(test_6.__name__ + ': key is ' + str(key) + ", val is " + str(args[key]))

test_6(0, 1, 2, 3, 4)
# wrapper: key is 0, val is 4
# wrapper: key is 1, val is 3
# wrapper: key is 2, val is 2
# wrapper: key is 3, val is 1
# wrapper: key is 4, val is 0

可以看到, 正常 test_6 是按照位置顺序打印它的所有参数的。 我们定义了一个装饰器修改了 test_6 的行为,使其按照和原来相反的顺序打印参数。

如果解决了你的问题, 给我点个赞, 加点点声望, 感谢 ~

9个月前 评论
讨论数量: 2

首先, def foo(*args)def foo(**kwds) 这个是python的可变参数写法,args 表示传入的多个位置参数, kwds 表示传入的多个关键字参数,正常它是这么用的 :

import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
logger.addHandler(handler)
def test_1(*args):
    for key in range(len(args)):
        logger.debug(test_1.__name__ + ': key is ' + str(key) + ", val is " + str(args[key]))

test_1(0, 1, 2, 3, 4)
# test_1: key is 0, val is 0
# test_1: key is 1, val is 1
# test_1: key is 2, val is 2
# test_1: key is 3, val is 3
# test_1: key is 4, val is 4

def  test_1_1(arg_1, arg_2, arg_3):
    logger.debug(test_1_1.__name__ + ': arg_1 is ' + str(arg_1) + ' arg_2 is ' + str(arg_2) + ' arg_3 is ' + str(arg_3))

test_1_1(*(1, 2, 3))
# test_1_1: arg_1 is 1 arg_2 is 2 arg_3 is 3

def test_2(**kwds):
    for key in kwds:
        logger.debug(test_1.__name__ + ': key is ' + str(key) + ", val is " + str(kwds[key]))

test_2(a='a', b='b', c='c', d='d')
# test_2: key is a, val is a
# test_2: key is b, val is b
# test_2: key is c, val is c
# test_2: key is d, val is d

def  test_2_1(kwd_1, kwd_2, kwd_3):
    logger.debug(test_2_1.__name__ + ': kwd_1 is ' + str(kwd_1) + ' kwd_2 is ' + str(kwd_2) + ' kwd_3 is ' + str(kwd_3))

test_2_1(**{'kwd_1':1, 'kwd_2':2, 'kwd_3':3})
# test_2_1: kwd_1 is 1 kwd_2 is 2 kwd_3 is 3

然后 / 是什么意思呢? 通常, python 参数既可以按参数位置传, 也可以按参数名字传. 但是如果加了 / 那么 / 前面的参数就只能按位置传,比如下面这样:

def test_3(arg_1, /, arg_2, arg_3):
    logger.debug(test_3.__name__ + ': arg_1 is ' + str(arg_1) + ' arg_2 is ' + str(arg_2) + ' arg_3 is ' + str(arg_3))
test_3(1, 2, arg_3 = 3)
# test_3: arg_1 is 1 arg_2 is 2 arg_3 is 3

test_3(1, arg_2 = 2, arg_3 = 3)
# arg_1 is 1 arg_2 is 2 arg_3 is 3

test_3(1, arg_3 = 3, arg_2 = 2)
# arg_1 is 1 arg_2 is 2 arg_3 is 3

try:
    test_3(1, 2, arg_2=3)
except Exception as error:
    logger.debug(error)
# test_3() got multiple values for argument 'arg_2'

try:
    test_3(arg_1=1, arg_2=2, arg_3=3)
except Exception as error:
    logger.debug(error) 
# test_3() got some positional-only arguments passed as keyword arguments: 'arg_1'

然后楼主提的问题中, 如果第一个位置参数是 nane, 然后可变参数又传一个 name。 那么相当于调用的时候传了两个 name, 它就会冲突。 之所以这样,是因为这个 name 它既可以是位置参数, 也可以是关键字参数。 那么我们在后面加一个 / 说明 name 只能按位置传,它就不会起冲突了:

def test_4(name, **kwds):
    logger.debug(test_4.__name__ + ': name is ' + str(name))

try:
    test_4('a', name='b')
except Exception as error:
    logger.debug(error) 
# test_4() got multiple values for argument 'name'

def test_5(name, / ,**kwds):
    logger.debug(test_5.__name__ + ': name is ' + str(name))

test_5('a', name='b')
# test_5: name is a

然后正常来说, 为了代码的可读性, 写方法的时候是应该明确的定义方法的参数。 可变参数一般是用来写 decorator 的, 就是修改方法的方法, 比如装饰器。 如果不好理解的话可以看一下下面的例子。

def reverse_order(func):
    def wrapper(*args):
        return func(*args[::-1])
    return wrapper

@reverse_order
def test_6(*args):
    for key in range(len(args)):
        logger.debug(test_6.__name__ + ': key is ' + str(key) + ", val is " + str(args[key]))

test_6(0, 1, 2, 3, 4)
# wrapper: key is 0, val is 4
# wrapper: key is 1, val is 3
# wrapper: key is 2, val is 2
# wrapper: key is 3, val is 1
# wrapper: key is 4, val is 0

可以看到, 正常 test_6 是按照位置顺序打印它的所有参数的。 我们定义了一个装饰器修改了 test_6 的行为,使其按照和原来相反的顺序打印参数。

如果解决了你的问题, 给我点个赞, 加点点声望, 感谢 ~

9个月前 评论

对了, 还有一个小细节, 就是python的参数传递传递的是引用传递而非值传递,需要注意一下。

分享:关于 Python函数参数的一些小细节

9个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!