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 のインタープリターとの会話を示しており、発生するエラーを強調しています。ATypeErrorが生成されますが、予期されたものではありません。ジェネレーターは問題ありませんでしたが、現在は失敗しています。

>>> 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ゼロから再作成すると、意志は期待どおりに機能し始めます。2 番目の引数の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. そうではないからです。シーケンス プロトコルの定義を参照してください。1つになるように実装__getitem__する必要があります。
  3. まだマージされていないバグで、ジェネレーターが壊れていることを示していますが、誤ったエラー メッセージが表示されます。

また、正当な理由以外で、このような型チェックで素敵な言語を汚さないでください:)。

于 2012-07-27T14:32:34.260 に答える