これは私が入力がlist
/Tuple
であることを確かめるために私が通常することです - しかしstr
ではありません。関数が誤ってstr
オブジェクトを渡すバグに遭遇したことが何度もあり、ターゲット関数はlst
が実際にはlist
またはTuple
であると仮定してfor x in lst
を行います。
assert isinstance(lst, (list, Tuple))
私の質問です:これを達成するためのより良い方法はありますか?
Python 2のみ(Python 3ではなく):
assert not isinstance(lst, basestring)
そうでなければ、リストのように振る舞うがlist
やTuple
のサブクラスではない多くのことを見逃してしまいます。
Pythonでは、「ダックタイピング」を使いたいことを忘れないでください。したがって、リストのように機能するものはすべてリストとして扱うことができます。そのため、リストの種類をチェックしないでください。リストのように機能するかどうかを確認してください。
しかし、文字列もリストのように振る舞います、そしてそれはしばしば私たちが望むものではありません。それも問題になることがあります!そのため、文字列を明示的にチェックしてから、ダックタイピングを使用してください。
これは私が楽しみのために書いた関数です。これは特別なバージョンのrepr()
で、任意のシーケンスを山括弧( '<'、 '>')で出力します。
def srepr(arg):
if isinstance(arg, basestring): # Python 3: isinstance(arg, str)
return repr(arg)
try:
return '<' + ", ".join(srepr(x) for x in arg) + '>'
except TypeError: # catch when for loop fails
return repr(arg) # not a sequence so just return repr
これは全体的にきれいでエレガントです。しかし、isinstance()
がそこでやっていることは何ですか?それは一種のハックです。しかしそれは不可欠です。
この関数は、リストのように振る舞うものは何でも再帰的に呼び出します。文字列を特別に処理しなかった場合、それはリストのように扱われ、一度に1文字ずつ分割されます。しかし、その後、再帰呼び出しは各文字をリストとして扱おうとします - そしてそれはうまくいきます! 1文字の文字列でもリストとして機能します。この関数は、スタックがオーバーフローするまでそれ自身を再帰的に呼び出し続けます。
このような関数は、実行する作業を細分化する各再帰呼び出しに依存しますが、特別な場合には文字列を使用する必要があります。文字ストリングはリストのように機能します。
注意:try
/except
は、私たちの意図を表現するための最も明確な方法です。しかし、このコードがどうにかしてタイムクリティカルであった場合は、arg
がシーケンスであるかどうかを確認するために何らかのテストに置き換えることをお勧めします。型をテストするのではなく、おそらく動作をテストする必要があります。それが.strip()
メソッドを持っているなら、それは文字列なので、それをシーケンスと考えてはいけません。そうでなければ、それがインデックス可能か反復可能なら、それはシーケンスです:
def is_sequence(arg):
return (not hasattr(arg, "strip") and
hasattr(arg, "__getitem__") or
hasattr(arg, "__iter__"))
def srepr(arg):
if is_sequence(arg):
return '<' + ", ".join(srepr(x) for x in arg) + '>'
return repr(arg)
編集:私はもともと__getslice__()
のチェックで上記を書いていましたが、私はcollections
モジュールのドキュメントの中で興味深い方法は__getitem__()
です。これは理にかなっている、それはあなたがオブジェクトにインデックスを付ける方法です。これは__getslice__()
より基本的なようですので、私は上記を変更しました。
H = "Hello"
if type(H) is list or type(H) is Tuple:
## Do Something.
else
## Do Something.
Python 2の場合:
import collections
if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
print "obj is a sequence (list, Tuple, etc) but not a string or unicode"
Python 3の場合:
import collections.abc
if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
print("obj is a sequence (list, Tuple, etc) but not a string or unicode")
バージョン3.3で変更された仕様:コレクション抽象基本クラスをcollections.abcモジュールに移動しました。後方互換性のため、バージョン3.8まではこのモジュールでも表示され続け、機能しなくなります。
PHPフレーバーを持つPython:
def is_array(var):
return isinstance(var, (list, Tuple))
一般的に言って、オブジェクトを反復処理する関数がタプルやリストだけでなく文字列にも作用するという事実は、バグよりも優れています。確かに can は引数をチェックするためにisinstance
やダックタイピングを使うことができますが、なぜあなたはそうすべきですか?
それは修辞的な質問のように聞こえますが、そうではありません。 「なぜ引数の型をチェックすべきなのか」に対する答え。認識されている問題ではなく、実際の問題に対する解決策を提案することになるでしょう。文字列が関数に渡されるとなぜそれがバグなのですか?また、もし文字列がこの関数に渡されたときにそれがバグであるならば、それが他のリスト/タプルでないイテラブルがそれに渡されたならばそれもバグですか?どうしてですか、どうしてですか?
私は、この質問に対する最も一般的な答えは、f("abc")
を書く開発者がf(["abc"])
を書いたかのように振る舞うことを期待しているということでしょう。おそらく、文字列内の文字を反復処理するユースケースをサポートするよりも、開発者を自分自身から保護する方が理にかなっている状況があります。しかし、私は最初にそれについて長くて難しいと思います。
読みやすさとベストプラクティスのためにこれを試してください。
Python2
import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
# Do something
Python3
import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
# Do something
それが役に立てば幸い。
str
オブジェクトは__iter__
属性を持っていません
>>> hasattr('', '__iter__')
False
だからあなたはチェックをすることができます
assert hasattr(x, '__iter__')
これはまた、他の反復不可能なオブジェクトに対してもNice AssertionError
を発生させます。
編集: Timがコメントで述べているように、これはpython 2.xでのみ動作し、3.xでは動作しません。
これはOPに直接答えることを意図したものではありませんが、いくつかの関連するアイデアを共有したいと思いました。
私は上記の@stevehaの回答に非常に興味を持っていました。これは、アヒルの打ち手が壊れているように見える例を示しているようでした。しかし考え直すと、彼の例はアヒルの型付けに従うのが難しいことを示唆していますが、notはstr
が特別な扱いに値することを示唆しています。
結局のところ、非str
型(例えば、複雑な再帰的構造を維持するユーザー定義型)は@steveha srepr
関数に無限再帰を引き起こすかもしれません。これは確かにかなりありそうもないですが、我々はこの可能性を無視することはできません。したがって、str
の中でsrepr
を特殊なケースにするのではなく、無限再帰が発生したときにsrepr
に何をさせたいのかを明確にする必要があります。
list(arg) == [arg]
の瞬間にsrepr
内の再帰を単純に破ることが合理的な方法の1つであるように思われるかもしれません。実際、これはstr
がなくてもisinstance
に関する問題を完全に解決します。
しかし、本当に複雑な再帰的構造はlist(arg) == [arg]
が決して起こらない無限ループを引き起こすかもしれません。したがって、上記のチェックは便利ですが、それだけでは不十分です。再帰の深さを厳しく制限するようなものが必要です。
私がポイントしているのは、もしあなたが任意の引数型を扱うつもりなら、アヒルタイピングによるstr
の扱いはあなたが(理論的に)遭遇するより一般的な型を扱うよりはるかに簡単であるということです。 str
インスタンスを除外する必要があると感じる場合は、代わりに、引数が明示的に指定された数種類のうちの1つのインスタンスであることを要求してください。
このような is_sequenceという関数がtensorflow にあります。
def is_sequence(seq):
"""Returns a true if its input is a collections.Sequence (except strings).
Args:
seq: an input sequence.
Returns:
True if the sequence is a not a string and is a collections.Sequence.
"""
return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))
そして私はそれがあなたのニーズを満たすことを確認しました。
私は自分のテストケースでこれを行います。
def assertIsIterable(self, item):
#add types here you don't want to mistake as iterables
if isinstance(item, basestring):
raise AssertionError("type %s is not iterable" % type(item))
#Fake an iteration.
try:
for x in item:
break;
except TypeError:
raise AssertionError("type %s is not iterable" % type(item))
発電機でテストされていない、私はあなたが発電機に渡された場合、あなたは次の '収量'に残されていると思う、それは物事を下流に台無しにするかもしれない。しかし、これもまた「単体テスト」です。
「ダックタイピング」マナーで、いかがですか
try:
lst = lst + []
except TypeError:
#it's not a list
または
try:
lst = lst + ()
except TypeError:
#it's not a Tuple
それぞれ。これはisinstance
/hasattr
のイントロスペクションを避けます。
逆もまた可能です。
try:
lst = lst + ''
except TypeError:
#it's not (base)string
すべてのバリアントは実際には変数の内容を変更しませんが、再割り当てを意味します。状況によってはこれが望ましくないかどうかはわかりません。
興味深いことに、TypeError
が list ( Tuple ではない)の場合、+=
no lst
が「インプレース」割り当てで発生します。割り当てがこのように行われるのはそのためです。たぶん誰かがその理由を明らかにすることができます。
最も簡単な方法... any
とisinstance
を使う
>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, Tuple)])
True
Python 3はこれを持っています:
from typing import List
def isit(value):
return isinstance(value, List)
isit([1, 2, 3]) # True
isit("test") # False
isit({"Hello": "Mars"}) # False
isit((1, 2)) # False
リストとタプルの両方をチェックするには、次のようになります。
from typing import List, Tuple
def isit(value):
return isinstance(value, List) or isinstance(value, Tuple)
assert (type(lst) == list) | (type(lst) == Tuple), "Not a valid lst type, cannot be string"
これだけ
if type(lst) in (list, Tuple):
# Do stuff