LoginSignup
192

More than 5 years have passed since last update.

posted at

updated at

Organization

Python初心者でも__hoge__使いたい!〜特殊属性,特殊メソッドの使い方〜

1.はじめに

Pythonでよくオブジェクト定義に用いられたり,いろんなオブジェクトに元からくっついてる__で囲まれた__hoge____hoge__()をそれぞれ特殊属性(特殊アトリビュート),特殊メソッドと言います.
これを使いこなしている人を見るとPython中級者以上感がするので僕も使いこなせるようになろうというメモです.

この特殊属性や特殊メソッドはオブジェクト指向の書き方です.Pythonのオブジェクト指向プログラミング(Object Oriented Programming)をOOPyと勝手に呼んでいるので是非使ってください.読み方は各自に任せます.

特殊属性や特殊メソッドは割といっぱいあるのでこの記事に順次追加していく予定です.

2.環境

  • Python 3.6.0

3.特殊属性

__dict__

そのオブジェクトのアトリビュート(変数とか配列とかメソッドではないもの)を辞書にして返す.

class DictTest(object):
    def __init__(self):
        self.a = 1
        self.b = [1,2]      

    def c(self):
        pass

dicttest = DictTest()
print(dicttest.__dict__)
# {'a': 1, 'b': [1, 2]}

上のようにaとbのアトリビュートだけ格納されてます.cのメソッドについては格納されていません.

ちなみにpythonチュートリアルによると,これを使うのは検死型のデバッガなどに限るべきだそうです.
これは,例えばself.aにアクセスするときは何か処理をしてから返すというようになっていた場合も,それを全部無視してアクセスできてしまうためだと思われます.

__doc__

def定義やclass定義宣言の直下に複数行コメントがあるとそれを読み込んで,ここに格納する.
help()によってこの情報を表示してくれる.
ちなみにサブクラスには継承されない.

def doc_test_func1():
    """This function is ...
        hogehoge
    """
    pass

def doc_test_func2():
    pass


print(doc_test_func1.__doc__)
# This function is ...
#        hogehoge

help(doc_test_func1)

print(doc_test_func2.__doc__)
# None
# これにも__doc__アトリビュートはついてるが中身がNoneになっている.

help(doc_test_func2)

__annotations__

関数注釈をすると,この__annotations__に情報が保存される.

ちなみに関数注釈とは,引数や返り値がどんなデータ型か人間用に見やすく表記する書き方である.
あくまでも人間用で,その注釈と違ったことをしてもエラーが出たりするわけではない.
詳細はPythonではじまる、型のある世界などを参照.

# 関数注釈
def ann_test_func(param1: str, param2: int=1) -> str:
    output = param1 + str(param2)
    print(output)
    return output

print(ann_test_func.__annotations__)
# {'param1': <class 'str'>, 'param2': <class 'int'>, 'return': <class 'str'>}
# このように各引数や,返り値がどんなデータ型か辞書で保存されている.

ann_test_func("test", 1)
# test1

ann_test_func("test", "a")
# testa
# intの場所にstrを入れても怒られない.

__module__

関数が定義されているモジュールの名前を返してくれます.

class ModuleTest(object):
    pass

modeletest = ModuleTest()
ModuleTest.__module__
# __main__

pythonでは,対話型環境もひとつのモジュールで,その名前は__main__と名付けられているためこのように表示されます.

4.特殊メソッド

__init__

オブジェクト指向で言われるところのコンストラクタ.
(下記コメントにあるようにイニシャライザと呼んだほうがいいようだ.Pythonには正確にこれがコンストラクタと呼べるものはないらしい.newinitとメタクラスとなどを参照.)
インスタンス化するときに呼ばれる.

class InitTest(object):
    def __init__(self):
      print("URYYYYYYYYYY")

inittest = InitTest() # ここで呼ばれる
# URYYYYYYYYYY

__iter__, __next__

__iter__は,pythonドキュメントによるとイテレーターオブジェクトを返すものとある.つまり,元々イテレーターでないものを変換するといった処理を書くメソッドである.

class IterTest(object):
    def __init__(self, *nums):
        self.nums = nums

    def __iter__(self):
        return iter(self.nums)

itertest = IterTest(1,2,3)
for i in itertest:
    print(i)
# 1
# 2
# 3

__next__は,次の値を渡す部分を書き,もう渡すものがなくなったらStopIteration例外を出すように実装する.

詳細はPythonのイテレータとジェネレータを参照.この記事のサンプルコードを引っ張ってくると以下のような感じ.

class MyIterator(object):
    def __init__(self, *numbers):
        self._numbers = numbers
        self._i = 0
    def __iter__(self):
        return self
    def __next__(self):
        # なぜか元の記事ではメソッド名がnextだが,__next__にすると動いた.
        if self._i == len(self._numbers):
            raise StopIteration()
        value = self._numbers[self._i]
        self._i += 1
        return value

__del__

このメソッドはデストラクタとも呼ばれる.インスタンスが消滅させられるときに呼ばれる.

class DelTest(object):
    def __del__(self):
        print("俺はまだ死にたくない!!")

deltest = DelTest()
del deltest
# 俺はまだ死にたくない!!

__call__

オブジェクト自体をメソッドのように書いたとき,この__call__を実行する.

class CallTest(object):
    def __call__(self):
        print("呼んだか?")

calltest = CallTest()
calltest()
# 呼んだか?

__setattr__,__getattr__, __delattr__

__setattr__はアトリビュートに値を代入するときに呼ばれる.
__getattribute__はアトリビュートがあろうがなかろうが呼ばれる.親クラスの__getattribute__を使うとかしないとすぐに再帰的エラーになる.(__getattribute__の中で__getattribute__呼んで...が繰り返される.)
__getattr__ はアトリビュートにアクセスするときに,そのアトリビュートがなかったときに呼ばれる.

class SetGetDelattrTest(object):
    def __setattr__(self, name, value): 
        self.__dict__[name] = value
        print(f"{name} : {value} has been set!")

    def __getattribute__(self, name):
        print("URYYYYYY")
        return super().__getattribute__(name)

    def __getattr__(self, name):
        print(f"try to return value of {name}")
        raise AttributeError("nothing...")

    def __delattr__(self, name):
        print("not work!")

sgdattr = SetGetDelattrTest()
sgdattr.a = 1 # __setattr__
sgdattr.a # __getattribute__
sgdattr.b # __getattr__

del sgdattr.a # __delattr__
# not work!

sgdattr.a
# URYYYYYY
# 1
# __delattr__に消すコードを書いていないので生きてる.

__setattr__とかの中のself.でも呼ばれているらしくURYYYYYYURYYYYYYめっちゃうるさい.

これはアトリビュートへのアクセスの仕方全てに関する特殊メソッドだが,ぶっちゃけこの辺はあまり使わないと思われる.
実用上はあるアトリビュートに対して個別にsetとgetをいじりたい場合が多い.そんなときは次の@propertyデコレータを使うと便利です.

@property

デコレータは,ある関数に対して,この関数はこういうものだという追加の設定ができるようなものです.
デコレータについてはPythonのデコレータについてあたり.
特に,@propertyに関してはPython @propertyが良い感じです.

まず@propertyを使って,アトリビュートと同じ名前のメソッドを定義します.これがgetterになります.
次に同じ名前で@x.setter,@x.deleterデコレータを使って,値代入時に呼ばれるsetterや削除時に呼ばれるdeleterを定義していきます.(なくても良い)

注意点としては,オブジェクト内ではプライベート変数として_xを定義しておいて,対人間用としてアトリビュートxでsetしたりgetするようにすることです.
そうしないと,getterのところで,object.xでxの値を取りにいったときに,中のreturn self.xでまたgetterを呼び出して...と延々とループします.

class PropertyTest(object):
    def __init__(self, x):
        self._x = x
        self.y = 1

    @property
    def x(self):
        print("getting x")
        return self._x

    @x.setter
    def x(self, x):
        if not isinstance(x, float):
            raise ValueError("hoge")

        print("setting x")
        self._x = x

    @x.deleter
    def x(self):
        print("頼む!殺さないでくれ!")
        del self._x

proptest = PropertyTest(x=10.)

proptest.x = 20.
# setting x

print(proptest.x)
# getting x
# 20.0

del proptest.x
# 頼む!殺さないでくれ!

print(proptest.y)
# 1
# アトリビュートyについては特に何も変更されていない.

アトリビュートyのgetterには特に何の変更もされておらず,xについてだけ修正されていることがわかります.

__dir__

インスタンスに対してdir()をすると動くメソッド.
デフォルトから変更すると嬉しい理由が特に思いつかない.

class DirTest(object):
    def __dir__(self):
        print("dirdirdir")
        return [1,2,3]

dirtest = DirTest()
dir(dirtest)
# dirdirdir
# [1, 2, 3]

5.reference

6.おわりに

もっとOOPy触って育てたい.

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
What you can do with signing up
192