Pythonのクラスについて学んだ #1

はじめに

この記事は、python実践入門 6章 を読み終わった後の学習記録です。
学習段階のため、間違った知識を記述している可能性があります。 そのため、あまりこの記事を鵜呑みにせずに、できるだけ公式ドキュメント もしくは 書籍を読むことを推奨します。
また、間違った記述を発見した場合は、コメントにてご指摘いただけると幸いです。(誰でもコメント可)

クラスの作成

class ClassName(InheritanceClass):
    def __init__(self, arg): # 初期化メソッド
      self.instance_var = arg # インスタンス変数の定義

    def method_name(self, arg):
        pass 
        # ...

このようにして、クラスを作成します。 命名規則(PEP8)に従い、クラス名にはキャメルケースを使用します。
メソッド名にはスネークケースを使用します。メソッドの第一引数にはselfを必ず使用します。 selfにはインスタンス自身が渡されます。
インスタンス変数には、スネークケースを使用します。  

また、(InheritanceClass)の部分は省略可能で、省略した際にはObjectClassが継承されます。

class ClassName:
    # 省略できる

__init__は特殊メソッドと言われるもので、後ほど説明します。

インスタンスの作成

instance = ClassName('value')

このようにして、インスタンスを作成します。

このようにインスタンスが作成された後__init__メソッドが呼び出されます。

引数には__init__に対応するものを渡します。
def __init__(self, arg): と定義されているので、引数は2つではないのかと思ってしまいますが、実際にはそうはなりません。 それは、先程記述した通り、selfにはインスタンス自身が自動で渡されるからです。

余談: __init__メソッド内では、主にインスタンス変数の定義が行われます。
__init__内の処理は、インスタンスの初期化を行われることを想定されているからです。
__init__メソッドの名前の由来は、initializeつまり、初期化から来ています。

メソッド呼び出し

instance.method_name('value')

このようにして、インスタンスを呼び出します。 先程の説明同様、 selfにはインスタンス自身が渡されます。

プロパティー

少し具体的なクラスを作っていきましょう。

class Triangle:
    def __init__(self, height, width):

        self.height = float(height) # 念の為float型変換
        self.width = float(width)

三角形のクラスを作成しました。
インスタンスを作成しましょう

figure = Triangle(10, -10)

print(figure.height) # -> 10
print(figure.width) # -> -10

!?
底辺が-10の三角形なんて存在してはいけないのに、存在できてしまいました。 これではいけないので、self.height, self.widthが0未満のときにエラーを起こすメソッドを作成しましょう。

class Triangle:
    def __init__(self, height, width):

        self.height = float(height) # 念の為float型変換
        self.width = float(width)
        self.check()

    def check(self) -> None:
        if self.height < 0:
            raise ValueError(f'height must be over 0.\nheight = {self.height}')
        if self.width < 0:
            raise ValueError(f'width must be over 0.\nwidth = {self.width}')

作成しました。

figure = Triangle(10, -10)
ValueError: width must be over 0.
width = -10.0

コレで一安心ですね。

figure = Triangle(10, 10)
figure.width = -10

print(figure.height) # -> 10
print(figure.width) # -> -10

嘘です。まだ、作成できてしまいます。


@property

そこで、@propertyの出番です。

class Triangle:
    def __init__(self, height, width):

        self._height = float(height) # 念の為float型変換
        self._width = float(width)
        self.check()

    @property
    def height(self):
        return self._height

    @property
    def width(self):
        return self._width

    def check(self) -> None:
        if self._height < 0:
            raise ValueError(f'height must be over 0.\nheight = {self._height}')
        if self._width < 0:
            raise ValueError(f'width must be over 0.\nwidth = {self._width}')

figure = Triangle(10, 10)

print(figure.height) # -> 10
print(figure.width) # -> 10

まず、インスタンス変数height, width_height, _widthに置き換えます。
アンダーバー_を接頭辞につけることによって、プライベートな変数ですよとアピールします。
そのため、実際にはパブリックです。

figure._height = -10

とか全然かけちゃう。個人的には、pythonのこういう所あんまり好きじゃないですね。


    @property
    def height(self):
        return self._height

このように、@propertyをつけたメソッドは、()を省いて呼ぶことができます。 こうすることにより、あたかもheight変数が存在するかのようにできます。
では、コレを用いて、areaメソッドを作成しましょう。

    @property
    def area(self):
        return self._height * self._width / 2

print(figure.area) # -> -100

あたかも、area変数が存在するかのようにできました。
@propertyをつけることによって、 ()なんて、わざわざ冗長なものを書かずにできるのはいいですね。


@*.setter

width, heightをやはり変更したいです。 どうすればいいでしょう。 そこで、@*.setterです。

    # ...
    @property
    def height(self):
        return self._height

    @height.setter
    def width(self, height: float):
        self._height = float(height)
        self.check()

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, width: float):
        self._width = float(width)
        self.check()
    # ...
figure.width = 20
print(figure.width)

figure.width = -10 # Error
ValueError: width must be over 0.
width = -10

こうすることによって、self.height = 20などの際に、@height.setterをつけたheightメソッドが呼ばれるようになりました。

こうすることによって、代入された時に任意の処理を挟むことができます。

継承、クラスメソッドなどは別の記事にまとめます。


class Triangle:
    def __init__(self, height, width):

        self._height = float(height) # 念の為float型変換
        self._width = float(width)
        self.check()

    @property
    def height(self):
        return self._height

    @height.setter
    def width(self, height: float):
        self._height = float(height)
        self.check()

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, width: float):
        self._width = float(width)
        self.check()

    @property
    def area(self):
        return self._height * self._width / 2

    def check(self) -> None:
        if self._height < 0:
            raise ValueError(f'height must be over 0.\nheight = {self._height}')
        if self._width < 0:
            raise ValueError(f'width must be over 0.\nwidth = {self._width}')

figure = Triangle(10, 10)

figure.width = 20

print(figure.height) # -> 10
print(figure.width) # -> 20
print(figure.area) # -> 100