Django Python

[Python基礎]Pythonの特殊メソッドを理解してコードを読めるようになろう!

かるでね

こんにちは!CryptoGamesというブロックチェーンゲーム企業でエンジニアをしているかるでねです!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

このブログ以外でも情報発信しているので、よければ他の記事も見ていってください。

今回はPythonの「特殊メソッド」について解説していきます。

Pythonの実務でよく他の人が書いたコードを読むことがあります。

その際結構な頻度で「特殊メソッド」が使われています。

慣れていないと何をしているのかわからず時間だけが過ぎていく可能性があります。

それを防ぐためにこの記事で「特殊メソッド」に慣れて、他の人が書いたコードをスラスラ読めるようになりましょう!

Pythonの基礎はあらかたマスターした!

Pythonのクラスの知識を深めたい。

Pythonの特殊メソッドってなんだろう?

他の人のコードを読めるようになりたい。

このような人に役に立てれば幸いです!

前置きは早々に本題に入っていきましょう。

この記事はPythonの「クラス」の理解をしておいた方が読みやすいです。「クラス」の理解が怪しいかたは以下を参考に学習してみてください。

はじめに

Pythonの「特殊メソッド」はめちゃくちゃたくさんあります。

その中でもよく使われるものを中心に紹介していきます。

もし今回紹介する以外の「特殊メソッド」を使いたい方は、公式ドキュメントを参考にしたりググってみてください。

また、この記事では丁寧な解説を心がけているので、同じようなコードが繰り返されます。

しつこく感じる方もいるかもしれませんが、初学者を対象に書いているのでご了承ください。

__new__

インスタンス生成。

概要

__new__は「インスタンスオブジェクト」が生成される前に呼び出されます。

インスタンスオブジェクト」とは、クラスを呼び出すことだと思ってもらえれば大丈夫です。

selfオブジェクトをインスタンス化し、第1引数clsにクラスオブジェクトが代入されます。

変更できないオブジェクトを変更したい」場面で使用されます。

では早速みていきましょう。

class Cardene:
    def __new__(cls):
        print("__new__")
        print(f"cls: {cls}")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__")
        print(f"self: {self}")
        
cardene = Cardene()

2行目で定義して、引数にclsを取っています。

出力を確認しましょう。

__new__
cls: <class '__main__.Cardene'>
__init__
self: <__main__.Cardene object at 0x10944cfd0>

__new__の方はクラス自体で、次章で解説する__init__はクラスオブジェクトになっているのが確認できますね。

インスタンス生成しない

ちなみにインスタンスを生成しないと__init__が呼ばれません。

確認してみましょう。

class Cardene:
    def __new__(cls):
        print("__new__")
        print(f"cls: {cls}")
        # return super().__new__(cls)
    
    def __init__(self):
        print("__init__")
        print(f"self: {self}")
        
cardene = Cardene()

5行目の部分をコメントアウトしました。

__new__
cls: <class '__main__.Cardene'>

先ほど呼ばれていた__init__が呼ばれていませんね。

このように__new__でインスタンスを生成していないと__init__が呼ばれないので注意しましょう。

__new__を定義しない場合は自動でインスタンスが生成されます。)

イミュータブルオブジェクトを変更

イミュータブル」とは、「変更できない」という意味です。

タプルなど一度定義した後に変更できないオブジェクトのことを指します。

通常クラスに渡された「イミュータブル」なオブジェクトは変更することができません。

まずはタプルから確認してみましょう。

numbers = (1, 2, 3)

print(numbers)
# (1, 2, 3)

ではこのタプルに変更を加えていきましょう。

numbers = (1, 2, 3)

numbers[1] = 4

print(numbers)
# TypeError: 'tuple' object does not support item assignment

想定通りエラーになりましたね。

では__init__ないで変更を加えてみましょう。

class Cardene:
    def __init__(self, num_tuple):
        self.num_tuple = num_tuple
        print(num_tuple)
        self.num_tuple[1] = 4
        
cardene = Cardene((1, 2, 3))

5行目で先ほど同様4を代入しようとしています。

TypeError: 'tuple' object does not support item assignment

先ほどと同じエラーが出ましたね。

では__new__を使って解決していきましょう。

class Cardene(tuple):
    def __new__(cls, num_tuple):
        self = tuple.__new__(cls, (
            num_tuple[0], 4, num_tuple[2]
        ))
        print(self)
    
cardene = Cardene((1, 2, 3))

4行目でタプルを再定義しています。

(1, 4, 3)

変更できましたね!

いやいや再定義してるじゃん!それなら別に__init__でもできるじゃん!

と思った方は鋭い!

ただこのような感じだったらどうでしょうか?

class Cardene(tuple):
    def __new__(cls, num_tuple):
        self = tuple.__new__(cls, (
            num_tuple[0], 4, num_tuple[2]
        ))
        print(self)
        
    def __init__(self, num_tuple):
        self.num_tuple = num_tuple
        print(num_tuple)
        
    
cardene = Cardene((1, 2, 3))

先ほど同様__new__を定義して、さらに__init__も付け足してみました。

(1, 4, 3)

しっかり出力できましたね。

何が言いたいかというと、__init__の時点でタプルの中身が変わっていますよね?

そのため実質タプルの中身を変更できたということです。

少し難しいですがこのように使用するので頭の片隅に入れておきましょう。

__init__

インスタンス初期化。

前章でもみてきましたが、Pythonで「クラス」を使用する際にほぼ必須で使われる「特殊メソッド」です。

役割としてはインスタンスの初期化を行なってくれます。

インスタンスを複数生成した場合に、前の値が残っていては使いづらくて仕方がありません。

そのようなことが起きないように初期化を行なってくれます。

では早速確認してみましょう。

class Cardene:
    def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        
cardene = Cardene(10, 20)
print(f"num1: {cardene.num1}")
print(f"num2: {cardene.num2}")

# num1: 10
# num2: 20

2行目で__init__を定義して、引数として受け取っているnum1num2をそれぞれself.num1self.num2に入れています。

このselfはインスタンス自身のことで、今回の場合はself=cardeneと思って貰えば理解しやすいと思います。

出力でもcardene.~と指定することで出力できていますね。

インスタンス化される度に値が変わることを確認しましょう。

class Cardene:
    def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        
    
cardene = Cardene(10, 20)
print(f"num1: {cardene.num1}")
print(f"num2: {cardene.num2}")

cardene2 = Cardene(1000, 2000)
print(f"num1: {cardene2.num1}")
print(f"num2: {cardene2.num2}")

# num1: 10
# num2: 20
# num1: 1000
# num2: 2000

ちゃんと値が変わっていますね!

__del__

インスタンス破壊時に呼び出し。

こちらは前章までの「生成時」ではなく、「破壊時」に呼び出されます。

デストラクタ」と呼ばれているのでこちらも一応覚えておきましょう。

滅多に使われませんが一応確認しましょう。

class Cardene:
    def __init__(self, num):
        self.num = num
        
    def __del__(self):
        print("インスタンスが破壊されたよ!")
        
cardene = Cardene(10)
print(f"num: {cardene.num}")

del cardene

5行目で「デストラクタ」を定義して、6行目でどんな処理をするかを書いています。

11行目でインスタンスを破壊しています。

num: 10
インスタンスが破壊されたよ!

ちゃんと__del__で定義して処理が実行されていますね。

__str__

文字列呼び出し。

__str__インスタンスを出力した際に呼び出されます。

まずは__str__を定義していない方から確認してみましょう。

class Cardene:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
cardene = Cardene("cardene", 20)
print(cardene)

# <__main__.Cardene object at 0x10b3dbaf0>

インスタンスオブジェクト情報が出力されいますね。

__str__を定義するとこの出力が変わります。

ではみてみましょう。

class Cardene:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __str__(self):
        return f"私の名前は{self.name}です。\n年齢は{self.age}です。"
    
cardene = Cardene("cardene", 20)
print(cardene)

6行目と7行目で__str__を定義しています。

では出力を確認してみましょう。

私の名前はcardeneです。
年齢は20です。

先ほどはオブジェクト情報が出力されていましたが、今回は__str__で定義した文字列が出力されていますね。

このようにインスタンスをprint()で出力した際に表示されるものを設定できます。

__repr__

文字列を呼び出し(型を戻せる)。

この__repr__は先細野__str__とほとんど同じです。

まずはこのreprがどういったものなのかを確認してきましょう。

rper

import datetime

today = datetime.datetime.today()

print(str(today))
print(type(str(today)))

print(repr(today))
print(type(repr(today)))

strの出力とrperの出力を比較していきます。

2022-02-25 12:13:30.371320
<class 'str'>
datetime.datetime(2022, 2, 25, 12, 13, 30, 371320)
<class 'str'>

どちらもデータ型はstrですが出力が違いますね。

rperの方はdatetime.datetimeと余分なものがついています。

これは型をもとに戻す際に役立ちます。

import datetime

today = datetime.datetime.today()

print(eval(repr(today)))
print(type(eval(repr(today))))

print(eval(str(today)))
print(type(eval(str(today))))

evalというものを使用して型をもとに戻してみます。

2022-02-25 12:15:47.573769
<class 'datetime.datetime'>
...
SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers

rperの方はデータ型がdatetimeに戻っていますが、strの方はデータ型をモドに戻せずエラーになっています。

このようにデータ型をもとに戻せるということを覚えておきましょう。

__rper__の確認

では本題に戻って__rper__をみていきましょう。

この__rper__は主にJupyter Notebookなどを使用している際に使われます。

どのように使われているのかみていきましょう。

class Cardene:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __str__(self):
        return f"str: 私の名前は{self.name}です。\n年齢は{self.age}です。"
    
    def __repr__(self):
        return f"repr: 私の名前は{self.name}です。\n年齢は{self.age}です。"
    
cardene = Cardene("cardene", 20)
print(cardene)
cardene

6行目と7行目で__str__を定義し、9行目と10行目で__rper__を定義しています。

13行目と14行目の出力部分はそれぞれprint()で囲ったものと、囲んでいないものの2つがあります。

では出力を確認していきましょう。

str: 私の名前はcardeneです。
年齢は20です。
repr: 私の名前はcardeneです。
年齢は20です。

どちらも出力できましたね。

ちなみになのですが、Jupyter Notebookではprint()で囲わなくても出力されます。

__rper__print()を使っても使わなくても呼び出すことができます。

補足としては、__str__の方が__rper__よりも優先して呼び出されます。

比較

次に数値の比較をした際に呼び出される「特殊メソッド」についてみていきましょう。

数値の比較とは、<=のことです。

__lt__

より小さい。

less thanの略で、「〜より小さいか」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __lt__(self, other):
        print("less than")
        return self.num < other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 < cardene_20)
less than
True

<で2つのインスタンスを比較することで、__lt__が呼び出されていますね。

__le__

その値以下。

less than or equal の略で、「〜以下か」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __le__(self, other):
        print("less than or qual")
        return self.num <= other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 <= cardene_20)
less than or qual
True

<=で2つのインスタンスを比較することで、__le__が呼び出されていますね。

__eq__

等しい。

equalの略で、「〜と等しいか」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __eq__(self, other):
        print("equal")
        return self.num == other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 == cardene_20)
equal
False

==で2つのインスタンスを比較することで、__eq__が呼び出されていますね。

__ne__

等しくない。,

not equalの略で、「〜と等しくないか」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __ne__(self, other):
        print("not equal")
        return self.num != other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 != cardene_20)
not equal
True

!=で2つのインスタンスを比較することで、__ne__が呼び出されていますね。

__gt__

より大きい。

greater thanの略で、「〜より大きいか」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __gt__(self, other):
        print("greater than")
        return self.num > other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 > cardene_20)
greater than
False

>で2つのインスタンスを比較することで、__gt__が呼び出されていますね。

__ge__

その値以上。

greater than or equalの略で、「〜以上か」を判定してくれます。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __ge__(self, other):
        print("greater than or equal")
        return self.num >= other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 >= cardene_20)
greater than or equal
False

<=で2つのインスタンスを比較することで、__ge__が呼び出されていますね。

まとめ

まとめとして一気に確認してみましょう。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __lt__(self, other):
        print("less than")
        return self.num < other.num

    def __le__(self, other):
        print("less than or qual")
        return self.num <= other.num
    
    def __eq__(self, other):
        print("equal")
        return self.num == other.num
    
    def __ne__(self, other):
        print("not equal")
        return self.num != other.num
    
    def __gt__(self, other):
        print("greater than")
        return self.num > other.num
    
        
    def __ge__(self, other):
        print("greater than or equal")
        return self.num >= other.num
    
cardene_10 = Cardene(10)
cardene_20 = Cardene(20)

print(cardene_10 < cardene_20)
print(cardene_10 <= cardene_20)
print(cardene_10 == cardene_20)
print(cardene_10 != cardene_20)
print(cardene_10 > cardene_20)
print(cardene_10 >= cardene_20)
less than
True
less than or qual
True
equal
False
not equal
True
greater than
False
greater than or equal
False

ちゃんと呼び出せましたね!

演算

この章では演算を行う「特殊メソッド」についてみていきます。

前章と同じように使用するのでそこまで難しくないはずです!

早速みていきましょう。

__add__

足し算。

足し算を行う「特殊メソッド」です。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __add__(self,other):
        print("足し算")
        return self.num + other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 + cardene2)
足し算
30

ちゃんと足し算できていますね。

__sub__

引き算。

引き算を行う「特殊メソッド」です。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __sub__(self,other):
        print("引き算")
        return self.num + other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 - cardene2)
引き算
-10

ちゃんと引き算できていますね。

__mul__

掛け算。

掛け算を行う「特殊メソッド」です。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __mul__(self,other):
        print("掛け算")
        return self.num * other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 * cardene2)
掛け算
200

ちゃんと掛け算できていますね。

__truediv__

割り算(四捨五入なし)。

割り算を行う「特殊メソッド」です。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __truediv__(self,other):
        print("割り算")
        return self.num / other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 / cardene2)
割り算
0.5

ちゃんと割り算できていますね。

__floordiv__

割り算(四捨五入あり)。

割り算を行う「特殊メソッド」です。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __floordiv__(self,other):
        print("割り算")
        return self.num // other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 // cardene2)
割り算
0

ちゃんと割り算できていますね。

まとめ

最後に演算を行う「特殊メソッド」をまとめて復習しましょう。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __add__(self,other):
        print("足し算")
        return self.num + other.num
    
    def __sub__(self,other):
        print("引き算")
        return self.num + other.num
    
    def __mul__(self,other):
        print("掛け算")
        return self.num * other.num
    
    def __truediv__(self,other):
        print("割り算")
        return self.num / other.num
        
    def __floordiv__(self,other):
        print("割り算")
        return self.num // other.num
    
cardene1 = Cardene(10)
cardene2 = Cardene(20)

print(cardene1 + cardene2)
print(cardene1 - cardene2)
print(cardene1 * cardene2)
print(cardene1 / cardene2)
print(cardene1 // cardene2)
足し算
30
引き算
30
掛け算
200
割り算
0.5
割り算
0

ビット演算子

この章では「ビット演算子」についてみていきます。

ビット演算子」とは01の組み合わせで出力が決まるものです。

1は正、0は偽と認識することもできます。)

and

まずはandから見ていきましょう。

andは値が全て1の時1を返し、1つでも0が含まれていたら0を返します。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __and__(self,other):
        print("and")
        return self.num & self.num
    
cardene1 = Cardene(1)
cardene2 = Cardene(0)

print(cardene1 & cardene2) # 1 & 0
print(cardene1 & cardene1) # 1 & 1
print(cardene2 & cardene2) # 0 & 0

12行目から14行目までの複数のパターンで試してみます。

and
0
and
1
and
0

ちゃんと説明した通りの出力になっていますね。

or

まずはorから見ていきましょう。

orは1つでも0が含まれていたら1を返します。

class Cardene:
    def __init__(self,num):
        self.num = num
        
    def __or__(self,other):
        print("or")
        return self.num | self.num
    
cardene1 = Cardene(1)
cardene2 = Cardene(0)

print(cardene1 | cardene2) # 1 & 0
print(cardene1 | cardene1) # 1 & 1
print(cardene2 | cardene2) # 0 & 0

12行目から14行目までの複数のパターンで試してみます。

or
1
or
1
or
0

ちゃんと説明した通りの出力になっていますね。

最後に

今回はPythonの「特殊メソッド」についてみてきました。

GitHubに上がっているコードや他の人が書いたコードを見る際に「特殊メソッド」をよく見かけます。

この機会にしっかり「特殊メソッド」について理解して、他の人が書いたコードを読んでいる際に「特殊メソッド」が出てきても楽々読み進められるようにしましょう!

普段はPythonやブロックチェーンメインに情報発信をしています。

Twiiterでは図解でわかりやすく解説する投稿をしているのでぜひフォローしてくれると嬉しいです!

-Django, Python
-,