nullable chain

存在しないかするか分からないオブジェクトや属性を参照しようするとき、存在しないものを取得しようとしてエラーになるのが面倒。
例えば、以下のようにissueから始めてlogin_nameを取り出したい時とか。

print issue.author.login_name

ただし、この時

  • issueが存在しない(None)こともある
  • issueが存在することもある。

そして、存在した場合はlogin_nameが欲しい。

こういう時以下のようなコード書くの面倒

if issue is None:
   return None
else:
   return issue.author.login_name

時々issueがあってもauthorが存在しないような場合もある。
そうなると以下のようになってしまう。

if issue is None:
   return None

author = issue.author
if author is None
   return None
else:
   return author.login_name

ちょー、面倒くさい。
かと言って、これを避けるためにループを書くのはどうかと思う。
関数にくくりだしても使い辛い。

def selfish_access(obj, *ks):
    for k in ks:
        if hasattr(obj, k):
            obj = obj.k
        else:
            return obj
    return obj

# selfish_access({}, "x")
selfish_access(issue, "author","login_name")

こんなのどうだろう?(NullableChain)

名前は適切かどうか分からないけれど

class NullableChain(object):
    def __init__(self, obj):
        self.obj = obj

    def __getattr__(self, k):
        obj = self.obj
        if hasattr(obj, k):
            return NullableChain(getattr(obj, k))
        else:
            return NullableChain(None)

print NullableChain({}).x # => None

NullableChainに渡すと、このクラスで包んだオブジェクトを返す。
このクラスのインスタンスは、好きなだけchainして値を取り出すことができる。
最終的な値を取り出したい場合は、objを最後につければ良い。

使い方

普通にアクセスした場合(エラー)

空のfooクラスを定義。
これにfoo.bar,foo.bar.bazという感じで値を取り出そうとしてもうまくいかない。
普通は、エラーが出てしまう。これがイライラする。

class foo(object):
    pass

print foo.bar.baz

# Traceback (most recent call last):
#   File "/home/podhmo/sandbox/python/nullable_chain.py", line 52, in <module>
#     print foo.bar.baz
# 	AttributeError: type object 'foo' has no attribute 'bar'
Nullable Chain

期待としては、foo.bar.bazがとれるかも???と思えるような状況でエラーが起きないこと。

class foo(object):
    pass

print NullableChain(foo).obj
print NullableChain(foo).bar.obj
print NullableChain(foo).bar.baz.obj

# <class '__main__.foo'>
# None
# None

結果を見てみるとうまく行っているっぽい。エラーにならずにNoneが返ってきている。

多段のものも適切に取り出せる。

class foo(object):
    """単にfoo.bar.bazとして使いたいだけ"""
    class bar(object):
        class baz(object) :
            pass

print NullableChain(foo).obj
print NullableChain(foo).bar.obj
print NullableChain(foo).bar.baz.obj

# <class '__main__.foo'>
# <class '__main__.bar'>
# <class '__main__.baz'>

意外と便利な気がする。