3年目エンジニアがPythonで躓いたこと5選

# はじめに
エンジニアとして満2年が過ぎ、３年目に突入しました。最初の3年が肝心と考えながら、過ごしてきました。この記事ではそんな振り返りも兼ねて駆け出しPythonistが躓きやすいことを5つ紹介できればと思います。

# Pythonで躓いたこと5選

## 1.動的型付け
Pythonは動的型付け言語です。C++やJavaなどの静的型付け言語とは異なり、変数を定義する際にいちいち型を指定する必要がありません。コードの量が少なくなるなどメリットもあるんですが、型の不一致による不具合が発生した場合に調査に時間がかかってしまいます。

そんな事情を知りつつも特に工夫することもなかった僕は当時参加していた開発現場のレビューで「動的型付けだからといって型を決めないで良いわけじゃないよ」と指摘（注意）されてしまいました。

それからは型ヒントを書いていくことで静的型付けほどではないですが、ある程度（気のせいかもしれませんが）安全に実装していくことができました。

### 型ヒントを使ってみる
簡単な予約プログラムです。

```py
from datetime import date
from enum import Enum, auto

class RoomType(Enum):
    SINGLE = auto()
    DOUBLE = auto()
    SUITE = auto()

def book_room(name: str, check_in: date, check_out: date, room_type: RoomType) -> str:
    duration = (check_out - check_in).days
    return f"{name}さんの予約が完了しました。{check_in}から{duration}日間、{room_type.name}ルームが予約されています。"

booking_confirmation = book_room(
    "テスト太郎",
    date(2024, 2, 6),
    date(2024, 2, 7),
    RoomType.DOUBLE.name
)

print(booking_confirmation)
```

他メンバーにとっては保守性が上がるし、実装者にとってはTypeErrorを回避できるので、駆け出しPythonistの方々はこれから意識していきましょう。

## 2.ライブラリ管理
開発を進めているとライブラリを使用する場面も少なくないでしょう。様々なライブラリの利用が不可欠ですが、これらの依存関係の管理は駆け出しPythonistにとっては少し難しい（面倒）だと思います。個人開発のレベルですが、筆者もグローバルなインストール空間を汚してしまっていました。

そこでプロジェクトごとに独立したライブラリ環境を保つために`requirements.txt`を用いた管理が重要だと現場に出て学ぶことができました。Pythonプロジェクトの保守性と移植性を高める上で、開始初立ち上げのタイミングでなくても導入することをお勧めします。

開発途中の方は下記コマンドで設置してみてください。

```
pip freeze > requirements.txt
```

## 3.self
PHPの経験がある筆者にとって、Pythonでの`self`の使い方は最初馴染みがなく、書き慣れませんでした。`self`はクラスのインスタンス自身を指すキーワードで、メソッド内でインスタンス変数や他のメソッドにアクセスするために使用します。

PHPでは、`$this`が同様の役割を果たしますが、Pythonでは`self`がこの役割を担っています。クラスのメソッドを定義する際、第一引数として`self`を明示的に記述する必要があるんですが、これのおかげでメソッド内でクラスの属性や他のメソッドにアクセスする際に、どのインスタンスに対して操作を行っているのかが明確になります。

### `self`を使用してインスタンス変数にアクセスする例
この例では、`__init__`メソッド（コンストラクタ）でインスタンス変数`value`を設定し、`show_value`メソッドでその値を表示しています。どちらのメソッドも、`self`を使用してインスタンス変数`value`にアクセスしています。

```py
class MyClass:
    def __init__(self, value):
        self.value = value

    def show_value(self):
        print(self.value)
```

## 4.引数デフォルト値
Pythonでは関数の引数にデフォルト値を設定することができます。これは特定の引数が関数呼び出し時に提供されなかった場合に使用される値です。デフォルト値を持つ引数は関数をより柔軟にしコードの量を減らすことができますが、不適切に使用すると予期しないバグの原因となることがあります。

関数定義時に引数の後に`=`を使ってデフォルト値を指定します。このときデフォルト値を持たない引数はデフォルト値を持つ引数よりも前に置く必要があります。

```py
def greet(name, message="Hello"):
    print(f"{message}, {name}!")
```

関数のデフォルト値は関数が定義された時点で一度だけ評価されます。これは、デフォルト値がミュータブル（変更可能）なオブジェクトの場合特に注意が必要です。例えばリストや辞書などのミュータブルなオブジェクトをデフォルト値として使用すると、予期しない挙動を引き起こす可能性があります。

```py
def add_item(name, item_list=[]):
    item_list.append(name)
    return item_list
```

ミュータブルなデフォルト値を避けるためには、デフォルト値として`None`を使用し、関数内で必要に応じて新しいオブジェクトを作成するのが一般的です。

```py
def add_item(name, item_list=None):
    if item_list is None:
        item_list = []
    item_list.append(name)
    return item_list
```

## 5.インデント
他の多くのプログラミング言語と異なり、Pythonではブロックを示すためにインデントを使用します。これはコードの可読性を高める一方で、インデントを誤ると思わぬバグを引き起こす原因となります。

```py
def my_function():
    for i in range(5):
        if i % 2 == 0:
            print(f"{i} is even")
        else:
            print(f"{i} is odd")
```

この例では、`for`ループ、`if`文、`else`文のそれぞれのブロックがインデントによって区切られています。

インデントの誤りは、特に駆け出しPythonistにとっては一見しただけでは気づきにくいことが多いです。しかしPythonのプログラムではインデントがコードの意味を大きく左右するため、正確なインデントの使用は非常に重要です。インデントによる誤りはプログラムのロジックに直接影響を及ぼし、時には深刻なバグの原因となり得ます。

プログラムの正確な動作を保証するためにはインデントを適切に管理し、コードの構造を明確にすることが不可欠です。これにより、コードの可読性が向上し、他の開発者がコードを理解しやすくなります。またインデントの誤りを避けることでプログラムのデバッグが容易になり、開発効率も向上します。

# おわりに
Pythonでの開発を進める中で、動的型付け、ライブラリ管理、selfの使用、引数のデフォルト値、そしてインデントの重要性といったポイントに注意を払うことは、効率的でバグの少ないコードを書くために非常に重要です。これからまた躓く度に更新していけたらと思います。

ここまで読んでいただきありがとうございました。