【问题标题】:Checking if a variable can be unpacked检查变量是否可以解包
【发布时间】:2016-10-28 14:44:08
【问题描述】:

假设我有一个变量x,它的数据类型未知。我还有一些随机函数foo。现在我想按照以下思路做一些事情:

如果x 是可以使用** 解包的类型,例如字典,请调用foo(**x)。否则,如果x 是可以使用* 解包的类型,例如元组,则调用foo(*x)。否则,请致电foo(x)

有没有一种简单的方法来检查一个类型是否可以通过 ** 或 * 解包?

我目前正在做的是检查 x 的类型并执行如下操作:

if type(x) == 'dict':
    foo(**x)
elif type(x) in ['tuple','list', ...]:
    foo(*x)
else:
    foo(x)

但问题是我不知道可以实际解包的数据类型的完整列表,我也不确定用户定义的数据类型是否可以有一个方法来允许它们被解包。

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

    你可以使用try:

    try:
        foo(**x)
    except:
        try:
            foo(*x)
        except:
            foo(x)
    

    它有点粗略,并且不区分为什么发生异常(这可能通过检查异常类型来缓解),但消除了尝试和枚举可以调用哪些类型的需要哪一条路。

    【讨论】:

    • 确实,except TypeError 是您应该对此施加的最小限制;尽管这仍然不能告诉您** 是否引发了异常或foo 中的某些内容。
    • 可爱的答案,但是如果 foo(x) 也失败了怎么办?可以再试一次吗?因为 x 是未知的,对吧?
    【解决方案2】:

    让我们检查一下我们在做不好时收到的错误:

    >>> x = 1
    >>> f(*x)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: f() argument after * must be a sequence, not int
    >>> f(**x)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: f() argument after ** must be a mapping, not int
    

    太好了:所以我们需要* 的序列类型和** 的映射类型。其余部分相当简单:Python 3 docs 状态:

    共有三种基本的序列类型:列表、元组和范围对象。专为处理二进制数据和文本字符串而定制的其他序列类型在专用部分中进行了描述。

    检查 var 是否为序列类型的故障安全方法是:

    >>> import collections
    >>> all(isinstance(x, collections.Sequence) for x in [[], (), 'foo', b'bar', range(3)])
    True
    

    (请参阅Python: check if an object is a sequence 了解更多信息)

    映射类型,根据docs,是dict

    目前只有一种标准映射类型,即字典。

    你可以用同样的方式检查这个,使用isinstance,它甚至会处理派生类:

    >>> from collections import OrderedDict
    >>> from collections import Counter
    >>> all(isinstance(x, dict) for x in [{}, OrderedDict(), Counter()])
    True
    

    所以你可以这样做:

    import collections
    if isinstance(x, dict):
        foo(**x)
    elif isinstance(x, collections.Sequence):
        foo(*x)
    else:
        foo(x)
    

    【讨论】:

    • 这与我要找的很接近,但它似乎不适用于集合。例如isinstance(set([1,2,3]),collections.Sequence) 是假的。但是,'{},{}'.format(*set([1,2,3])) 解压缩为 '1,2' 就好了。我不确定是否还有其他例外,但至少有这个。
    • set([1,2,3]) 解压到 1,2 可以吗?
    • @K.Mao 嗯...你是对的,但请注意,这并没有多大意义,因为 set 没有排序。我现在正在检查这个
    • 我做了一些挖掘工作。也许collections.Iterable 可以工作?您可以使用 * 解包集合和字典(尽管应该使用 ** 解包字典)。 all(isinstance(x,collections.Iterable) for x in [set(),[],(),{},'foo',b'bar',range(3)]) 评估为真,而 collections.Sequence 在集合和字典上失败。但是,我同意使用 * 解包集合或字典没有多大意义,因为它们没有排序。但由于某种原因,您可以在 Python 中完成。
    • 你是对的:对于dict,它将遍历键。我建议使用Sequence,因为它们会给你可预测的、有序的行为。
    【解决方案3】:

    怎么样

    try:
        (lambda **a: None)(**x)
    except TypeError:
        try:
            (lambda *a: None)(*x)
        except TypeError:
            # x cannot be unpacked
            foo(x)
        else:
            # x can be unpacked as a list
            foo(*x)
    else:
        # x can be unpacked as a dict
        foo(**x)
    

    或者你可以定义一些实用函数

    def is_list_unpackable(obj):
        try:
            (lambda *a: None)(*obj)
        except TypeError:
            return False
        return True
    
    def is_dict_unpackable(obj):
        try:
            (lambda **a: None)(**obj)
        except TypeError:
            return False
        return True
    
    if is_dict_unpackable(x): # type(x) == 'dict'
        foo(**x)
    elif is_dict_unpackable(x): # type(x) in ['tuple','list', ...]:
        foo(*x)
    else:
        foo(x)
    

    (受 Scott Hunter 的回答和 deceze 的评论启发)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-03
      • 1970-01-01
      • 2015-12-15
      • 1970-01-01
      • 1970-01-01
      • 2022-01-04
      • 2021-12-28
      相关资源
      最近更新 更多