【问题标题】:python unpacking arg list with *args and **kwargs along with other positional and keyword only paramspython 使用 *args 和 **kwargs 以及其他仅位置和关键字参数解包 arg 列表
【发布时间】:2020-11-03 17:35:55
【问题描述】:

我在名为 kwargs.py 的文件中定义了以下函数。 (将模块更改为 unpacking.py 使该功能按预期工作)。请参阅问题底部的图片。

def tag(name, *content, cls=None, **attrs):
    if cls is not None:
        attrs['class'] = cls
    
    if attrs:
        attr_str = ''.join(' %s="%s"' % (key,val) 
                                         for key, val 
                                         in sorted(attrs.items()))
    else:
        attr_str = ''
    
    if content:
        return '\n'.join('<%s%s>%s</%s>' % (name,attr_str,c,name) for c in content)
    else:
        return '<%s%s />' % (name,attr_str)

执行时

tag('div', 'testing', cls='test', **dict(id=33,alt='this is a test'))

我得到的结果是

'<div alt="this is a test" class="test" id="33">testing</div>'

但是当我执行这个时

tag(**dict(name='div', content=('testing','and testing'), cls='test', id=33, alt='this is a test'))

我只得到

'<div alt="this is a test" class="test" id="33" />'

为什么会分配参数name 而不是content。 (即使元组没有解包,我也希望至少元组本身被分配给 content[0])。

我在这里错过了什么?

编辑: Windows 10 中的 Python 3.8.3 x86

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

    首先让我们确保我们使用相同的术语:

    • 一个函数声明了参数(在你的例子中是name, *content, cls=None, **attrs),
    • 函数调用接收参数(例如'div', 'testing', cls='test', **dict(id=33, alt='this is a test'))。

    然后将参数绑定到参数,因此可以在函数体中访问它们。

    首先我们看一下函数签名:

    def tag(name, *content, cls=None, **attrs):
        ...
    

    这个函数定义了四个参数:

    • name - 位置或关键字参数,
    • content - 可变参数参数,可捕获任意数量的附加 位置 参数,
    • cls - 具有默认值的仅限关键字的参数,
    • attrs - 一个关键字参数,可捕获任意数量的附加 keyword 参数。

    当您以下列方式调用此函数时,会发生以下情况:

    tag('div', 'testing', cls='test', **dict(id=33, alt='this is a test'))
    
    • 'div' 绑定到name
    • 'testing'content 捕获,生成一个 1 元组,
    • 'test' 绑定到cls
    • id=33, alt='this is a test'attrs 捕获。

    现在*content**attrs 参数的特殊之处在于它们捕获了任意数量的多余参数,但它们不能直接绑定。 IE。你不能绑定content=(1, 2)。相反,如果您传递tag('foo', 1, 2),则此绑定会自动发生。所以如果你以如下方式调用函数:

    tag(**dict(name='div', content=('testing', 'and testing'), cls='test', id=33, alt='this is a test'))
    

    然后所有参数都由关键字提供,因此除namecls 之外的所有参数都由attrs 捕获。这是因为*content 只捕获位置 参数。

    【讨论】:

    • 你说得对,我就是这么理解的。但问题是content=('testing', 'and testing') 没有分配给参数**kwargs。这就是促使我发布这个问题的原因。接受的答案告诉我更改我的模块名称,因为我将其命名为“kwargs.py”。将名称更改为“unpacking.py”后,我发布的相同示例按预期工作。
    • 如果您能解释为什么模块名称会导致此问题,我们将不胜感激。事实上,模块名称并没有导致整个 kwargs 参数不可用。它只屏蔽了元组参数(与变量参数 param 同名)被映射。如果名称是参数字典中的其他名称,它会起作用。
    • @ThirumalaiParthasarathi 在您的问题中,我没有看到任何提及此模块kwargs.py 并且tag 函数没有声明具有该名称的参数。因此,尚不清楚他们将如何互动。您能否通过更新您的问题提供更多信息?
    • @ThirumalaiParthasarathi 这是有道理的。修改磁盘上的模块时,您需要调用 importlib.reload(kwargs) 以使更改在正在运行的解释器会话中生效,再次执行 import kwargs 只会从 sys.modules 缓存中获取模块的旧版本。
    • @ThirumalaiParthasarathi 我认为这超出了这个问题的范围,即使它是您问题的根本原因。就问题而言,它不包括一个模块的多个版本。
    【解决方案2】:

    *content 不是参数。你不能给它分配任何东西。

    文档确实没有明确解释。它只是一个可以在函数体中使用的变量。它的作用是“收集所有剩余的输入参数”。

    通常,这些可变参数将在形式参数列表中排在最后,因为它们会收集传递给函数的所有剩余输入参数。

    https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists

    这个函数

    def func(*args, **kwargs):
        print("args", args)
        print("kwargs", kwargs)
    
    
    func(args=[1, 2, 3])
    

    将打印

    args ()
    kwargs {'args': [1, 2, 3]}
    

    编辑:

    你的例子是错误的。您无法获得不同的输出。

    对于这个功能

    def tag(name, *content, cls=None, **attrs):
        if cls is not None:
            attrs['class'] = cls
        if attrs:
            attr_str = ''.join(' %s="%s"' % (key, val)
                               for key, val
                               in sorted(attrs.items()))
        else:
            attr_str = ''
    
        if content:
            resp = '\n'.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content)
        else:
            resp = '<%s%s />' % (name, attr_str)
    
        print("name", name)
        print("*content", content)
        print("cls", cls)
        print("attrs", attrs)
        print("attrs_str", attr_str)
        print("resp", resp)
        print('-'*10)
       
        return resp
    
    

    如果你运行这个

    tag('div', 'testing', cls='test', **dict(id=33, alt='this is a test'))
    tag(**dict(name='div', content=('testing','and testing'), cls='test', id=33, alt='this is a test'))
    

    你得到

    name div
    *content ('testing',)
    cls test
    attrs {'id': 33, 'alt': 'this is a test', 'class': 'test'}
    attrs_str  alt="this is a test" class="test" id="33"
    resp <div alt="this is a test" class="test" id="33">testing</div>
    ----------
    name div
    *content ()
    cls test
    attrs {'content': ('testing', 'and testing'), 'id': 33, 'alt': 'this is a test', 'class': 'test'}
    attrs_str  alt="this is a test" class="test" content="('testing', 'and testing')" id="33"
    resp <div alt="this is a test" class="test" content="('testing', 'and testing')" id="33" />
    ----------
    

    所以你不应该在第二种情况下得到'&lt;div alt="this is a test" class="test" id="33" /&gt;'。我得到了 &lt;div alt="this is a test" class="test" content="('testing', 'and testing')" id="33" /&gt; 的完全相同的功能。

    编辑 2:可能您的命名空间已损坏,因为名称 **kwargs 经常以您在此处使用 **attrs 的方式在其他地方使用,因此直接更改此模块名称/导入函数应该可以解决您的问题。

    【讨论】:

    • 如果是这种情况,那么为什么 kwargs 没有捕获 dict 条目?我对你的解释很不满意。
    • 为什么不呢?您允许 **kwargs 并且所有 cls='test', **dict(id=33,alt='this is a test') 将作为键值传递给函数。内容已作为 content 传递给 kwargs。如果你这样做attrs.get('content'),你会在那里看到它。
    • attr_str = ''.join(' %s="%s"' % (key,val) for key, val in sorted(attrs.items())) 会打印所有 dict 条目。但正如我在结果中指出的那样,它并没有被打印出来。
    • 对于完全相同的代码,我得到的输出与您不同。编辑我的答案后添加。
    • 我明白你的意思,但这就是我在我的机器上得到的。我已经更新了我的版本信息。也许我也应该上传我的控制台的片段。
    猜你喜欢
    • 2012-12-09
    • 1970-01-01
    • 2023-04-06
    • 2015-09-20
    • 2020-09-16
    • 2017-01-28
    • 2018-02-19
    • 1970-01-01
    • 2021-07-26
    相关资源
    最近更新 更多