类型错误:* 后的 function() 参数必须是序列,而不是生成器 [英] TypeError: function() argument after * must be a sequence, not generator
问题描述
在尝试编写一个很小的、经过混淆的类型检查器时,发现了一个不可接受的代码模式.但是,它不一致地无法正常工作.这是最初编写用于测试的代码.
def statictypes(a):def b(a, b, c):如果 b 在 a 而不是 isinstance(c, a[b]): raise TypeError('{} 应该是 {}, not {}'.format(b, a[b], type(c)))返回 creturn __import__('functools').wraps(a)(lambda *c: b(a.__annotations__, 'return', a(*(b(a.__annotations__, *d) for d in zip(a.__code__.co_varnames), C)))))@静态类型def isallinstance(iterable: object, class_or_type_or_tuple: (type, tuple)) ->布尔:"""isallinstance(iterable, class_or_type_or_tuple) -> bool返回迭代中的所有项目是类的实例还是类的实例其子类.使用类型作为第二个参数,返回是否是所有项目的类型.使用元组的形式, isallinstance(x, (A, B, ...)),是 any(isallinstance(x, y) for y in (A, B, ...)) 的快捷方式."""返回所有(isinstance(item, class_or_type_or_tuple) for item in iterable)
以下显示了与 Python 解释器的对话,并突出显示了出现的错误.生成了 TypeError
,但不是预期的.虽然生成器很好,但现在它们失败了.
statictypes
函数可以重写,isallinstance
函数重新定义和包装.最简单的解决方案是将 statictypes
中的生成器重写为列表推导式.
def statictypes(a):def b(a, b, c):如果 b 在 a 而不是 isinstance(c, a[b]): raise TypeError('{} 应该是 {}, not {}'.format(b, a[b], type(c)))返回 creturn __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
按预期正确生成.
问题:
- 为什么带有生成器的第一个函数有时会工作而有时会失败?
- 为什么一个生成器不被认为是一个序列(因为它生成了一个序列)?
- 当生成器在某些时候显然可以工作时,为什么需要一个序列?
- 因为
isinstance
和其他几个棘手的标准库函数一样,当你给它一个元组时,它会做与其他序列不同的事情.也就是说,它可以工作,并检查类型是否是给定的任何类型. - 因为它不是.请参阅序列协议定义.它需要将
__getitem__
实现为一个. - 一个错误,它仍然尚未合并,它告诉你你的生成器坏了,但错误消息不正确.
另外,请不要用这样的类型检查来玷污我们可爱的语言,除非有充分的理由:)
While trying to write a tiny, obfuscated type checker, an unacceptable code pattern was discovered. However, it inconsistently fails to work properly. This is the code that was initally written to test it with.
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)
The following shows a conversation with Python's interpreter and highlights the error that comes up. A TypeError
is generated, but not the one that was expected. While generators were fine, now they fail.
>>> 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
The statictypes
function can be rewritten, and the isallinstance
function redefined and wrapped. The simplest solution is to rewrite the generatior in statictypes
to be a list comprehension.
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)])))
After that, the isallinstance
will starting working as expected once it is recreated from scratch. The TypeError
stating what was wrong with the second argument is properly generated as was desired.
>>> 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'>
Questions:
- Why does the first function with the generator somtimes work and other times fail?
- Why is a generator not considered a sequence (since it generates a sequence)?
- Why is a sequence needed when a generator obviously works some of the time?
- Because
isinstance
, like a couple of other screwy standard library functions, does a different thing when you give it a tuple than other sequences. Namely, it works, and checks that the type is any of the ones given. - Because it isn't. See the sequence protocol definition. It'd need to implement
__getitem__
to be one. - A bug, which still hasn't been merged, which is telling you that your generator is broken, but with the incorrect error message.
Also, please don't dirty our lovely language with type checking like this for anything but good reasons :).
这篇关于类型错误:* 后的 function() 参数必须是序列,而不是生成器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!