精読「独習Python」(オブジェクト指向構文 応用)
独習Python
「独習Python」は、初心者から中級者までを対象に、Pythonの基礎から応用までを体系的に学べる入門書で、豊富な例題と練習問題を通じて実践的なスキルを身につけられる一冊です。
関連記事
例外処理
例外クラスの型
例外をクラス型で指定する方法は次のようになる
try:
# エラーが発生するコード
x = 1 / 0 # ZeroDivisionErrorが発生
except ZeroDivisionError as e:
print(f"ゼロで割り算しました: {e}")
except Exception as e:
print(f"その他のエラーが発生: {e}")
終了処理を定義するーfinally節
Pythonのtry...except
構文におけるfinally
節は、例外の有無にかかわらず必ず実行され、主にリソースの後始末(クリーンアップ)に使用される。finally
節は1つしか指定でき、try
節でリソースを解放し忘れないようにするために重要。例えば、ファイル操作などでfile.close()
をfinally
で呼び出すことで、例外発生時でもリソースを確実に解放できる。
また、with...as
構文を使うと、リソースの解放が自動的に行われるため、finally
よりシンプルに記述できる。with
節内で発生した例外は、コンテキストマネージャーの__exit__
メソッドで処理され、return True
を使うとその後の処理が中断され、False
にすると例外が再送出される。
finally
節の実行順序は、基本的にtry→(except)→finally
。finally
は、try
節でreturn
やbreak
/continue
が発生しても必ず実行され、その後の処理に影響を与える。特に、finally
節内でreturn
があった場合、finally
のreturn
が優先される。
例外をスローするーraise命令
raise
命令は、Pythonで例外を発生させるための命令。これにより、プログラムの実行中に意図的にエラーを発生させ、エラーハンドリングの仕組みを通じて適切に処理することができる。
try:
# 何らかの処理
raise ValueError("エラーが発生しました")
except ValueError as e:
print("エラー:", e)
raise # 現在の例外を再送出
独自の例外クラス
ythonでは、独自の例外クラスを作成して、アプリケーション固有のエラーハンドリングを行うことができる。独自の例外クラスは、標準の例外クラスを継承することで作成する。これにより、標準的なエラーハンドリング機構を利用しつつ、特定のエラーを明示的に区別することができる
class InvalidUserInputError(Exception):
def __init__(self, message="無効な入力がありました"):
# Exceptionの__init__メソッドを呼び出し
super().__init__(message)
self.message = message
# 使用例
def get_user_input(value):
if value < 0:
raise InvalidUserInputError("入力値は負の数ではありません。")
return value
try:
get_user_input(-1)
except InvalidUserInputError as e:
print(f"エラー: {e}")
特殊メソッド
オブジェクトの文字列表現を取得する
__str__
メソッドはエンドユーザー向けのわかりやすい文字列を返し、print(obj) で呼ばれる。オブジェクトの特徴的な情報を簡潔に表現する。
# Personクラスを定義
class Person:
# コンストラクタ: インスタンスを初期化
def __init__(self, firstname, lastname):
self.firstname = firstname # 姓をインスタンス変数に格納
self.lastname = lastname # 名をインスタンス変数に格納
# __str__メソッドを定義: オブジェクトの文字列表現を返す
def __str__(self):
return f"{self.firstname} {self.lastname}" # フルネームを返す
# Personクラスのインスタンスを作成
person = Person("太郎", "山田")
# print関数でpersonオブジェクトを出力
print(person) # 結果: 太郎 山田
__repr__
メソッドは開発者向けで、オブジェクトを再生成可能な文字列を返し、eval() などで使用されます。
class Person:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __repr__(self):
return f"Person('{self.firstname}', '{self.lastname}')"
person = Person("太郎", "山田")
print(repr(person)) # 結果: Person('太郎', '山田')
repr(person)
で、オブジェクトの再生成が可能な文字列を表示する。eval(repr(person))
を使えば元のオブジェクトを再生成できる。
__str__
はユーザー向け、__repr__
は開発者向けに使い分ける
オブジェクト動詞が等しいかどうかを判定する
__eq__
メソッドは、オブジェクト同士の同値性を判定するためのメソッドで、==
演算子が使われると呼び出される。デフォルトでは、object クラスが提供する __eq__
は同一性(参照が同じかどうか)を確認するだけ。意味的に同じかどうかを判定するためには、__eq__
をオーバーライドして比較基準を定義する必要がある。
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
# 型が異なる場合は False
if not isinstance(other, Person):
return False
# name と age が一致する場合に True
return self.name == other.name and self.age == other.age
# 使用例
person1 = Person('太郎', 30)
person2 = Person('太郎', 30)
person3 = Person('次郎', 25)
# 同じ名前と年齢なので同値
print(person1 == person2) # True
# 名前が異なるので同値ではない
print(person1 == person3) # False
オブジェクトのハッシュ値を取得する
__hash__
メソッドは、オブジェクトのハッシュ値を返すために使われ、dict
や set
での検索に利用される。同値のオブジェクトは同じハッシュ値を返すべきですが、異なるオブジェクトが同じハッシュ値を返しても問題ない。重要なのは、オブジェクトのハッシュ値は変更されないこと。変更可能な変数を使うと、検索時にエラーが発生することがある。そのため、__hash__
を実装する際は、不変の変数を使用するべき。
class Person:
def __init__(self, firstname, lastname):
self.firstname = firstname
self.lastname = lastname
def __eq__(self, other):
return (self.firstname, self.lastname) == (other.firstname, other.lastname)
def __hash__(self):
return hash((self.firstname, self.lastname))
# ハッシュ表で利用
person1 = Person("太郎", "山田")
person2 = Person("太郎", "山田")
person3 = Person("次郎", "山田")
# 同じハッシュ値を持つか確認
print(hash(person1)) # person1のハッシュ値
print(hash(person2)) # person2のハッシュ値
print(hash(person3)) # person3のハッシュ値
# dictを使った例
person_dict = {person1: "社員A", person3: "社員B"}
print(person_dict[person2]) # person1とperson2は同じハッシュ値なので、"社員A"が返る
オブジェクト同士を比較する
演算子をオーバーロード(再定義)することで、カスタムクラスのオブジェクトに対して四則演算や比較を直感的に行えるようにできる。これを行うには、__add__
(加算)、__sub__
(減算)などの特殊メソッドを定義する。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# + 演算子のオーバーロード
def __add__(self, other):
if isinstance(other, Point):
return Point(self.x + other.x, self.y + other.y)
else:
raise TypeError("Can only add Point to Point")
def __str__(self):
return f"({self.x}, {self.y})"
# 使用例
p1 = Point(1, 2)
p2 = Point(3, 4)
# Pointオブジェクト同士を加算
p3 = p1 + p2
print(p3) # 結果: (4, 6)
データ型を変換する
__int__
と __float__
メソッドを使って、Coordinate クラスのインスタンスを整数や浮動小数点数に変換する例。
-
__float__
メソッドは、座標の原点からの距離を計算し、浮動小数点数として返す。 -
__int__
メソッドは、__float__
を呼び出して得た距離を整数に変換して返す。
これにより、Coordinate
オブジェクトに対して float()
や int()
関数を使って数値表現を取得できるようになる
import math
class Coordinate:
def __init__(self, x, y):
self.x = x
self.y = y
def __int__(self):
return int(self.__float__())
def __float__(self):
return math.sqrt(self.x ** 2 + self.y ** 2)
# 使用例
c = Coordinate(1, 2)
print(float(c)) # 結果:2.23606797749979
print(int(c)) # 結果:2
オブジェクトの真偽を判定する
オブジェクトの真偽判定は以下のルールで行われる
-
__bool__
が定義されていれば、その値がTrue
かFalse
になる。 -
__bool__
がない場合、__len__
が使われ、長さがゼロならFalse
、それ以外はTrue
。 - 両方がない場合、オブジェクトは常に
True
。
アトリビュートの取得/設定の挙動をカスタマイズする
アトリビュートの取得・設定の挙動をカスタマイズするためには、__getattr__
、__getattribute__
、__setattr__
、__set__
などの特殊メソッドを実装する。これにより、オブジェクトがアトリビュートにアクセスする際の動作を制御できる。
-
__getattr__
(アトリビュートが存在しないときに呼ばれる)
__getattr__
は、オブジェクトがアクセスするアトリビュートが存在しない場合に呼び出される。このメソッドを使って、動的にアトリビュートの値を生成したり、デフォルト値を返したりできる。 -
__getattribute__
(すべてのアトリビュートアクセスで呼ばれる)
__getattribute__
は、すべてのアトリビュートアクセス時に呼び出されるため、アトリビュートが存在するかどうかに関係なく実行される。通常は__getattr__の方が優先されますが、__getattribute__でのアクセスをカスタマイズできる。 -
__setattr__
(アトリビュートが設定されるときに呼ばれる)
__setattr__
はアトリビュートに値を設定する際に呼び出される。ここで、設定の前に検証を行ったり、特殊な処理を加えることができる。 -
__set__
(プロパティで使用される)
__set__
は、@propertyデコレータやカスタムのプロパティに使用される。これを使うと、アトリビュートが設定される際にカスタマイズしたロジックを実行できる。
class Person:
def __init__(self, name, age):
self.name = name # __setattr__が呼ばれます
self.age = age # __setattr__が呼ばれます
# アトリビュートの設定時にカスタマイズ(例:名前が空文字だった場合にデフォルト値を設定)
def __setattr__(self, name, value):
if name == "name" and not value:
value = "Unknown" # 名前が空ならデフォルト値
super().__setattr__(name, value)
# アトリビュートの取得時にカスタマイズ(例:年齢が負の場合にエラーメッセージを返す)
def __getattr__(self, name):
if name == "age":
raise ValueError("Age is not accessible directly!")
return super().__getattr__(name)
# アトリビュートの取得時にカスタマイズ(例:設定した名前を小文字にする)
def __getattribute__(self, name):
if name == "name":
return super().__getattribute__(name).lower() # 名前を小文字で返す
return super().__getattribute__(name)
# 使用例
p = Person("Alice", 30)
print(p.name) # 結果: alice
print(p.age) # 結果: 30(Error: Age is not accessible directly!は発生しません)
p.name = "" # 名前が空ならデフォルトの'Unknown'を設定
print(p.name) # 結果: unknown
ディスクリプター
Pythonのディスクリプターは、属性のアクセス、設定、削除を制御するためのオブジェクト。以下のメソッドを実装することで作成できる
-
__get__
: 属性の取得時に呼ばれる -
__set__
: 属性の設定時に呼ばれる -
__delete__
: 属性の削除時に呼ばれる
class Descriptor:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
print(f"Getting {self.name}")
return instance.__dict__.get(self.name, None)
def __set__(self, instance, value):
print(f"Setting {self.name} to {value}")
instance.__dict__[self.name] = value
def __delete__(self, instance):
print(f"Deleting {self.name}")
del instance.__dict__[self.name]
class MyClass:
attr = Descriptor("attr")
obj = MyClass()
obj.attr = 10 # Setting attr to 10
print(obj.attr) # Getting attr
del obj.attr # Deleting attr
インスタンスを関数的に呼び出す
Pythonでインスタンスを関数的に呼び出すには、__call__
メソッドを定義する。このメソッドを実装することで、インスタンスを関数のように呼び出すことができる。
class CallableClass:
def __init__(self, value):
self.value = value
def __call__(self, x):
return self.value * x
# インスタンスを関数のように呼び出す
instance = CallableClass(5)
result = instance(10) # 5 * 10 = 50
print(result) # 50
データクラス
データクラスの基本
簡単にクラスを定義して、主にデータを格納するためのもの。データクラスを使うと、__init__
や__repr__
などの特殊メソッドを自動的に生成してくれる。
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
# インスタンスを作成
person = Person(name="Alice", age=30)
# 属性にアクセス
print(person.name) # Alice
print(person.age) # 30
dataclassデレコーターの主なオプション
オプション | 説明 | デフォルト値 |
---|---|---|
init |
__init__ メソッドの自動生成を有効/無効にする |
True |
repr |
__repr__ メソッドの自動生成を有効/無効にする |
True |
eq |
__eq__ メソッドの自動生成を有効/無効にする |
True |
order |
__lt__ , __le__ , __gt__ , __ge__ メソッドの自動生成を有効/無効にする |
False |
unsafe_hash |
__hash__ メソッドの自動生成を有効/無効にする |
False |
frozen |
属性を変更不可にする(イミュータブル) | False |
フィールドのカスタマイズ
dataclassでは、フィールドに対してカスタマイズを行うためにfield()
関数を使用する。field()
を使うことで、属性のデフォルト値やその他のオプションを制御できる。
@dataclass
class Person:
name: str
age: int
_id: int = field(init=False) # __init__メソッドには含めない
イミュータブルなクラス
dataclassを使ってイミュータブルなクラスを作成する方法は、frozen=True
オプションを使用すること。これにより、インスタンスが生成された後にフィールドの値を変更できなくなる。frozen=True
を指定すると、Pythonは__setattr__
メソッドを無効化し、オブジェクトのフィールドに対する変更を防ぐ。
from dataclasses import dataclass
@dataclass(frozen=True)
class Person:
name: str
age: int
# インスタンスの生成
p = Person(name="Alice", age=30)
# フィールドへの変更を試みるとエラーが発生します
# p.age = 31 # AttributeError: can't set attribute
メタクラス
メタクラスは、クラスを作成するためのクラス。通常、Pythonではクラスを使ってオブジェクトを作成しますが、メタクラスはそのクラスを作成するために使われる。つまり、クラス自体を操作するためのクラス。
クラスがオブジェクトの設計図であるのに対して、メタクラスはクラスの設計図。
メタクラスの基本
通常のクラスは、class
キーワードを使って定義しますが、メタクラスは type
クラス(もしくは他のカスタムメタクラス)を使って定義する。メタクラスを利用することで、クラスがどのように生成されるか、またはクラスの振る舞いをカスタマイズできる。
メタクラスの使い方
メタクラスを作成するには、type
を継承して、__new__()
または __init__()
メソッドをオーバーライドします。__new__()
はクラスの作成時に呼ばれ、__init__()
は作成されたクラスの初期化時に呼ばれる。
メタクラスの例
# メタクラスを定義
class MyMeta(type):
def __new__(cls, name, bases, dct):
print(f"Creating class {name}")
return super().__new__(cls, name, bases, dct)
# メタクラスを使ったクラス定義
class MyClass(metaclass=MyMeta):
pass
# インスタンス生成
obj = MyClass()
メタクラスの用途
メタクラスは、以下のような用途で使用されます:
- クラスの作成時に自動的に属性やメソッドを追加する
- クラスの継承やインスタンス化のルールを変更する
- クラス定義のバリデーションを行う
- クラスのインスタンス化時に特別な処理を追加する
例:クラス定義時に自動でメソッドを追加する
class MethodAddingMeta(type):
def __new__(cls, name, bases, dct):
# クラスにメソッドを追加
def greet(self):
return "Hello from the class!"
dct['greet'] = greet
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=MethodAddingMeta):
pass
obj = MyClass()
print(obj.greet()) # "Hello from the class!"
メタクラスの実用的な使用例
メタクラスは、Pythonでフレームワークやライブラリを作成する際によく使われる。例えば、DjangoのORM(Object-Relational Mapping)や、構造化されたデータを扱うライブラリでは、メタクラスを使ってクラス定義の際に自動で設定やチェックを行っている。
まとめ
- メタクラスは、クラスを生成するためのクラス。
- メタクラスを使うことで、クラスの生成時にカスタマイズや拡張を行うことができる。
-
type
を基にしてカスタムメタクラスを定義し、クラスの動作や構造を制御することができる。
Discussion