• 属性の参照について分かりやすく解説

    Pythonの勉強をしている中で、属性の参照という言葉があります。

    何となく分かるようで分かりづらいことの言葉。実際に理解するためには、Pythonの言語の仕組みについてしっかりと理解している必要があります。

    そこで今回は、属性の参照について分かりやすく解説していきます。

    なお、この記事は、クラスとオブジェクトについて理解していることが前提です。詳しくは、クラスという記事や、クラスとオブジェクトという記事を参考にして下さい。

    具体的なコードを見てみる

    まずは、以下のコードを見ていきます。

    コード
    

    class City():

        def __init__(self, temperature = 10):

            self.temp = temperature

    Cityというクラスを作成しました。ここから、実際にオブジェクトを作ってみます。

    コード
    

    tokyo = City()

    print(tokyo.temp)

    アウトプット
    

    10

    tokyoというオブジェクトを作り、tokyo.tempというインスタンス変数をprint()関数で出力するというコードです。

    __init__()関数でtemperatureに初期値である10が与えられていますので、このコードを実行すると、10が返されます。

    次に、このようなコードを書いてみるとどうなるでしょうか。

    コード
    

    tokyo = City()

    tokyo.temp = 20

    print(tokyo.temp)

    アウトプット
    

    20

    tokyo.temp = 20というコードを実行したので、tokyo.tempを実行した結果、20が返ってきました。

    当たり前といえば当たり前ですよね。

    しかし、このようなコードにしてみるとどうなるでしょうか?

    コード
    

    class City():

        def __init__(self, temperature = 10):

            self.temp = temperature

        def __setattr__(self, name, value):

            return

    __setattr__という関数を定義しましたが、この関数は中身が何もありません。ですので、このCityクラスには何も影響を与えることはないですよね。

    では、このコードを実行してみるとどうなるでしょうか?

    コード
    

    tokyo = City()

    print(tokyo.temp)

    アウトプット
    

    AttribureError: 'City' object has no attribute 'temp'

    と、エラーが返ってきてしまいました。

    何もしない__setattr__関数を入れただけで、なぜ結果が変わってしまったのでしょうか?

    実はここに、属性の参照に対する秘密が隠れているのです。

    Cityクラス内で、__setattr__がしていること

    ここで、__setattr__が何なのかということについて説明していきましょう。

    __setattr__はset attributeという名前の通り、属性を設定するために使われる関数です。この関数はクラスに対する組み込み関数です。

    つまり、Cityクラスの中で__setattr__()関数を改めて定義したので、親クラスの__setattr__()関数をオーバーライド(上書き)してしまったのです。

    逆の言い方をすれば、クラスに__setattr__という関数が備わっているからこそ、属性を設定することができている、といえるのです。

    属性を参照するとは

    ここで、属性を参照するということがどういったことなのか説明していきます。

    属性を参照するとは、先程の例ではtokyo.tempのように、オブジェクト.属性(またはメソッド)を指定することを言います。

    これはイメージが非常に湧きづらいのですが、Pythonがコードを実行する時に、tokyo.tempといったコードを見つけると、「このコードは属性を参照しているのだな」

    と判断し、対応する関数を実行する。と理解して下さい。

    __init__メソッドはオブジェクトの作成時に実行されるメソッドである。という考え方と同じです。

    __setattr__メソッドは、属性の参照時に実行されるメソッド、ということです。

    属性への参照で__setattr__が呼びされていることを確認する

    では、実際に属性への参照で関数が呼びされていることを確認してみましょう。

    コード
    

    class City():

        def __setattr__(self, name, value):

            print('setattribute is called')

            return

    __setattr__()関数の下にprint()関数を定義しました。この上で、以下のようなコードを実行してみます。

    コード
    

    tokyo = City()

    tokyo.temp = 20

    アウトプット
    

    setattribute is called

    tokyo.temp = 20というコードが、属性を参照しているので、__setattr__()関数が呼びされ、setattribute is calledという文章が出力されました。

    ちなみに、ここではtokyo.tempとしましたが、これはtempでなくても何でも構いません。

    また、この__setattr__メソッドはクラスメソッドをオーバーライドしており、本来__setattr__メソッドが実装している「属性の設定」という機能は持っていません。

    つまり、今回定義した__setattr__メソッドを実行しても、属性を設定することができませんので、__setattr__メソッドを実行した後に属性を呼び出そうとしてもエラーが出てしまいます。

    コード
    

    print(tokyo.temp)

    アウトプット
    

    AttributeError: 'City' object has no attribute 'temp'

    Cityオブジェクトにはtempという属性はありません。というエラーが出てしまいました。つまり、属性が設定されていないということです。

    属性を設定する方法

    参考として、属性を設定する方法についてご紹介していきます。

    コード
    

    def __setattr__(self, name, value):

        self.name = value

    selfは呼び出し元のオブジェクト、nameは属性、valueは設定する値です。

    そして、tokyo.temp=90という形でコードが実行されると__setattr__メソッドが実行され、self.name = valueとなり、新たな値である90がtemp属性として設定されます。

    実際にコードを書いて実行してみましょう。

    コード
    

    class City():

        def __setattr__(self, name, value):

            self.name = value

    tokyo = City()

    tokyo.temp = 20

    アウトプット
    [Previous line repeated 327 more times]
    

    RecursionError: maximum recursion depth exceeded

    同じコードが何回も繰り返されています。というエラーが出てしまいました。

    ここからは、なぜこのようなエラーが出てしまうのか説明していきます。

    tokyo.temp = 90というコードをPythonが読み込んだ時、Pythonはこれが属性の参照だと解釈し、__setattr__()関数を呼び出します。

    そして、self.name = valueにそれぞれの値が代入されるのですが、代入された結果は、tokyo.temp = 90です。

    これは、tokyo.tempという記載の通り、属性の参照にほかなりません。

    結果として、また__setattr__()関数が呼び出されてしまい、それが属性を参照し、また__setattr__()関数が呼び出されてしまうというループになってしまうのです。

    これを避けるためには、違う書き方をします。

    具体的なコードで見ていきましょう。

    コード
    

    class City():

        def __setattr__(self, name, value):

            self.__dict__[name] = value

    これで、エラーを防ぐことができます。

    _dict__()関数は、対象となるオブジェクトの名前空間の中から、[]の中に入っている項目を見つけ出してくれます。

    こうすることによって、Pythonは属性への参照ではないと解釈し、属性を設定することができるのです。

    実際にコードを実行してみましょう。

    コード
    

    tokyo = City():

    tokyo.temp = 20

    print(tokyo.temp)

    アウトプット
    

    20

    無事に、20という結果を得ることができました。

    属性の参照の方法についてお伝えしました。propertyは、この属性への参照の考え方を理解していると、理解が非常に容易になると思います。属性への参照を理解したこのタイミングで、是非propertyについても理解をしてしまいましょう。