Pythonの@propertyによるプロパティへのアクセス制御のまとめ

Python では、プログラム設計者が想定していない変更が加えられることによって、重大なエラーが発生することを防ぐために、クラスのプロパティ(インスタンス変数やクラス変数の値)を、外部から読み取ったり、更新したりできないようにすることができます。

これは、オブジェクト指向プログラミングにおいて、とても重要な知識であり、テクニックです。

これらを使いこなせるようになると、保守性も拡張性も高いコードを書くことができるようになります。ぜひ、一つずつ理解を深めていきましょう。

目次

1. Python の非公開変数の作り方

オブジェクト指向プログラミングでは、第三者が、クラスの設計者が意図しない操作をしてしまうことによって、重大なエラーが発生してしまうような事態を防ぐ仕組みがいくつかあります。

プロパティの制御がまさにそれですが、その前に、非公開変数から解説しておきたいと思います。「非公開変数」とは、外部から、その値を参照したり変更したりすることができない変数のことです。

詳しく見ていきましょう。

1.1. 先頭に __ がついているものは非公開変数になる

Python では、インスタンス変数の前に、アンダースコア (_) を 2 個続けて書いておくと、それだけで、その変数は、クラスブロックの外や、外部ファイルから値を調べたり、更新したりできない「非公開変数」になります。

次のコードをご覧ください。

In [1]:
'''Dog クラス'''
class Dog():
 
    '''初期化メソッド'''
    def __init__(self):
        self.__name = "John" #変数の前に __ をつけると非公開の変数になります。

Dog クラスを作り、そのインスタンス変数として __name を作っています。このコードを書いたファイルに、『class_dog1.py』という名前をつけて保存しておきます。

それでは、外部ファイルから、インスタンス変数 __name の値を参照できるかどうか確認してみましょう。

次のコードをご覧ください。

In [1]:
'''class_dog1.py ファイルから Dog クラスをインポートします。'''
from class_dog1 import Dog
 
'''非公開の変数は外部から参照することはできません。'''
dog = Dog() #Dog クラスのインスタンスを作ります。
dog.__name #外部ファイルから非公開変数を参照してみましょう。
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-d7d510d00330> in <module>()
      2 '''非公開の変数は外部から参照することはできません。'''
      3 dog = Dog()
----> 4 dog.__name

AttributeError: 'Dog' object has no attribute '__name'

ご覧のように、外部ファイル(クラスの外)から、非公開変数を参照しようとすると、「その変数は存在しません(= Attribute)」 というエラーが発生します。

1.2. クラスブロック内では非公開変数を自由に使うことができる

なお、Dog クラス内 (= class Dog: のブロック内にインデントされて書かれている部分)では、非公開変数を自由に使うことができます。

例えば、次のコードをご覧ください。

先ほどのファイルに、dogname() メソッドを追加したものです。

In [1]:
'''クラスのブロックの中では、非公開変数も自由に使うことができます。'''
class Dog():
 
    '''初期化メソッド'''
    def __init__(self):
        self.__name = "John" #非公開変数 __name
        
    '''クラスブロック内で非公開変数を使ったメソッドを作ります。'''
    def dogname(self):
        return self.__name

dogname()メソッドは、class Dog: のブロックの中に書かれています。そのため、その定義文の中で、非公開変数 __name を自由に使うことができます。

このファイルには、『class_dog2.py』という名前をつけて保存しておきます。

このように、クラスブロック内で非公開変数の値を参照するメソッドを作っておけば、それを使うことによって、外部ファイルからでも、その値を確認することができるようになります。

以下をご覧ください。

In [1]:
'''class_dog2 ファイルから Dog クラスをインポートします。'''
from class_dog2 import Dog
 
'''Dog クラスのインスタンスを作ります。'''
dog = Dog()
 
'''class_dog2 ファイルに書いているメソッドを使って非公開変数__nameを参照します。'''
dog.dogname()
Out[1]:
'John'

これは、外部ファイルですが、dogname() メソッドを通してなら、非公開変数にアクセスすることができていますね。

2. Python におけるプロパティ制御

それでは、ここから本題であるプロパティについて解説します。

プロパティとは、オブジェクト指向プログラミングにおいて、「オブジェクト(インスタンス)が持っているデータそのもの」のことです。例えば、上で見てきた class Dog には、__name という文字列データをもつ変数がありましたね。この __name がプロパティです。

オブジェクト指向プログラミングでは、変数の値が、不用意に変更される可能性を徹底的に排除します。

そのため、プロパティ(変数の値)を、外部から参照したり変更したりするには、「アクセサ (= getter と setter)」というものを使うようにすることにして、プロパティの不用意な変更を防いでいるのですね。

Python は、もともと、こうしたプロパティを管理する機能がある言語なので、他の多くのプログラミング言語と比べて、非常に少ないコード量で、これを実現することができます。

早速見ていきましょう。

なおプロパティ制御自体は、非公開変数以外に対しても行えます。ここでは非公開変数を基本として解説します。

2.1. @property で getter と setter を作る

まず、getter と setter は次のように機能するものです。

  • getter:インスタンス変数の値を返す
  • setter:インスタンス変数に新しい値を設定する

オブジェクト指向プログラミングでは、基本的に、これらを介して、インスタンス変数を操作します。

getter と setter はクラスブロック内に、次のように書きます。

In [0]:
    '''getter'''
    @property
    def プロパティ名(self):
        ステートメント
        return  
    '''setter'''
    @プロパティ名.setter
    def プロパティ名(self, value):
        self.変数 = value

このように、getter の先頭には @property、 setter の先頭には @プロパティ名.setter をつけます。@で始まる文は「デコレータ」といいます。そして、def 文で、それぞれの機能を定義します。

getter と setter のプロパティ名を同じにすることがポイントです。

次の例をご覧ください。これまで作った 『class_dog.py』ファイルに、getter と setter を追加したものです。

In [1]:
'''Dog クラス'''
class Dog():
     
    '''初期化メソッド'''
    def __init__(self):
        self.__name = "John" #非公開変数 __name
        
    '''クラスブロック内で非公開変数を使ったメソッドを作ります。'''
    def dogname(self):
        return self.__name
     
    '''nameプロパティのgetter'''
    @property
    def name(self): #プロパティ名は name です。
        return self.__name #__nameの値を返す。
     
    '''nameプロパティのsetter'''
    @name.setter
    def name(self, value): #getter と同じプロパティ名にします。
        self.__name = value #__nameの値を変更する

getter と setter が追加されていますね。これによって、非公開変数を外部から参照したり、変更したりすることが可能になります。このコードを、『class_dog3.py』ファイルとして保存します。

それでは、getter と setter を使って、Dog クラスの非公開変数 __name を操作してみましょう。

次のコードをご覧ください。

まずは、Dog クラスのインスタンスを作ります。

In [1]:
'''class_dog3 ファイルから Dog クラスをインポートします。'''
from class_dog3 import Dog
 
'''Dog クラスのインスタンスを作ります。'''
dog = Dog()

それでは、getter で、プロパティ(インスタンス変数)を参照してみましょう。

In [2]:
'''getter でインスタンス変数を確認してみましょう。'''
dog.name #メソッドと違い getter には () は不要です。
Out[2]:
'John'

このように、エラーにならずに値を確認することができます。setter を使うと、プロパティ(インスタンス変数)の値を変更することができます。

以下をご覧ください。

In [3]:
'''setterを使うとインスタンス変数の値を変更することができます・'''
dog.name = "Sandy"
 
'''getterで確認しましょう。'''
dog.name
Out[3]:
'Sandy'

2.2. property() 関数を使って getter と setter を指定する

上の例では、@property を使って、アクセサ(getterとsetter)を作りました。これとは別に、property() 関数によって作る方法もあります。

次のコードをご覧ください。

In [1]:
'''クラスのブロックの中では、非公開変数も自由に使うことができます。'''
class Dog():
     
    '''初期化メソッド'''
    def __init__(self):
        self.__name = "John" #非公開変数 __name
        
    '''クラスブロック内で非公開変数を使ったメソッドを作ります。'''
    def dogname(self):
        return self.__name
    
    '''get_nameメソッド'''
    def get_name(self): #get_nameメソッドを作ります。
        return self.__name
     
    '''set_nameメソッド'''
    def set_name(self, value): #set_nameメソッドを作ります。
        self.__name = value

    '''property()関数で、get_nameメソッドとset_nameメソッドをプロパティ化します。'''
    name = property(get_name, set_name)

3つのメソッドが定義されていますが、最後に、porperty()関数を使って、get_name() メソッドと set_name() メソッドをプロパティ化しています。変数 name に代入されているので、これらは、’インスタンス.name’ で使うことができます。

このファイルを、『class_dog4.py』と名付けて保存します。

それでは、以下をご覧ください。

In [2]:
from class_dog4 import Dog
dog = Dog()
dog.name = "Mary"
dog.name
Out[2]:
'Mary'

2.3. リードオンリーの非公開変数を作る

リードオンリー(外部から参照は可能だが変更は不可能)の変数は、getter プロパティだけを用意することによって、作ることができます。

次のコードをご覧ください。

In [1]:
'''Item クラス'''
class Item:
    
    '''初期化メソッド'''
    def __init__(self, name, price):
        self.__data = {"name":name, "price":price} #辞書型のオブジェクト
    
    '''辞書の"name"キーの値は参照と変更が可能にします。'''
    @property
    def name(self):
        return self.__data["name"]
    
    @name.setter
    def name(self, value):
        self.__data["name"] = value
    
    '''辞書の"price"キーはgetterメソッドしか書かないのでリードオンリーになります。'''
    @property
    def price(self):
        return self.__data["price"]

このコードでは、辞書型オブジェクトの __data のうち、”name”キーの値は参照も変更も可能で、”price”キーの値は参照のみ可能(リードオンリー)になっています。

実際に見ていきましょう。まずは、インスタンスを作ります。

In [3]:
'''Itemクラスのインスタンスを作ります。'''
watch = Item("Rolex", 100000)

次のように “name” キーの値に対しては、getter と setter の両方のプロパティがあるので、参照も変更も可能です。

In [4]:
'''インスタンス変数の name は、getterとsetterの両方のプロパティが用意されているので、読み取りも変更も可能です。'''
watch.name = "Omega" #nameの値を変更します。
watch.name #確認しましょう。
Out[4]:
'Omega'

しかし “price” キーの値に関しては、getter プロパティのみなので、リードオンリーになります。

In [7]:
'''インスタンス変数の price は getter のみなので、読み取りのみ可能(リードオンリー)です。'''
watch.price = 50000 #setterプロパティがないのでエラーになります。 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-2d1cc6b980ec> in <module>()
      1 '''インスタンス変数の price は getter のみなので、読み取りのみ可能(リードオンリー)です。'''
----> 2 watch.price = 50000 #setterプロパティがないのでエラーになります。
AttributeError: can't set attribute

なお、同じコードを property() 関数を使って書くと次のようになります。

In [8]:
'''Item クラス'''
class Item:
    
    '''初期化メソッド'''
    def __init__(self, name, price):
        self.__data = {"name":name, "price":price} #辞書型のインスタンス
    
    '''get_name メソッドを作ります。'''
    def get_name(self):
        return self.__data["name"]
    
    '''set_name メソッドを作ります。'''
    def set_name(self, value):
        self.__data["name"] = value
    
    '''get_price メソッドを作ります。'''
    def get_price(self):
        return self.__data["price"]
    
    '''property()関数でプロパティ化します。'''
    name = property(get_name, set_name) #二つのメソッドを name プロパティ化します。
    price = property(get_price) #インスタンス変数priceに関しては、get_priceのみプロパティ化します。

3. まとめ

改めて、重要なポイントを振り返ってみましょう。

  • クラスの変数の前に、__ をつけることによって非公開変数にすることができる。
  • 非公開変数は、クラスブロック内でのみ参照することができる。
  • 外部からプロパティ(クラスの変数)にアクセスするにはアクセサ(getter と setter)を使う。
  • アクセサを作るには、@property デコレータか、property()関数を使う。
  • getter のみ指定することで、そのプロパティをリードオンリーにすることができる。

保守性も拡張性も求められる仕事をする時には、必須のものなので、ぜひ、参考にして頂ければ嬉しく思います。



よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメント一覧 (1件)

  • propertyについてわかりやすく解説していただいてありがとうございます。理解が深まりました!
    少し修正した方がわかりやすいと思ったので、残させていただきます。
    非公開変数の説明のところですが、実際にはマングリングしているだけなので._Dog__nameでアクセスできます。
    完結にするため省いたのだと思いますが、説明があったほうが理解が深まるかなと初心者目線からおもいました。

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

目次
閉じる