コンテンツにスキップ

オブジェクト


1. format関数の謎

Python では、これまで学んできた def を使って関数を定義し、呼び出すことができます。例えば、以下のようなオリジナルの関数を定義できます。

def greet(name):
    return f"こんにちは、{name}さん!"

print(greet("太郎"))  # こんにちは、太郎さん!

一方で、Python には .format() のようにオブジェクトに対してメソッドを呼び出す書き方があります。

プログラム:

message = "こんにちは、{}さん!".format("太郎")
print(message)

結果:

こんにちは、太郎さん!

関数とメソッドの違い

項目 関数 (def) メソッド (オブジェクト.メソッド())
所属 独立している 特定のオブジェクトに所属している
呼び出し方 関数名(引数) オブジェクト.メソッド名(引数)
使用例 print(len("hello")) "hello".upper()
影響範囲 どこからでも呼び出せる そのオブジェクトのみに適用される

例:文字列の format() はメソッド

Python の str.format() は 文字列オブジェクトに属するメソッド です。

s = "私の名前は {} です。"
print(s.format("花子"))  # 私の名前は 花子 です。

これは s という文字列オブジェクトに対して .format() を実行しているため、 "文字列".format(値) の形になります。

一方で、関数として提供される print() などは、どんなオブジェクトにも適用できる独立した関数です。

Pythonでは全ての値がオブジェクト

Pythonでは、すべての値がオブジェクトとして扱われます。これは、数値・文字列・リスト・辞書などのデータ型だけでなく、関数やクラスまでもがオブジェクトであることを意味します。

例えば、以下のコードを見てみましょう。

x = 10
print(x.bit_length())  # 10のビット長を求めるメソッド

ここでは x は単なる整数のように見えますが、実際には int クラスのインスタンス(オブジェクト)であり、メソッド bit_length() を持っています。

また、文字列もオブジェクトです。

s = "hello"
print(s.upper())  # 文字列を大文字に変換するメソッド

このように、Pythonではデータ型ごとにオブジェクトが持つメソッドが定義されており、適切な操作を実行できます。

さらに、関数もオブジェクトなので、変数に代入したり、引数として渡したりできます。

def greet():
    print("Hello!")

f = greet
f()  # "Hello!" を出力

このように、Pythonでは「すべての値がオブジェクトである」という設計が統一されており、一貫した考え方のもとでプログラムを構築できます。


オブジェクトの型

Pythonでは、オブジェクトの型によって利用できるメソッドが決まります。 たとえば、文字列(str 型)のオブジェクトと、リスト(list 型)のオブジェクトでは、それぞれ異なるメソッドを持っています。

例:文字列 (str) のメソッド

s = "hello"
print(s.upper())
print(s.replace("h", "H"))

出力:

HELLO
Hello

upper()replace()str 型のオブジェクトにのみ使えます。

例:リスト (list) のメソッド

lst = [1, 2, 3]
lst.append(4)
print(lst)

出力:

[1, 2, 3, 4]

append()list 型のオブジェクトに使えますが、文字列には使えません。


型による制約

異なる型のオブジェクトに対応しないメソッドを呼び出すとエラーになります。

プログラム:

s = "hello"
s.append("!")

出力:

---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

/var/folders/lc/k7jt3gx9283453ptp9t9ty5m0000gn/T/ipykernel_10351/1270942352.py in <module>
        1 s = "hello"
----> 2 s.append("!")


AttributeError: 'str' object has no attribute 'append'


文字列オブジェクトが持つメソッド

以下にPythonの文字列型が持つ代表的なメソッドをまとめます。

メソッド名 説明 使用例 出力
upper() すべて大文字に変換 "hello".upper() "HELLO"
lower() すべて小文字に変換 "HELLO".lower() "hello"
capitalize() 先頭の1文字を大文字にする "python".capitalize() "Python"
title() 各単語の先頭を大文字にする "hello world".title() "Hello World"
strip() 前後の空白や改行を削除 " hello ".strip() "hello"
lstrip() 左側の空白を削除 " hello ".lstrip() "hello "
rstrip() 右側の空白を削除 " hello ".rstrip() " hello"
replace(old, new, count) 文字列を置換(countは置換回数指定) "apple apple".replace("apple", "orange", 1) "orange apple"
split(sep) 指定した区切り文字で分割(リストで返す) "a,b,c".split(",") ["a", "b", "c"]
join(iterable) 指定した区切り文字で連結 ",".join(["a", "b", "c"]) "a,b,c"
find(sub) 部分文字列の位置を検索(見つからない場合 -1 "hello".find("l") 2
count(sub) 指定した部分文字列の出現回数を返す "banana".count("a") 3
startswith(prefix) 指定した文字列で始まるか判定 "hello".startswith("he") True
endswith(suffix) 指定した文字列で終わるか判定 "hello".endswith("lo") True
isalpha() 文字列が英字のみか判定 "hello".isalpha() True
isdigit() 文字列が数字のみか判定 "123".isdigit() True
isalnum() 文字列が英数字のみか判定 "abc123".isalnum() True

使いどころのポイント

  • テキスト処理:replace(), split(), join()
  • 大文字・小文字変換:upper(), lower(), capitalize(), title()
  • 不要な空白の処理:strip(), lstrip(), rstrip()
  • 検索・判定:find(), count(), startswith(), endswith()
  • データのバリデーション:isalpha(), isdigit(), isalnum()

Pythonの文字列メソッドを活用すると、データの加工や整形が効率的に行えます!


2. オブジェクトの設計図

オブジェクトの姿を決定づける設計図

Pythonでは、すべての値(データ)はオブジェクトとして扱われます。では、オブジェクトの「型」や「ふるまい(メソッド)」を決めるものは何でしょうか? それを決定づけるのが 「設計図」 にあたる クラス(class) です。

クラスとは?
クラスとは、オブジェクトが持つ データ(属性) や 操作(メソッド) を定義する設計図のようなものです。例えば、intstr もクラスであり、それらのクラスによって、整数や文字列のオブジェクトがどのように振る舞うかが決まっています。

オブジェクトとクラスの関係

Pythonのオブジェクトは、それぞれ あるクラスに基づいて作られたインスタンス です。たとえば、次のコードを見てみましょう。

num = 42
text = "hello"

print(type(num))  # <class 'int'>
print(type(text)) # <class 'str'>

このように、42int クラスのインスタンスであり、"hello"str クラスのインスタンスです。

なぜクラスが必要なのか?

クラスを使うことで、オブジェクトがどのようなデータを持ち、どのような振る舞いをするかを統一的に管理できます。例えば、すべての文字列(str のインスタンス)は .upper() などのメソッドを使えますが、整数(int のインスタンス)には .upper() は存在しません。これは、それぞれのクラスで定義されているメソッドが異なるためです。

クラスを意識することで得られること

  • データと振る舞いを統一的に扱える(オブジェクト指向の基礎)
  • カスタムのオブジェクトを作れる(後の章で学ぶクラスの定義へ)
  • コードの見通しがよくなる(型ごとに適切なメソッドを利用できる)

次の項では、実際にクラスを定義することで、オブジェクトの設計図をどのように作るかを解説していきます。


オリジナルの設計図を作る

前の項では、オブジェクトの姿を決定づける設計図として クラス(class) の概念を紹介しました。 ここでは、実際に クラスを定義し、自分だけのオブジェクトを作る 方法を説明します。

1. クラスの定義

Pythonでは class キーワードを使ってクラスを定義します。 クラスの中には データ(属性) や 操作(メソッド) を定義できます。

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed

    def bark(self):
        print(f"{self.name}{self.breed}): ワンワン!")

ここでのポイント:

  • __init__ は コンストラクタ と呼ばれ、オブジェクトが作られたときに実行されます。
  • self.nameself.breed は インスタンス変数(属性) で、オブジェクトごとに異なる値を持ちます。
  • bark() は メソッド で、オブジェクトが行える動作を定義しています。

2. クラスからオブジェクトを作る

クラスを定義したら、それを インスタンス化(オブジェクトの生成) できます。

プログラム:

dog1 = Dog("ポチ", "柴犬")
dog2 = Dog("レオ", "ゴールデンレトリバー")

dog1.bark()
dog2.bark()

出力:

ポチ(柴犬): ワンワン!
レオ(ゴールデンレトリバー): ワンワン!

  • Dog("ポチ", "柴犬")Dog クラスの新しいオブジェクトを作成
  • bark() メソッドを呼び出すと、それぞれの namebreed に応じた動作を実行

3. クラスを使うメリット

オブジェクトの設計図を定義することで、以下のようなメリットがあります。

  • データと動作をひとまとめにできる
    namebreed というデータと bark() という動作が、Dog クラスの中にまとまっている
  • 再利用性が高まる
    Dog クラスを作れば、新しい犬のオブジェクトを簡単に作れる
  • コードの見通しがよくなる
    Dog クラスがあることで、「このオブジェクトは犬を表している」と明確になる

3. オブジェクトの落とし穴

オブジェクトのアイデンティティ

Pythonでは、全てのオブジェクトに一意の識別子(ID)が割り振られます。この識別子は、そのオブジェクトがメモリ上でどこに存在するかを示しており、オブジェクトが一意であることを証明するために使われます。id関数を使用してオブジェクトのIDを取得することができます。

1. オブジェクトのID

id() は、引数に与えたオブジェクトの 一意のID を返します。このIDは、オブジェクトがメモリ上で格納されているアドレスを示しており、オブジェクトがメモリから削除されるまで同じ値を持ち続けます。

プログラム:

a = 10
b = 10

print(id(a))
print(id(b))

出力:

140489824279120
140489824279120

上記の例では、ab は両方とも値 10 を持っていますが、Pythonの最適化機能によって、同じ整数オブジェクト 10 を指し示すため、id() が同じ値を返します。これを 整数のインターン化 と呼びます。

2. 同じオブジェクトを指す参照

同じオブジェクトを指す変数が複数ある場合、それらの変数は同じ ID を持ちます。これは、Pythonがメモリを効率的に使うために 参照渡し を行っているためです。

プログラム:

x = [1, 2, 3]
y = x

print(id(x))
print(id(y))

出力:

140489891910976
140489891910976

この例では、xy は同じリストオブジェクトを指し示しており、id(x)id(y) は同じIDを返します。

3. 異なるオブジェクトを指す参照

異なるオブジェクトを指す変数には、当然異なるIDが割り振られます。

プログラム:

a = [1, 2, 3]
b = [1, 2, 3]

print(id(a))
print(id(b))

出力:

140489894591680
140489894622336

この例では、ab は異なるリストオブジェクトを指しており、異なるIDが返されます。たとえ中身が同じであっても、異なるオブジェクトとして扱われます。

4. 同じオブジェクトであるかどうかの確認

Pythonでは is 演算子を使うことで、2つのオブジェクトが同じIDを持つか(すなわち、同じオブジェクトを指しているか)を確認することができます。

プログラム:

a = [1, 2, 3]
b = a

print(a is b)

出力:

True

この場合、ab は同じオブジェクトを指しているため、a is bTrue になります。

オブジェクトのアイデンティティのまとめ

  • ID は、Pythonのオブジェクトに一意の識別子として割り当てられるもので、id() を使って取得できます。
  • 同じオブジェクトを指す変数は同じIDを持ち、異なるオブジェクトは異なるIDを持ちます。
  • 参照渡しによって、変数はオブジェクトのメモリ内の位置を指し示します。
  • is 演算子を使うことで、2つのオブジェクトが同じID(同じオブジェクト)を持っているか確認できます。

参照

Pythonでは、変数に値を代入する際に、実際には オブジェクトへの参照 が代入されます。言い換えれば、変数自体がデータを保持するのではなく、データが格納されたメモリ上の位置(アドレス)を指し示すような形になります。この仕組みは「参照渡し」と呼ばれます。

1. 参照の基本概念

変数に値を代入すると、実際にはその値が格納されている場所(オブジェクト)を指し示す参照が変数に格納されます。以下の例を見てみましょう。

a = [1, 2, 3]
b = a  # aの参照をbに渡す

print(a)  # [1, 2, 3]
print(b)  # [1, 2, 3]
print(a is b)  # True

この場合、b = a の代入によって、ba と同じリストオブジェクトを指し示すようになっています。ここで a is bTrue になるのは、両方の変数が同じオブジェクトを指しているためです。

2. 参照による変更

参照が使われる場合、オブジェクトそのものが変更されると、参照を持つ他の変数にも影響が出ます。以下の例で確認してみましょう。

a = [1, 2, 3]
b = a  # aとbは同じオブジェクトを指している

b.append(4)  # bを変更するとaにも反映される

print(a)  # [1, 2, 3, 4]
print(b)  # [1, 2, 3, 4]

この例では、b.append(4) によってリスト b4 を追加していますが、a も同じリストオブジェクトを参照しているため、a の内容にも 4 が追加されます。これは、ab が同じオブジェクトを指しているため、どちらか一方で行った変更が他方にも影響を与えるからです。

3. 参照による副作用

参照が使われると、意図せぬ副作用が発生することがあります。例えば、関数の引数としてリストを渡し、関数内でそのリストを変更すると、その変更が呼び出し元にも影響を与えることがあります。

def modify_list(lst):
    lst.append(5)

a = [1, 2, 3]
modify_list(a)

print(a)  # [1, 2, 3, 5] → 呼び出し元のリストが変更された

上記の例では、関数 modify_list 内で引数として渡されたリスト lst に変更が加えられ、その変更が呼び出し元のリスト a にも反映されます。これは、関数の引数として渡されたリストが参照によって渡されたためです。

4. 参照とコピーの違い

参照は、オブジェクト自体を指し示すだけであり、オブジェクトの実体を複製しないため、同じオブジェクトを指し示す他の変数や関数にも影響を及ぼす可能性があります。一方、コピー を作成する場合は、オブジェクトの実体を新たに作り直して、新しいオブジェクトを作成します。コピーには「浅いコピー」と「深いコピー」があります。

5. 参照のまとめ

Pythonでは、変数に値を代入すると、その変数は実際には オブジェクトへの参照 を持つことになります。

参照を使うと、複数の変数が同じオブジェクトを指し示し、どちらかを変更すると他方にも影響を与える場合があります。

関数に渡された引数も、基本的には参照渡しされるため、関数内でオブジェクトを変更すると、呼び出し元にも変更が反映されることに注意が必要です。

参照の概念をしっかり理解することで、オブジェクトの扱い方や意図しない副作用を避けることができます。次は、参照による副作用 についてさらに詳しく解説していきます。


参照による副作用

Pythonでは、変数がオブジェクトへの参照を持つことから、意図しない副作用が発生することがあります。特に、関数に渡した引数が変更されることで、呼び出し元のデータに影響を与える場合です。これは、「参照渡し」による副作用です。

関数内での変更

関数にオブジェクト(リスト、辞書、セットなど)を渡すと、関数内でそのオブジェクトが変更されると、呼び出し元のオブジェクトも変更されてしまうことがあります。これが意図しない副作用になる場合があるため、注意が必要です。

例えば、以下のようなコードを見てみましょう。

def append_to_list(lst):
    lst.append(4)  # リストに4を追加

my_list = [1, 2, 3]
append_to_list(my_list)

print(my_list)  # [1, 2, 3, 4] → 呼び出し元のリストも変更されてしまう

ここでは、append_to_list 関数内でリスト lst に要素 4 を追加しています。この関数に渡されたリスト my_list が参照渡しされているため、関数内で行った変更が呼び出し元の my_list にも反映されてしまいます。

防御的コピー

このような副作用を防ぐために、関数内で渡されたオブジェクトを変更せず、新しいオブジェクトを作成する方法があります。これを「防御的コピー」と言います。

防御的コピーは、元のオブジェクトを変更せず、変更したい場合は新たにオブジェクトを作成することで、呼び出し元のデータに影響を与えません。

次のコードは、防御的コピーを使って、元のリストを変更しないようにした例です。

プログラム:

def append_to_new_list(lst):
    new_lst = lst.copy()
    new_lst.append(4)
    return new_lst

my_list = [1, 2, 3]
new_list = append_to_new_list(my_list)

print(my_list)
print(new_list)

出力:

[1, 2, 3]
[1, 2, 3, 4]

ここでは、lst.copy() を使用してリストのコピーを作成し、新しいリスト new_lst に変更を加えています。これにより、元のリスト my_list は変更されず、関数内で行った変更が呼び出し元に影響を与えないようになっています。


リスト、辞書、セットなどのミュータブル(可変)オブジェクトの取り扱い

  • リスト、辞書、セット などは、ミュータブル(可変)オブジェクトであり、参照渡しされると変更可能です。これらのオブジェクトを関数に渡す場合、意図せぬ変更が関数内で発生する可能性があるため、特に注意が必要です。
  • タプル や 文字列 などのイミュータブル(不変)オブジェクトは、参照渡しされてもその内容を変更することができません。したがって、これらの場合には参照による副作用の心配はありません。

副作用を防ぐためのベストプラクティス

  • 関数に渡すオブジェクトがミュータブルである場合、必要に応じてそのコピーを使用する。
  • 変更が必要な場合、新たにオブジェクトを生成することを検討する。
  • 関数内での変更が呼び出し元に影響を与える場合、その動作を明確にドキュメントとして記述する。

参照渡しによって、関数内でオブジェクトを変更すると、呼び出し元のオブジェクトにも変更が反映されてしまいます。この仕組みを理解し、意図しない副作用を防ぐためには、防御的コピーを利用するなどの対策を講じることが重要です。参照による副作用を理解することで、より安全なコードを書くことができます。


不変オブジェクト

不変オブジェクト(Immutable Objects) とは、そのオブジェクトの状態を変更できないオブジェクトのことを指します。Pythonでは、文字列(str) や タプル(tuple) が代表的な不変オブジェクトです。これらのオブジェクトは、作成された後にその内容を変更することができません。

1. 不変オブジェクトの特徴

不変オブジェクトは一度作成されると、その後変更が不可能であるため、参照渡しされる際に元のオブジェクトが変更される心配がありません。これにより、関数間での予期しない副作用を回避することができます。

例えば、文字列やタプルは変更できないため、リストのように直接変更されることはなく、常に新しいオブジェクトが返されます。

2. 不変オブジェクトの例

文字列(str)やタプル(tuple)は不変オブジェクトです。次のコードを見てみましょう。

プログラム:

# 文字列の例
text = "hello"
# 文字列は不変なので、新しい文字列を作成する
text = text.upper()

# タプルの例
my_tuple = (1, 2, 3)
# タプルは不変なので、元のタプルを変更できない
my_tuple[0] = 10  # エラーが発生する

出力:

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

/var/folders/lc/k7jt3gx9283453ptp9t9ty5m0000gn/T/ipykernel_10351/1985596220.py in <module>
        2 my_tuple = (1, 2, 3)
        3 # タプルは不変なので、元のタプルを変更できない
----> 4 my_tuple[0] = 10  # エラーが発生する


TypeError: 'tuple' object does not support item assignment

文字列 text は変更できませんが、text.upper() を実行すると新しい文字列 "HELLO" が返されます。同様に、タプル my_tuple の要素を変更しようとするとエラーが発生します。

3. 不変オブジェクトの利点

  • 安全性:不変オブジェクトは変更されないため、関数間でオブジェクトが共有されても、他の部分で内容が変わる心配がありません。これにより、副作用が発生しにくくなります。
  • 効率性:不変オブジェクトは変更できないため、Pythonはこれらを効率的にキャッシュして再利用することができます。

4. 不変オブジェクトと参照の挙動

不変オブジェクトの参照は、変更されることがないため、同じオブジェクトを複数の変数が参照することができます。そのため、メモリの使用効率が高くなります。

a = "hello"
b = a  # a と b は同じ文字列オブジェクトを参照している
print(a is b)  # True - a と b は同じオブジェクトを参照している

上記のコードでは、ab は同じ文字列オブジェクトを参照しており、is 演算子を使って確認すると True が返されます。この挙動は不変オブジェクト特有のものです。

5. 不変オブジェクトの利用シーン

不変オブジェクトは主に以下のような場面で有用です: - キーとして使用:辞書やセットではキーが不変オブジェクトでなければなりません。文字列やタプルは変更できないため、これらのデータ構造のキーとして使うことができます。 - スレッド安全性:不変オブジェクトは他のスレッドからの変更を受けることがないため、並行処理が行われる環境でも安全に使うことができます。

6. 不変オブジェクトのまとめ

不変オブジェクトは変更不可能なため、関数間での予期しない副作用を避けるのに有効です。また、不変オブジェクトを使用することで、メモリ効率やスレッド安全性を高めることができます。Pythonの標準的な不変オブジェクトである文字列やタプルを理解し、適切に活用することが重要です。


オブジェクトのまとめ

1. オブジェクトの基本構造

  • Pythonではすべての値がオブジェクトとして扱われ、オブジェクトはデータとそのデータを操作するメソッドを持っています。
  • オブジェクトは、データ(属性)とそのデータに関連する操作(メソッド)を一体化した構造を持っています。
  • 文字列などの組み込み型オブジェクトは、それぞれ特定のメソッドを提供し、データ操作を容易にします。

2. クラスと型

  • オブジェクトはクラスという設計図に基づいて作成され、その型(クラス)がオブジェクトの属性やメソッドを決定します。
  • クラスを定義し、それを基にインスタンスを生成することでオブジェクトが作られ、クラスによりオブジェクトの特性が決まります。

3. アイデンティティと参照

  • Pythonのオブジェクトには一意の識別子(ID)が割り振られ、id()関数を使用してその識別情報を確認することができます。
  • 変数はオブジェクトを参照しており、同じオブジェクトを複数の変数が参照することができます。この参照によって、複数の変数が同一のオブジェクトを共有します。
  • 参照の使い方には注意が必要で、複数の変数が同じオブジェクトを参照している場合、予期しない副作用を引き起こすことがあります。

4. オブジェクトの不変性

  • 不変オブジェクト(例:文字列やタプル)はその状態を変更できないオブジェクトです。これにより、変更不可能な状態を保つことができます。
  • 不変オブジェクトを使うことで、オブジェクトの変更による副作用を防ぐことができ、予測可能な動作が保証されます。
  • 防御的コピー(deepcopy)などを利用して、参照による副作用を避ける方法もあります。

この章では、Pythonのオブジェクトの基本的な構造、クラスと型、アイデンティティや参照による影響、不変オブジェクトの重要性など、オブジェクト指向の基礎的な概念を学びました。オブジェクトの管理方法や参照の扱いは、実際の開発で非常に重要なポイントとなります。


練習問題

問題 1: オブジェクトの識別子

次のコードを実行した後、id()関数を使ってabのIDが同じか確認してください。

a = [1, 2, 3]
b = a

質問:

  • abは同じオブジェクトを参照していますか?その理由を説明してください。

問題 2: 不変オブジェクト

次のコードを実行した後、リストaを変更し、文字列bを変更してみて、違いを確認してください。

a = [1, 2, 3]
b = "Hello"
a.append(4)
b += " World"

質問:

  • リストaと文字列bの変更結果について、どのような違いが見られますか?
  • それぞれのオブジェクトの性質(可変性、不変性)について説明してください。

問題 3: 参照による副作用

次のコードを実行した後、リストabの内容を確認してください。

a = [1, 2, 3]
b = a
b.append(4)

質問:

  • abの内容に違いはありますか?その理由を説明してください。
  • 参照による副作用についてどのように対処できますか?

問題 4: オブジェクトのコピー

次のコードを実行した後、abが同じオブジェクトを参照しているか、aとbをコピーした場合の違いを説明してください。

import copy

a = [1, 2, 3]
b = copy.copy(a)

質問:

  • abが同じオブジェクトを参照していますか?また、bを変更するとaはどうなりますか?
  • copy.copy()の使い方とその目的を説明してください。

問題 5: オブジェクトの型とメソッド

次のコードを実行し、str型とlist型のオブジェクトで使用できるメソッドを比較してみてください。

s = "hello"
l = [1, 2, 3]

print(s.upper())   # str型のメソッド
print(l.append(4)) # list型のメソッド

質問:

  • str型とlist型のオブジェクトは異なるメソッドを持っています。各オブジェクトが持つメソッドの違いを簡潔に説明してください。

👉 次の章では、モジュール について学び、より実用的なプログラムを作成していきましょう!