Python基礎(3)〜関数とクラス〜

Python中でも、「関数」と「クラス」は、コードの再利用性、保守性、可読性を向上させるための重要なツールです。

関数は一度定義すれば何度も呼び出すことができ、複雑な処理を簡潔にまとめることができます。

また、クラスを使うことで、データとその処理を一体化し、オブジェクト指向プログラミングの強みを活かした設計が可能となります。

この講義では、Pythonにおける既存の関数の使い方、新しい関数の作成方法、そしてオブジェクト指向の基本概念であるクラスについて、基礎から応用までを体系的に学んでいきます。

これにより、Pythonプログラムを構造的かつ効率的に書くためのスキルを習得していきましょう。

1. 既存の関数

Pythonには多くの組み込み関数があり、それらを理解し活用することで、より効率的なプログラムが書けます。

ここでは、既存の関数の中でも基本的なものに焦点を当て、使い方の幅を広げます。

1.1 input()

input()はユーザーからの入力を受け取るための関数です。この関数は入力値を常に文字列(str型)として返します。

基本的な使い方

name = input("あなたの名前を教えてください: ")
print("こんにちは、" + name + "さん!")

数値として扱いたい場合

ユーザーが数値を入力する場合、文字列を型変換する必要があります。

age = int(input("あなたの年齢を教えてください: "))
print(f"来年は {age + 1} 歳ですね!")

エラーハンドリングを伴う入力

不正な入力に対処するには、try-exceptを使って例外処理を追加することができます。

try:
    number = int(input("数字を入力してください: "))
    print(f"あなたが入力した数字は {number} です。")
except ValueError:
    print("正しい数字を入力してください。")

1.2 abs()

abs()は数値の絶対値を返します。絶対値とは、その数値の正負に関わらず0からの距離を表すものです。

基本的な使い方

print(abs(-5))  # 結果: 5
print(abs(3))   # 結果: 3

用途

絶対値の計算は、数値が正か負かにかかわらず、距離や差を測る際に便利です。

例えば、二つの数値の差を絶対値で比較することで、符号に関わらず差異の大きさを測定できます。

def difference(a, b):
    return abs(a - b)

print(difference(7, 10))  # 結果: 3
print(difference(10, 7))  # 結果: 3

1.3 range()

range()は指定した範囲の整数のリストのようなシーケンスを生成します。ループ処理で特によく使われますが、他にも様々な用途があります。

基本的な使い方

range(start, stop, step) という形式で3つの引数を取ります。

startは開始位置、stopは終了位置(ただしstopは含まれません)、stepは増分を表します。

for i in range(3):  # 0から2まで(3は含まない)
    print(i)
for i in range(1, 10, 2):  # 1から9まで、2ずつ増加
    print(i)

リストに変換する

range()は直接リストとしては扱えませんが、list()を使うことでリストに変換できます。

my_range = range(5)
my_list = list(my_range)  # [0, 1, 2, 3, 4]

逆順のrange()

stepを負の値にすることで、逆順に数値を生成できます。

for i in range(10, 0, -1):  # 10から1まで、1ずつ減少
    print(i)

• range()の効率性

range()は非常に効率的な関数で、リストのように見えるものの実際にはすべての数をメモリに保存しているわけではありません。

これを遅延評価と呼び、数値を必要なタイミングで生成します。

これにより、大きな範囲でもメモリ効率が良くなります。

large_range = range(1000000)  # 大きな範囲でもメモリ効率が高い

1.4 len()

len()はシーケンスやコレクションの長さ(要素数)を返します。

リストや文字列に対する長さの取得

my_list = [1, 2, 3, 4]
print(len(my_list))  # 結果: 4

my_string = "Hello, World!"
print(len(my_string))  # 結果: 13

ネストされた構造に対する応用

ネストされたリストや辞書の中でもlen()を使ってその構造の要素数を取得することができます。

nested_list = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
print(len(nested_list))  # 結果: 3 (外側のリストの要素数)

my_dict = {'a': 1, 'b': 2, 'c': 3}
print(len(my_dict))  # 結果: 3 (辞書のキーの数)

Ex. 既存の関数の活用

既存の関数を組み合わせてより複雑なロジックを作ることができます。

例えば、次のような問題を解決するために既存の関数を駆使します。

リストから最大値と最小値の差を計算する

my_list = [3, 5, 1, 9, 2]
difference = abs(max(my_list) - min(my_list))
print(f"最大値と最小値の差: {difference}")

入力から特定の条件を満たす数をカウントする

numbers = []
for _ in range(5):
    num = int(input("数字を入力してください: "))
    numbers.append(num)

even_count = len([n for n in numbers if n % 2 == 0])
print(f"偶数の数: {even_count}")

2. 新規の関数を作成する

関数はプログラムを効率的かつ柔軟に構造化するための重要なツールです。

単に処理をまとめるだけでなく、以下の要素を考慮することで、再利用性、可読性、メンテナンス性の高いコードが書けます。

2.1 関数設計の基本

関数は1つの責務に集中させるのが基本的な設計方針です。

関数が複数の異なる責務を持つと、コードが複雑になり、バグやメンテナンスの難しさが増します。

シンプルな関数例

def calculate_area(width, height):
    return width * height

この関数は「幅」と「高さ」を受け取り、その面積を計算する1つの責務に従っています。

このように関数を設計することで、関数の再利用性が向上します。

2.2 ドキュメント文字列(docstring)

関数に説明を追加するために、Pythonではドキュメント文字列を使って関数の目的、引数、返り値を明示的に説明できます。

これにより、関数を使用する際の理解が容易になります。

関数のドキュメント文字列

def calculate_area(width, height):
    """
    与えられた幅と高さから矩形の面積を計算します。
    
    :param width: 矩形の幅 (float)
    :param height: 矩形の高さ (float)
    :return: 面積 (float)
    """
    return width * height

docstringは、関数の目的や動作を他の開発者に伝えるために使います。特に、大規模なプロジェクトやチーム開発では、必須といってよいほどの重要な要素です。

2.3 デフォルト引数

関数を柔軟にするために、デフォルト引数を設定できます。

これにより、ユーザーが引数を省略した場合にデフォルトの値が使われるため、簡潔な関数呼び出しが可能になります。

デフォルト引数の使い方

def greet(name="ゲスト", greeting="こんにちは"):
    print(f"{greeting}、{name}さん!")

greet()  # こんにちは、ゲストさん!
greet("太郎")  # こんにちは、太郎さん!
greet("太郎", "おはよう")  # おはよう、太郎さん!

デフォルト引数は、関数が柔軟に使われる場面で役立ちます。例えば、デフォルトの挨拶を提供しながらも、ユーザーが指定すれば別の挨拶を行えるように設計できます。

2.4 変数のスコープ

関数の中で定義される変数は、ローカルスコープにあります。

関数の外側で定義された変数はグローバルスコープにあります。

変数のスコープを意識することで、予期しないバグを避けることができます。

ローカル変数とグローバル変数

x = 10  # グローバル変数

def add_to_x(y):
    x = 5  # ローカル変数
    return x + y

print(add_to_x(3))  # 結果: 8
print(x)  # 結果: 10(グローバル変数のxは変更されていない)

ローカル変数グローバル変数の違いを理解していないと、関数内で意図しない動作を引き起こすことがあります。スコープの境界を明確にすることで、コードの安全性が高まります。

2.5 エラーハンドリング

関数の中で発生する可能性があるエラーに対処するためには、例外処理を使います。

これにより、関数が異常な動作をしてもプログラム全体を停止させることなく、エラーメッセージを表示したり、別の処理に移行したりできます。

例外処理の追加

def divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        return "ゼロでは割れません。"
    return result

print(divide(10, 2))  # 結果: 5.0
print(divide(10, 0))  # 結果: ゼロでは割れません。

例外処理を組み込むことで、エラーが発生したときでもユーザーに適切なフィードバックを与え、プログラムを安全に動作させることができます。

2.6 高階関数

Pythonでは、関数を他の関数に渡したり、関数を返り値として使うことができます。

これを高階関数と呼びます。

高階関数を利用すると、コードをより抽象的かつ再利用可能にできます。

関数を引数として渡す

def apply_function(func, value):
    return func(value)

def square(x):
    return x * x

print(apply_function(square, 5))  # 結果: 25

高階関数は、他の関数を操作したり組み合わせたりする際に強力なツールです。

例えば、関数を動的に切り替える柔軟なシステムを構築する際に使えます。

2.7 再帰関数

再帰関数は、自分自身を呼び出す関数のことです。

再帰を使うと、問題をよりシンプルな部分問題に分割して解決できます。

特に、ツリー構造や階層的なデータの操作に適しています。

再帰関数の例: フィボナッチ数列

def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(6))  # 結果: 8

再帰を使う場合、終了条件(base case)を必ず設ける必要があります。

終了条件がなければ無限ループになってしまいます。

2.8 可変長引数

関数に渡す引数の数が変動する場合、可変長引数を使います。

これにより、任意の数の引数を受け取る柔軟な関数が作成できます。

任意の数の引数を受け取る

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # 結果: 10
print(sum_all(5, 6))  # 結果: 11

*argsはタプル形式で複数の引数を受け取る際に使われます。これにより、引数の数が不明でも対応できる関数が作れます。

2.9 キーワード引数

キーワード引数は、名前付きの引数を使用することで、引数を順番に依存せずに渡すことができます。

また、辞書の形式で任意の数の引数を受け取ることもできます。

キーワード引数と辞書形式

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="Tokyo")

**kwargsを使うことで、関数に渡される任意のキーワード引数を辞書形式で受け取り、柔軟に処理できます。

3. クラスの概念と使い方

3.1 オブジェクト指向の考え方とクラスの本質

オブジェクト指向プログラミング(OOP)では、現実世界の概念をプログラムの構造として扱うことが基本です。

クラスはその中で「設計図」として機能し、オブジェクトはクラスに基づいて生成される具体的な「インスタンス」です。

クラスの強みは、データ(属性)とそのデータを操作する関数(メソッド)を一体化して扱う点にあります。

現実の例で言えば、「」クラスを設計する際、車の属性(色、モデル、エンジン)と、車の動作(アクセルを踏む、ブレーキをかける)といったものを組み合わせて表現できます。

3.2 クラスの設計

クラスを効果的に設計するには、単一責任の原則(Single Responsibility Principle)を意識することが重要です。

これは、「クラスは1つの目的のために存在するべきだ」という原則です。

これにより、クラスが複雑化せず、各クラスが再利用可能でメンテナンスしやすくなります。

シンプルな車のクラス

class Car:
    def __init__(self, model, color, engine):
        self.model = model
        self.color = color
        self.engine = engine

    def start_engine(self):
        print(f"{self.model}のエンジンを始動します。")

# クラスのインスタンス化
my_car = Car("トヨタ", "赤", "V8")
my_car.start_engine()  # 出力: トヨタのエンジンを始動します。

クラス設計のポイント:

  • 属性(プロパティ): model, color, engine のように、オブジェクトが持つデータを定義。
  • メソッド: start_engine() のように、オブジェクトが持つ動作や処理を定義。

3.3 アクセス修飾子とカプセル化

クラス内部の属性やメソッドに対するアクセス制御は、カプセル化と呼ばれ、オブジェクト指向プログラミングの重要な概念です。

Pythonではアクセス修飾子として、プライベートパブリックがあります。

パブリック属性: 通常の属性は外部からアクセス可能です。

プライベート属性: 属性名の前に__(ダブルアンダースコア)を付けることで、外部からアクセスできなくなります。

プライベート属性を使った例

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance  # プライベート属性

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
        else:
            print("無効な金額です。")

    def get_balance(self):
        return self.__balance

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance())  # 1500
# print(account.__balance)  # エラー: プライベート属性にアクセスできない

カプセル化の利点:

• 内部のデータを外部から直接変更されないように保護する。

• データの不正な変更や操作を防ぐために、メソッド経由で操作を行うことで、整合性を保つ。

3.4 継承と多態性(ポリモーフィズム)

クラスは継承によって既存のクラスの機能を再利用し、新しいクラスを作成することができます。

これにより、コードの重複を避け、保守性を高めます。

また、ポリモーフィズム(多態性)により、異なるクラスでも同じメソッドを使用でき、拡張性が高まります。

継承の例

class Animal:
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print(f"{self.name}が音を出した。")

class Dog(Animal):
    def make_sound(self):
        print(f"{self.name}がワンワンと鳴いた。")

class Cat(Animal):
    def make_sound(self):
        print(f"{self.name}がニャーニャーと鳴いた。")

animals = [Dog("ポチ"), Cat("タマ")]

for animal in animals:
    animal.make_sound()  # ポチがワンワンと鳴いた、タマがニャーニャーと鳴いた

継承の利点:

  • 親クラス(Animal)で定義された基本的な機能を子クラス(DogやCat)に引き継ぐことができる。
  • 子クラスでメソッドを上書き(オーバーライド)することで、独自の振る舞いを実装できる。

3.5 クラスメソッドと静的メソッド

通常のメソッドは、インスタンス(オブジェクト)に対して操作を行いますが、クラスメソッドと静的メソッドを使うことで、インスタンスを作成せずにメソッドを呼び出すことができます。

クラスメソッド:

クラス全体に対して作用するメソッドで、@classmethodデコレータを使って定義します。

第1引数にはクラス自身が渡され、通常はclsと命名します。

class Product:
    total_products = 0

    def __init__(self, name):
        self.name = name
        Product.total_products += 1

    @classmethod
    def get_total_products(cls):
        return cls.total_products

product1 = Product("スマートフォン")
product2 = Product("ラップトップ")

print(Product.get_total_products())  # 2

静的メソッド:

クラスやインスタンスに依存しないメソッドを定義する場合、@staticmethodデコレータを使います。

これは、単に便利なユーティリティ関数をクラスの中で定義したい場合に有用です。

class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

print(MathUtils.add(3, 5))  # 8

クラスメソッドと静的メソッドの使い分け:

  • クラス全体に関連するデータ(例えば総数や設定値)を扱う場合は、クラスメソッドを使います。
  • どのインスタンスやクラスとも無関係な処理を行う場合は、静的メソッドを使用します。

3.6 データモデルと特殊メソッド(マジックメソッド)

Pythonでは、マジックメソッド(特殊メソッド)を使うことで、クラスに対して演算子の動作や標準的な挙動をオーバーライドできます。

これにより、クラスをよりPython的に扱えるようになります。

• __str__()__repr__()

__str__()は人間が読みやすい形式でオブジェクトを表示する際に使われ、__repr__()はデバッグや開発時に利用される正式な文字列表現を提供します。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"名前: {self.name}, 年齢: {self.age}"

    def __repr__(self):
        return f"Person(name={self.name}, age={self.age})"

person = Person("Alice", 30)

まとめ

今回の講義では、Pythonにおける「関数」と「クラス」の概念とその実践的な使い方を詳しく解説しました。

Pythonでコードを書く際、これらの基本的な概念を理解して応用できるようになることは、開発効率の向上やコードの品質を大きく高める重要なステップです。

関数とクラスは、規模の小さなスクリプトから複雑なシステムに至るまで、あらゆるプロジェクトにおいて欠かせない基盤です。

これをしっかりと理解し、実際の開発に役立てることで、あなたのプログラミングスキルが次のレベルへと進化するでしょう。

SHARE
採用バナー