LoginSignup
14
7

More than 3 years have passed since last update.

【Python中級者への道】クラスに__getattr__関数を定義する

Posted at

まとめへのリンク

はじめに

Pythonの勉強をするために、acopyという群知能ライブラリを写経していました。

acopyでは、多くのPythonの面白い文法・イディオムが使われており、その中でも便利だなぁというのをまとめています。

今回は、クラスに__getattr__特殊メソッドを定義してみます。

インスタンス属性

Pythonでインスタンス内部で使用される属性を取り出す方法として
__dict__を参照する方法があります。

インスタンス内部で使用される名前空間の名前が__dict__に辞書として定義されています。

例えば、以下のような例を見てみます。

class A:

    def __init__(self, x, y):
        self.x = x
        self.y = y


# {'x': 10, 'y': 20}
a = A(10, 20)
print(a.__dict__)

aというインスタンスには、$x, y$の名前が存在し
それらを辞書として取り出すことができます。

__getattr__

Pythonのクラスには、__getattr__という特殊メソッドを定義することができます。

Pythonのインスタンスの名前呼び出しにおいて
もし定義されていない名前で呼ばれればAttributeErrorを返します。

しかし、もし__getattr__が定義されている場合は、
「名前が定義されていない呼び出し時に」AttributeErrorを返す代わりに、__getattr__が実行されます。

例を見てみます。

class A:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, name):
        print(name, 'getattr')
        if name == 'hello':
            return self.world
        raise AttributeError

    def world(self, x):
        return x


'''
{'x': 10, 'y': 20}
hello getattr
<bound method A.world of <__main__.A object at 0x10e602150>>
hello getattr
20
'''
a = A(10, 20)
print(a.__dict__)
print(a.hello)
print(a.hello(20))

上のAクラスには__getattr__メソッドが定義されています。

a.helloを呼び出そうとすると、helloという属性がaに定義されていません。
そのため、__getattr__メソッドが呼び出され、代わりにworldメソッドを返しています。

a.hello(20)では、まずworldメソッドが返されて、worldメソッドに20という値を与えて実行しています。
よって、helloという名前が確定した時点で実行されています。

よって、以上の性質を使うと、以下のような無駄コードをかけます。

class A:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, name):
        print(name, 'getattr')
        if name == 'hello':
            return self.world
        if name == 'world':
            return self.great
        if name == 'great':
            return self.bad
        if name == 'bad':
            return 0
        raise AttributeError


'''
{'x': 10, 'y': 20}
hello getattr
world getattr
great getattr
bad getattr
0
'''
a = A(10, 20)
print(a.__dict__)
print(a.hello)

応用例

あまり応用例は思いつきませんが、
もし属性がない場合に代わりの規程処理をやらせる、などがあるのかなと思います。
また、分岐などを呼び出し名によって変えるなどがありそうです。

class A:

    def __getattr__(self, name):
        self.nothing(name)

    def exist(self):
        print('exist!')

    def nothing(self, name):
        print(f'nothing {name}')


'''
exist!
nothing unnti
'''
a = A()
a.exist()
a.unnti

また、後日別記事として各内容として
lambda式で名前がないとき例外コードを投げるなどでも使えそうです。

参考文献

14
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
7