これは、入力がlist
/ tuple
-であるが、ではないことを確認するために私が通常行うことstr
です。関数が誤ってオブジェクトを渡すバグに何度も遭遇したためstr
、ターゲット関数はそれが実際にはまたはであるとfor x in lst
想定します。lst
list
tuple
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文字の文字列でもリストとして機能します!関数は、スタックがオーバーフローするまで再帰的に呼び出し続けます。
このような関数は、実行する作業を分解する各再帰呼び出しに依存し、特殊なケースの文字列を使用する必要があります。これは、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 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 a dictionary.")
バージョン3.3で変更:「CollectionsAbstractBaseClasses」のグローバル名前空間を
abc
からcollections.abc
モジュールに移動しました。下位互換性のために、バージョン3.8で動作が停止するまで、このモジュールでも引き続き表示されます。
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 or dictionary."
PHPフレーバーのPython:
def is_array(var):
return isinstance(var, (list, tuple))
一般的に言って、オブジェクトを反復処理する関数が文字列だけでなくタプルやリストでも機能するという事実は、バグよりも機能です。あなたは確かに引数をチェックするためにタイピングを使うかダックタイピングすることができisinstance
ます、しかしなぜあなたはそうするべきですか?
それは修辞的な質問のように聞こえますが、そうではありません。「なぜ引数の型をチェックする必要があるのか」に対する答え。おそらく、知覚された問題ではなく、実際の問題の解決策を提案するでしょう。文字列が関数に渡されるときにバグになるのはなぜですか?また、文字列がこの関数に渡されたときのバグである場合、他の非リスト/タプルの反復可能オブジェクトがこの関数に渡された場合のバグでもありますか?なぜ、またはなぜそうではないのですか?
この質問に対する最も一般的な答えは、作成する開発者f("abc")
が、関数が作成したかのように動作することを期待していることだと思いf(["abc"])
ます。文字列内の文字を反復処理するユースケースをサポートするよりも、開発者を自分自身から保護する方が理にかなっている状況がおそらくあります。しかし、私は最初にそれについて長くそして一生懸命に考えるでしょう。
読みやすさとベストプラクティスのためにこれを試してください。
Python2- isinstance()
import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
# Do something
Python3- isinstance()
import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
# Do something
それが役に立てば幸い。
オブジェクトに属性str
がありません__iter__
>>> hasattr('', '__iter__')
False
だからあなたはチェックをすることができます
assert hasattr(x, '__iter__')
そして、これはAssertionError
他の反復不可能なオブジェクトにも良い結果をもたらします。
編集: ティムがコメントで述べているように、これはpython 2.xでのみ機能し、3.xでは機能しません
これはOPに直接回答することを意図したものではありませんが、いくつかの関連するアイデアを共有したいと思いました。
上記の@stevehaの回答に非常に興味がありました。これは、ダックタイピングが壊れているように見える例を示しているようです。しかし、考え直してみると、彼の例は、ダックタイピングに準拠するのが難しいことを示唆していますが、特別な処理に値することを示唆していません。str
結局のところ、非str
型(たとえば、いくつかの複雑な再帰構造を維持するユーザー定義型)は、@stevehasrepr
関数に無限の再帰を引き起こす可能性があります。確かにこれはかなりありそうもないことですが、この可能性を無視することはできません。したがって、特別なケースstr
ではなく、無限再帰が発生したときにsrepr
何をしたいのかを明確にする必要があります。srepr
srepr
合理的なアプローチの1つは、その瞬間に再帰を単純に中断することであるように思われるかもしれませんlist(arg) == [arg]
。これは、実際にはstr
、を使用せずに、を使用して問題を完全に解決しますisinstance
。
ただし、非常に複雑な再帰構造では、発生しlist(arg) == [arg]
ない無限ループが発生する可能性があります。したがって、上記のチェックは便利ですが、それだけでは不十分です。再帰の深さを厳しく制限するようなものが必要です。
私のポイントは、任意の引数タイプを処理することを計画している場合、str
ダックタイピングによる処理は(理論的に)遭遇する可能性のあるより一般的なタイプを処理するよりもはるかに簡単であるということです。したがって、str
インスタンスを除外する必要があると感じた場合は、代わりに、引数が明示的に指定した数少ないタイプの1つのインスタンスであることを要求する必要があります。
私はtensorflowでis_sequenceという名前のそのような関数を見つけました。
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
すべてのバリアントは実際には変数の内容を変更しませんが、再割り当てを意味します。状況によっては、これが望ましくないかどうかはわかりません。
興味深いことに、「インプレース」割り当てでは、がリスト(タプルではない)の場合、どのような場合でも+=
noが発生します。そのため、割り当てはこのように行われます。多分誰かがそれがなぜであるかを明らかにすることができます。TypeError
lst
文字列のようなオブジェクトを他のシーケンスのようなオブジェクトから区別するのに役立つダックタイピングの別のバージョン。
文字列のようなオブジェクトの文字列表現は文字列自体であるため、str
コンストラクタから等しいオブジェクトが返されるかどうかを確認できます。
# If a string was passed, convert it to a single-element sequence
if var == str(var):
my_list = [var]
# All other iterables
else:
my_list = list(var)
これは、互換性のあるすべてのオブジェクトstr
およびすべての種類の反復可能なオブジェクトに対して機能するはずです。
最も簡単な方法...とを使用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
Python3にはこれがあります:
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
Pythonで>3.6
import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance({},collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False
私はこれを行う傾向があります(私が本当にそうしなければならなかった場合):
for i in some_var:
if type(i) == type(list()):
#do something with a list
elif type(i) == type(tuple()):
#do something with a tuple
elif type(i) == type(str()):
#here's your string