6

在尝试编写一个微小的混淆类型检查器时,发现了一个不可接受的代码模式。但是,它始终无法正常工作。这是最初为测试它而编写的代码。

def statictypes(a):
    def b(a, b, c):
        if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
        return c
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*(b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)))))

@statictypes
def isallinstance(iterable: object, class_or_type_or_tuple: (type, tuple)) -> bool:
    """isallinstance(iterable, class_or_type_or_tuple) -> bool

    Return whether all items in an iterable are instances of a class or of a
    subclass thereof. With a type as second argument, return whether that is
    all items' type. The form using a tuple, isallinstance(x, (A, B, ...)),
    is a shortcut for any(isallinstance(x, y) for y in (A, B, ...)).
    """
    return all(isinstance(item, class_or_type_or_tuple) for item in iterable)

下面显示了与 Python 解释器的对话,并突出显示了出现的错误。生成了A TypeError,但不是预期的。虽然生成器很好,但现在它们失败了。

>>> isallinstance(range(1000000), int)
True
>>> isallinstance(range(1000000), (int, float))
True
>>> isallinstance(range(1000000), [int, float])
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    isallinstance(range(1000000), [int, float])
  File "C:\Users\schappell\Downloads\test.py", line 5, in <lambda>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*(b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)))))
TypeError: isallinstance() argument after * must be a sequence, not generator

statictypes函数可以重写,函数可以重新isallinstance定义和包装。最简单的解决方案是将生成器重写statictypes为列表理解。

def statictypes(a):
    def b(a, b, c):
        if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
        return c
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))

之后,isallinstance一旦从头开始重新创建,将开始按预期工作。说明第二个参数有什么问题是根据TypeError需要正确生成的。

>>> isallinstance(range(1000000), int)
True
>>> isallinstance(range(1000000), (int, float))
True
>>> isallinstance(range(1000000), [int, float])
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    isallinstance(range(1000000), [int, float])
  File "C:\Users\schappell\Downloads\test.py", line 5, in <lambda>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "C:\Users\schappell\Downloads\test.py", line 5, in <listcomp>
    return __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*[b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames, c)])))
  File "C:\Users\schappell\Downloads\test.py", line 3, in b
    if b in a and not isinstance(c, a[b]): raise TypeError('{} should be {}, not {}'.format(b, a[b], type(c)))
TypeError: class_or_type_or_tuple should be (<class 'type'>, <class 'tuple'>), not <class 'list'>

问题:

  1. 为什么生成器的第一个功能有时会工作而其他时候会失败?
  2. 为什么生成器不被视为序列(因为它生成序列)?
  3. 当生成器在某些时候明显工作时,为什么需要一个序列?
4

1 回答 1

8
  1. 因为isinstance,就像其他几个古怪的标准库函数一样,当你给它一个元组时,它的作用与其他序列不同。也就是说,它可以工作,并检查类型是否为给定的类型。
  2. 因为它不是。请参阅序列协议定义。它需要实现__getitem__成为一个。
  3. 一个尚未合并的错误,它告诉您生成器已损坏,但错误消息不正确。

另外,请不要为了任何正当理由而用这样的类型检查来玷污我们可爱的语言:)。

于 2012-07-27T14:32:34.260 回答