web-dev-qa-db-ja.com

属性の存在を確認する最良の方法はどれですか?

属性の存在を確認するより良い方法はどれですか?

Jarret Hardie この答えを提供:

if hasattr(a, 'property'):
    a.property

私もそれがこの方法でできることがわかります:

if 'property' in a.__dict__:
    a.property

通常、1つのアプローチが他のアプローチよりも多く使用されていますか?

61
Andrew Halloran

「最適な」方法はありません属性が存在するかどうかを確認するだけではないためです。それは常にいくつかの大きなプログラムの一部です。いくつかの正しい方法と1つの注目すべき正しくない方法があります。

間違ったやり方

if 'property' in a.__dict__:
    a.property

この手法が失敗することを示すデモを次に示します。

class A(object):
    @property
    def prop(self):
        return 3

a = A()
print "'prop' in a.__dict__ =", 'prop' in a.__dict__
print "hasattr(a, 'prop') =", hasattr(a, 'prop')
print "a.prop =", a.prop

出力:

 'prop' in a .__ dict__ = False 
 hasattr(a、 'prop')= True 
 a.prop = 3 

ほとんどの場合、__dict__をいじりたくありません。これは、特別なことを行うための特別な属性であり、属性が存在するかどうかを確認することはごく当たり前のことです。

EAFPの方法

Pythonの一般的なイディオムは「許可よりも許しを求める方が簡単です」、またはEAFPの略です。このイディオムを使用する多くのPythonコードが表示されます、属性の存在を確認するためだけではありません。

# Cached attribute
try:
    big_object = self.big_object
    # or getattr(self, 'big_object')
except AttributeError:
    # Creating the Big Object takes five days
    # and three hundred pounds of over-ripe melons.
    big_object = CreateBigObject()
    self.big_object = big_object
big_object.do_something()

これは、存在しない可能性があるファイルを開くためのまったく同じイディオムであることに注意してください。

try:
    f = open('some_file', 'r')
except IOError as ex:
    if ex.errno != errno.ENOENT:
        raise
    # it doesn't exist
else:
    # it does and it's open

また、文字列を整数に変換します。

try:
    i = int(s)
except ValueError:
    print "Not an integer! Please try again."
    sys.exit(1)

オプションのモジュールをインポートしても...

try:
    import readline
except ImportError:
    pass

LBYLの方法

もちろん、hasattrメソッドも機能します。この手法は、「先読みする」または略してLBYLと呼ばれます。

# Cached attribute
if not hasattr(self, 'big_object'):
    big_object = CreateBigObject()
    self.big_object = CreateBigObject()
big_object.do_something()

hasattrビルトインは、実際にPython 3.2より前のバージョンの例外に関しては奇妙に動作します-すべきでない例外をキャッチします-しかし、これはおそらく無関係です。 hasattrテクニックもtry/exceptより遅いですが、気にするほど頻繁に呼び出すことはなく、違いはそれほど大きくありません。最後に、hasattrは「アトミックではないので、別のスレッドが属性を削除するとAttributeErrorをスローする可能性がありますが、これは非常にフェッチされたシナリオであり、とにかくスレッドについて非常に注意する必要があります。心配する価値がある。)

hasattrの使用は、属性が存在するかどうかを知る必要がある限り、try/exceptよりもはるかに簡単です。私にとって大きな問題は、PythonプログラマーとしてEAFPテクニックを読むことに慣れているためです。上記の例を書き換えて使用する場合、 LBYLスタイルでは、不器用なコード、完全に正しくないコード、または書きにくいコードが得られます。

# Seems rather fragile...
if re.match('^(:?0|-?[1-9][0-9]*)$', s):
    i = int(s)
else:
    print "Not an integer! Please try again."
    sys.exit(1)

そして、LBYLは時々完全に間違っています:

if os.path.isfile('some_file'):
    # At this point, some other program could
    # delete some_file...
    f = open('some_file', 'r')

オプションのモジュールをインポートするためのLBYL関数を作成する場合は、私のゲストになります...この関数は完全なモンスターのようです。

Getattrの方法

デフォルト値だけが必要な場合、getattrtry/exceptの短縮バージョンです。

x = getattr(self, 'x', default_value)

デフォルト値の構築に費用がかかる場合、次のような結果になります。

x = getattr(self, 'attr', None)
if x is None:
    x = CreateDefaultValue()
    self.attr = x

または、Noneが可能な値である場合、

sentinel = object()

x = getattr(self, 'attr', sentinel)
if x is sentinel:
    x = CreateDefaultValue()
    self.attr = x

結論

内部的に、getattrおよびhasattrビルトインはtry/exceptテクニックを使用します(Cで記述されている場合を除く)。そのため、すべてが同じように動作し、正しいものを選ぶのは状況とスタイルの問題によるものです。

try/except EAFPコードは常に一部のプログラマーを間違った方法でこすり、hasattr/getattr LBYLコードは他のプログラマーを刺激します。それらは両方とも正しいものであり、どちらかを選択する真に説得力のある理由はしばしばありません。 (他のプログラマーは、属性が未定義であると通常だと考えることにうんざりしており、一部のプログラマーは、Pythonで未定義の属性を持つことさえ可能であることを恐れています。)

139
Dietrich Epp

hasattr()は道です*

a.__dict__は見苦しく、多くの場合機能しません。 hasattr()は実際に属性を取得しようとし、AttributeErrorを内部でキャッチするため、カスタム__getattr__()メソッドを定義しても機能します。

属性の2回の要求を避けるために、getattr()の3番目の引数を使用できます。

not_exist = object()

# ...
attr = getattr(obj, 'attr', not_exist)
if attr is not_exist:
   do_something_else()
else:
   do_something(attr)

あなたの場合により適切であれば、not_exist番兵の代わりにデフォルト値を使用することができます。

try: do_something(x.attr) \n except AttributeError: ..が好きではありません。do_something()関数内でAttributeErrorを非表示にする場合があります。

*Python 3.1 hasattr()すべての例外を抑制する前AttributeErrorだけでなく)望ましくない場合は、getattr()を使用する必要があります。

11
jfs

hasattr() は、Pythonによる方法です。それを学び、愛してください。

他の可能な方法は、変数名がlocals()またはglobals()にあるかどうかをチェックすることです:

_if varName in locals() or in globals():
    do_something()
else:
    do_something_else()
_

私は個人的に何かをチェックするために例外をキャッチするのが嫌いです。見た目も感じもfeelいです。文字列に数字のみが含まれているかどうかを確認するのと同じです:

_s = "84984x"
try:
    int(s)
    do_something(s)
except ValueError:
    do_something_else(s)
_

s.isdigit()を優しく使用する代わりに。うわー.

5
iTayb

非常に古い質問ですが、本当に良い答えが必要です。短いプログラムであっても、カスタム関数を使用するといいでしょう!

以下に例を示します。すべてのアプリケーションに最適というわけではありませんが、私にとっては、無数のAPIからの応答を解析し、Djangoを使用するためです。すべての人の要件に合わせて簡単に修正できます。

from Django.core.exceptions import ObjectDoesNotExist
from functools import reduce

class MultipleObjectsReturned(Exception):
    pass

def get_attr(obj, attr, default, asString=False, silent=True):
    """
    Gets any attribute of obj.
    Recursively get attributes by separating attribute names with the .-character.        
    Calls the last attribute if it's a function.

    Usage: get_attr(obj, 'x.y.z', None)
    """
    try:
        attr = reduce(getattr, attr.split("."), obj)
        if hasattr(attr, '__call__'):
            attr = attr()
        if attr is None:
            return default
        if isinstance(attr, list):
            if len(attr) > 1:
                logger.debug("Found multiple attributes: " + str(attr))
                raise MultipleObjectsReturned("Expected a single attribute")
            else:
                return str(attr[0]) if asString else attr[0]
        else:
            return str(attr) if asString else attr
    except AttributeError:
        if not silent:
            raise
        return default
    except ObjectDoesNotExist:
        if not silent:
            raise
        return default
0
Mikael Lindlöf