├── .github └── workflows │ └── mkdocs.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── ch01-01-installation.md ├── ch01-02-hello-world.md ├── ch02-01-variables.md ├── ch02-02-functions.md ├── ch02-03-comments.md ├── ch02-04-conditions.md ├── ch02-05-loops.md ├── ch02-06-list-comprehensions.md ├── ch02-07-lambdas.md ├── ch03-01-classes.md ├── ch03-02-scopes.md ├── ch03-03-special-attributes.md ├── ch03-04-properties.md ├── ch04-01-modules.md ├── ch04-02-packages.md ├── ch04-03-pip.md ├── ch04-04-venv.md ├── ch04-07-project-structures.md ├── ch05-01-files.md ├── ch05-02-contexts.md ├── ch05-03-csv.md ├── ch05-04-json.md ├── ch06-01-exceptions.md ├── ch07-01-generators.md ├── ch08-01-doctest.md ├── ch08-02-pytest.md ├── ch09-01-tools.md ├── ch09-02-pipenv.md ├── ch09-03-poetry.md ├── img │ ├── python.svg │ ├── vscode-01.png │ ├── vscode-02.png │ ├── vscode-03.png │ ├── vscode-04.png │ └── vscode-05.png └── index.md ├── mkdocs.yml ├── pyproject.toml ├── requirements.txt └── uv.lock /.github/workflows/mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: MkDocs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - mkdocs.yml 9 | - docs/** 10 | 11 | jobs: 12 | deploy: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout the repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Configure Git 20 | run: | 21 | git config user.name github-actions 22 | git config user.email github-actions@github.com 23 | git fetch origin gh-pages 24 | 25 | - name: Install MkDocs 26 | run: | 27 | python3 -m pip install setuptools 28 | python3 -m pip install -r requirements.txt 29 | 30 | - name: Deploy HTML files 31 | run: python3 -m mkdocs gh-deploy 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | 3 | # Created by https://www.gitignore.io/api/macOS 4 | # Edit at https://www.gitignore.io/?templates=macOS 5 | 6 | ### macOS ### 7 | # General 8 | .DS_Store 9 | .AppleDouble 10 | .LSOverride 11 | 12 | # Icon must end with two \r 13 | Icon 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | # End of https://www.gitignore.io/api/macOS 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Ida Kenichiro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ゼロから学ぶ Python 2 | 3 | このリポジトリはオンライン学習サイト[ゼロから学ぶ Python]のソースコードリポジトリです。 4 | 5 | ## 必要なもの 6 | 7 | ソースコードから HTML ページを生成するには下記のものが必要です。 8 | 9 | - Python3 10 | 11 | ## ビルド 12 | 13 | HTML を生成するには下記のコマンドを実行してください。 14 | 15 | ```shell 16 | $ pip install -r requirements.txt 17 | $ mkdocs build 18 | ``` 19 | 20 | ビルド結果をブラウザ上で確認するには次のコマンドを実行します。 21 | 22 | ```shell 23 | $ mkdocs serve 24 | ``` 25 | 26 | http://localhost:8000 にアクセスすると Web ページが表示されます。 27 | 28 | [ゼロから学ぶ Python]: https://rinatz.github.io/python-book 29 | -------------------------------------------------------------------------------- /docs/ch01-01-installation.md: -------------------------------------------------------------------------------- 1 | # インストール 2 | 3 | Python 開発に必要な下記のツールをインストールします。 4 | 5 | - Python 6 | - Visual Studio Code 7 | 8 | ## Python 9 | 10 | 各プラットフォームに合わせて Python のインストール手順が下記サイトにまとまっていますのでインストールをしてください。 11 | 12 | - [macOS](https://www.python.jp/install/macos/install_python.html) 13 | - [Windows](https://www.python.jp/install/windows/install.html) 14 | 15 | コマンドを打って Python が正しくインストールされたかどうかを確認します。 16 | 17 | === "macOS" 18 | 19 | ターミナル上で下記コマンドを実行します。 20 | 21 | ```shell 22 | $ python3 --version 23 | ``` 24 | 25 | バージョンが表示されれば成功です。 26 | 27 | === "Windows" 28 | 29 | コマンドプロンプト上で下記コマンドを実行します。 30 | 31 | ```shell 32 | $ py -3 --version 33 | ``` 34 | 35 | バージョンが表示されれば成功です。 36 | 37 | ## Visual Studio Code のインストール 38 | 39 | Visual Studio Code (VS Code) はエンジニアの間で人気のエディタです。 40 | 拡張機能を取り入れることで見た目の変更や機能追加などを自由にカスタマイズできるのが特徴です。 41 | 42 | 下記のサイトで VS Code をダウンロードし、インストールをしてください。 43 | 44 | !!! Info "VS Code" 45 | [https://code.visualstudio.com/](https://code.visualstudio.com/) 46 | 47 | 次に Python の開発環境を整えるための VS Code 拡張機能をインストールします。 48 | 49 | | 拡張機能 | 概要 | 50 | |-------------------------------------------|-----------------------------| 51 | | [Python extension for Visual Studio Code] | Python 開発のための基本機能 | 52 | | [Pylance] | コード補完 | 53 | | [Visual Studio IntelliCode] | オートコンプリート | 54 | 55 | [Python extension for Visual Studio Code]: https://marketplace.visualstudio.com/items?itemName=ms-python.python 56 | [Pylance]: https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance 57 | [Visual Studio IntelliCode]: https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode 58 | 59 | インストールは VS Code を起動した後、左側にあるペインから拡張機能のタブを選択し、 60 | インストールする拡張機能を検索してインストールします。 61 | 62 | [![](img/vscode-01.png)](img/vscode-01.png) 63 | -------------------------------------------------------------------------------- /docs/ch01-02-hello-world.md: -------------------------------------------------------------------------------- 1 | # Hello, World! 2 | 3 | 標準出力に `Hello, World!` と出力する簡単な Python プログラムを書いてみます。 4 | 5 | --- 6 | 7 | まずターミナル上で `hello` というディレクトリを作成し、それを VSCode で開きます。 8 | 9 | ```shell 10 | $ mkdir hello 11 | $ cd hello 12 | $ code . 13 | ``` 14 | 15 | [![](img/vscode-02.png)](img/vscode-02.png) 16 | 17 | --- 18 | 19 | 次にファイルの新規作成のアイコンを押して `hello.py` という名前のファイルを作成します。 20 | 21 | [![](img/vscode-03.png)](img/vscode-03.png) 22 | 23 | --- 24 | 25 | ファイルを作成してそのファイルを開くとウィンドウの左下に使用する Python のバージョンが表示されます。 26 | このバージョンがインストールした Python のバージョンと異なる場合は Python のバージョンをクリックすると 27 | 使用する Python を変更することができます。 28 | 29 | [![](img/vscode-04.png)](img/vscode-04.png) 30 | 31 | 複数の Python をインストールしている場合にはこのようにして使用する Python を変更してください。 32 | 33 | --- 34 | 35 | ここまでできたら `hello.py` にコードを書いてみます。 36 | 37 | **hello.py** 38 | 39 | ```python 40 | #!/usr/bin/env python 41 | 42 | 43 | def main(): 44 | print('Hello, World!') 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | ``` 50 | 51 | できたらファイルを保存してプログラムを実行してみます。 52 | 53 | ウィンドウの右上にある再生ボタンをクリックするとプログラムが実行されます。 54 | 55 | [![](img/vscode-05.png)](img/vscode-05.png) 56 | 57 | ## 構文の説明 58 | 59 | ### シバン 60 | 61 | ```python hl_lines="1" 62 | #!/usr/bin/env python 63 | 64 | 65 | def main(): 66 | print('Hello, World!') 67 | 68 | 69 | if __name__ == '__main__': 70 | main() 71 | ``` 72 | 73 | `#` から始まる 1 行目は [シバン] といいます。シバンはこのソースコードを実行する際に使用するコマンドを記述します。`/usr/bin/env python` は `python` コマンドを呼び出しているという意味になります。 74 | 75 | [シバン]: https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%90%E3%83%B3_(Unix) 76 | 77 | !!! warning 78 | Windows では `python` コマンドの代わりに `py` コマンドを使用するように説明しましたが、シバンは 79 | 80 | ``` 81 | #!/usr/bin/env python 82 | ``` 83 | 84 | と書いて下さい。 85 | 86 | ### `main()` 87 | 88 | ```python hl_lines="4 5" 89 | #!/usr/bin/env python 90 | 91 | 92 | def main(): 93 | print('Hello, World!') 94 | 95 | 96 | if __name__ == '__main__': 97 | main() 98 | ``` 99 | 100 | これは関数の定義をしています。関数の詳細な説明は [関数] の章で説明します。 101 | 102 | `print()` がインデントされていることはとても重要です。なぜなら 103 | 104 | ```python 105 | def main(): 106 | print('Hello, World!') 107 | ``` 108 | 109 | と書くと構文エラーになるからです。関数の実装は必ずインデントしてから記述するルールになっています。 110 | 111 | ### `__name__` 112 | 113 | ```python hl_lines="8 9" 114 | #!/usr/bin/env python 115 | 116 | 117 | def main(): 118 | print('Hello, World!') 119 | 120 | 121 | if __name__ == '__main__': 122 | main() 123 | ``` 124 | 125 | `main()` は関数を呼び出しています。`if __name__ == '__main__':` については [モジュール] の章で説明しますので、今はおまじないだと思って下さい。`main()` の呼び出しは必ずインデントをして下さい。さもないと構文エラーになります。 126 | 127 | ### シンプルな書き方 128 | 129 | Python の処理はソースコードの上の行から逐次実行されます。そのため、処理を関数内に収めなくても正しく実行することができます。 130 | 131 | ```python 132 | #!/usr/bin/env python 133 | 134 | 135 | print('Hello, World!') 136 | ``` 137 | 138 | しかし特別な理由がある場合を除いて、関数内に定義する方が望ましいです。その理由は [モジュール] の章で明らかになります。 139 | 140 | [関数]: ch02-02-functions.md 141 | [モジュール]: ch04-01-modules.md 142 | -------------------------------------------------------------------------------- /docs/ch02-01-variables.md: -------------------------------------------------------------------------------- 1 | # 変数 2 | 3 | 次のようなコードを書いて色々な変数を出力してみましょう。 4 | 5 | ```python hl_lines="6" 6 | #!/usr/bin/env python 7 | 8 | 9 | def main(): 10 | x = 10 # 変数の定義 11 | print(x) 12 | 13 | 14 | if __name__ == '__main__': 15 | main() 16 | ``` 17 | 18 | ## 数値型 19 | 20 | ### 整数 `int` 21 | 22 | ```python 23 | x = 10 24 | ``` 25 | 26 | ### 浮動小数点 `float` 27 | 28 | ```python 29 | x = 3.14 30 | ``` 31 | 32 | ### 数値演算 33 | 34 | 数値に対しては四則演算ができます。 35 | 36 | ```python 37 | # 加算 38 | total = 5 + 10 39 | 40 | # 減算 41 | difference = 95.5 - 4.3 42 | 43 | # 乗算 44 | product = 4 * 30 45 | 46 | # 除算 47 | division = 56.7 / 32.2 48 | 49 | # 商 50 | quotient = 62 // 12 51 | 52 | # 剰余 53 | remainder = 43 % 5 54 | ``` 55 | 56 | !!! warning "整数 / 整数 の結果" 57 | 整数に対する `/` は Python 2 系と 3 系では意味が変わります。 58 | 59 | | バージョン | 挙動 | 5 / 2 = ? | 60 | |------------|------|-----------| 61 | | 2.x.x | 商 | 2 | 62 | | 3.x.x | 除算 | 2.5 | 63 | 64 | ## 真偽値 `bool` 65 | 66 | 真偽値を扱うための型です。 67 | 68 | ```python 69 | x = True 70 | y = False 71 | ``` 72 | 73 | ## 文字列 `str` 74 | 75 | ```python 76 | x = 'Hello, World!' 77 | y = '日本語' 78 | ``` 79 | 80 | ### f-string 81 | 82 | 文字列の先頭に `f` を付けると文字列内に変数や式の埋め込みができるようになります。 83 | 84 | ```python 85 | x = 10 86 | s1 = f'The value of x is {x}' # 'The value of x is 10' 87 | s2 = f'The value of x * x is {x * x}' # 'The value of x * x is 100' 88 | ``` 89 | 90 | f-string を使わなくても次のようにも書けます。 91 | 92 | ```python 93 | s1 = 'The value of x is {}'.format(x) 94 | s2 = 'The value of x * x is {}'.format(x * x) 95 | ``` 96 | 97 | f-string は `'...'.format()` のシンプルな書き方を提供する機能です。 98 | 99 | ## バイト `byte` 100 | 101 | バイト列を扱う型です。 102 | 103 | ```python 104 | x = b'0xDEADBEEF' 105 | ``` 106 | 107 | ## コレクション型 108 | 109 | コレクション型は複数の値をまとめて扱える型の総称です。 110 | 111 | ### リスト `list` 112 | 113 | 配列を扱うための型です。リストの各要素は必ずしも同じ型である必要はありません。 114 | 115 | ```python 116 | x = [0, 1, 2, 3, 4] 117 | y = [10, 3.14, 'Hello, World!'] 118 | ``` 119 | 120 | 要素の末尾に `,` が入っていても構文として正しいです。 121 | 122 | ```python 123 | x = [ 124 | 10, 125 | 3.14, 126 | 'Hello, World!', # OK 127 | ] 128 | ``` 129 | 130 | 要素の追加は次のようにします。 131 | 132 | ```python 133 | x.append(100) 134 | ``` 135 | 136 | また要素参照は次のようにします。 137 | 138 | ```python 139 | x = [10, 3.14, 'Hello, World!'] 140 | 141 | x[0] # 10 142 | x[1] # 3.14 143 | x[2] # 'Hello, World!' 144 | ``` 145 | 146 | 添字には負の数も指定できます。負の数を指定した場合は末尾の要素から参照されます。 147 | 148 | ```python 149 | x = [10, 3.14, 'Hello, World!'] 150 | 151 | x[-1] # 'Hello, World!' 152 | x[-2] # 3.14 153 | x[-3] # 10 154 | ``` 155 | 156 | 要素が空のリストを作る場合は `x = []` とします。 157 | 158 | ### タプル `tuple` 159 | 160 | リストとよく似た扱いができる型ですが、タプルは要素の変更も追加もできません。 161 | 162 | ```python 163 | x = (0, 1, 2, 3, 4) 164 | y = (10, 3.14, 'Hello, World!') 165 | ``` 166 | 167 | 要素参照はリストと同じようにしてできます。`()` はただの飾りであり、必須ではありません。タプルになるかどうかは `,` が含まれているかどうかで決まります。 168 | 169 | ```python 170 | x = 10, 3.14, 'Hello, World!' # 3 要素のタプル 171 | y = 20, # 1 要素のタプル 172 | ``` 173 | 174 | ただし空のタプルを作りたいときは `()` を使います。 175 | 176 | ```python 177 | x = () 178 | ``` 179 | 180 | !!! note 181 | 要素の変更が必要ない場合はリストよりタプルを使う方が安全です。 182 | 183 | ### アンパック代入 184 | 185 | リストやタプルの要素を複数の変数に同時に代入する構文を **アンパック代入** といいます。 186 | 187 | ```python 188 | x = (10, 3.14, 'Hello, World!') 189 | a, b, c = x # a: 10, b: 3.14, c: 'Hello, World!' 190 | ``` 191 | 192 | 代入元と代入先の要素数は一致している必要があります。 193 | 194 | ```python 195 | x = [0, 1, 2, 3, 4] 196 | a, b, c = x # エラー 197 | ``` 198 | 199 | しかし代入先の変数に `*` を付けておくと、その変数はリスト型になるため、要素数が一致しなくてもアンパック代入ができるようになります。 200 | 201 | ```python 202 | x = [0, 1, 2, 3, 4] 203 | a, b, *c = x # a: 0, b: 1, c: [2, 3, 4] 204 | ``` 205 | 206 | ### 辞書 `dict` 207 | 208 | キーに対する値を管理するための型です。 209 | 210 | ```python 211 | x = {'name': 'John Doe', 'age': 30} 212 | 213 | x['name'] # 'John Doe' 214 | x['age'] # 30 215 | ``` 216 | 217 | 要素が空の辞書を作る場合は `x = {}` とします。 218 | 219 | ### 集合 `set` 220 | 221 | 重複を取り除いて複数の要素を扱うための型です。 222 | 223 | ```python 224 | x = {0, 1, 2, 2, 3, 4, 4} # {0, 1, 2, 3, 4} 225 | ``` 226 | 227 | `set()` を使うとリストやタプルから集合を作ることができます。 228 | 229 | ```python 230 | x = set([1, 1, 2, 3, 4, 4]) # {1, 2, 3, 4} 231 | y = set(('x', 'x', 3.14, [1, 2], [1, 2])) # {'x', 3.14, [1, 2]} 232 | ``` 233 | 234 | 空の集合を作る場合は `x = set()` とします。`x = {}` としても空の辞書となるので注意してください。 235 | 236 | ## `None` 237 | 238 | 変数に `None` というキーワードを代入すると、その変数はどの型にも属さない変数になります。 239 | 240 | ```python 241 | x = None 242 | ``` 243 | 244 | 変数は用意したいけど、どのような値を入れるかは後で決めたいといったケースでは `None` が使用されます。 245 | 246 | ## 定数 247 | 248 | Python には定数の概念がありません。しかし、書き換えを想定しない変数は大文字で書いて定数であることを示すというルールがあります。 249 | 250 | ```python 251 | PI = 3.14 252 | ``` 253 | -------------------------------------------------------------------------------- /docs/ch02-02-functions.md: -------------------------------------------------------------------------------- 1 | # 関数 2 | 3 | 関数を定義するには `def` キーワードを使います。 4 | 5 | ```python 6 | #!/usr/bin/env python 7 | 8 | 9 | def another_function(): 10 | print('Another function') 11 | 12 | 13 | def main(): 14 | print('Hello, World!') 15 | 16 | another_function() 17 | 18 | 19 | if __name__ == '__main__': 20 | main() 21 | ``` 22 | 23 | 関数名は自由に与えることができますが、慣習的に英数字かつ *snake_case* (小文字を `_` でつなぐ記法)で表記します。 24 | 25 | !!! note 26 | 関数定義の間は慣習的に 2 行開けるルールになっています。 27 | 28 | ## 引数 29 | 30 | 関数には引数を渡すことができます。 31 | 32 | ```python 33 | def another_function(x, y): 34 | print(f'The value of x is {x}') 35 | print(f'The value of y is {y}') 36 | 37 | 38 | def main(): 39 | another_function(5, 6) 40 | ``` 41 | 42 | ## デフォルト引数 43 | 44 | 関数には引数を明示的に渡さなかった場合にデフォルト値を暗黙的に渡す機能があります。 45 | 46 | ```python 47 | def another_function(x, y=10): 48 | print(f'The value of x is {x}') 49 | print(f'The value of y is {y}') 50 | 51 | 52 | def main(): 53 | another_function(5, 3) # x: 5, y: 3 54 | another_function(7) # x: 7, y: 10 55 | ``` 56 | 57 | デフォルト引数は引数内で一番最後に渡す必要があります。 58 | 59 | ```python 60 | # OK 61 | def f1(x, y=10): 62 | print(f'The value of x is {x}') 63 | print(f'The value of y is {y}') 64 | 65 | 66 | # OK 67 | def f2(x, y=10, z=20): 68 | print(f'The value of x is {x}') 69 | print(f'The value of y is {y}') 70 | print(f'The value of z is {z}') 71 | 72 | 73 | # NG 74 | def f3(y=10, x, z=20): 75 | print(f'The value of x is {x}') 76 | print(f'The value of y is {y}') 77 | print(f'The value of z is {z}') 78 | ``` 79 | 80 | !!! note 81 | デフォルト引数の定義は慣習的に `=` の両端にはスペースを入れずに書きます。 82 | 83 | ## キーワード引数 84 | 85 | 関数を呼び出すときに引数名を指定すれば引数を順不同で渡すこともできます。 86 | 87 | ```python hl_lines="7" 88 | def another_function(x, y): 89 | print(f'The value of x is {x}') 90 | print(f'The value of y is {y}') 91 | 92 | 93 | def main(): 94 | another_function(y=10, x=20) 95 | ``` 96 | 97 | ## `*args, **kwargs` 98 | 99 | 関数の引数名に `*` が付いたものがあると、その変数は複数の引数を 1 つのタプルとして受け取るようになります。 100 | 101 | ```python 102 | def another_function(x, *args): 103 | print(f'The value of x is {x}') # x: 0 104 | print(f'The value of args is {args}') # args: (1, 2, 3) 105 | 106 | 107 | def main(): 108 | another_function(0, 1, 2, 3) 109 | ``` 110 | 111 | また引数名に `**` を付けると複数のキーワード引数を 1 つの辞書として受け取るようになります。 112 | 113 | ```python 114 | def another_function(x, **kwargs): 115 | print(f'The value of x is {x}') # x: 0 116 | print(f'The value of kwargs is {kwargs}') # kwargs: {'y': 10, 'z': 20} 117 | 118 | 119 | def main(): 120 | another_function(0, y=10, z=20) 121 | ``` 122 | 123 | `*args, **kwargs` は併用することも可能です。 124 | 125 | ```python 126 | def another_function(x, *args, **kwargs): 127 | print(f'The value of x is {x}') # x: 0 128 | print(f'The value of args is {args}') # args: (1, 2) 129 | print(f'The value of kwargs is {kwargs}') # kwargs: {'y': 10, 'z': 20} 130 | 131 | 132 | def main(): 133 | another_function(0, 1, 2, y=10, z=20) 134 | ``` 135 | 136 | !!! note 137 | `*` 引数と `**` 引数はどのような名前にしても構いませんが、慣習的に `*args, **kwargs` が使われます。 138 | 139 | ## 引数のアンパック 140 | 141 | 関数に引数を渡すときにタプルに `*` を付けて渡すとタプルの各要素を個別の引数として渡せるようになります。 142 | 143 | ```python 144 | def another_function(x, y, z): 145 | print(f'The value of x is {x}') # x: 0 146 | print(f'The value of y is {y}') # y: 1 147 | print(f'The value of z is {z}') # z: 2 148 | 149 | 150 | def main(): 151 | args = (0, 1, 2) 152 | another_function(*args) # another_function(0, 1, 2) 153 | ``` 154 | 155 | また辞書に `**` を付けて渡すと辞書の各要素をキーワード引数として渡せるようになります。 156 | 157 | ```python 158 | def another_function(x, y, z): 159 | print(f'The value of x is {x}') # x: 0 160 | print(f'The value of y is {y}') # y: 1 161 | print(f'The value of z is {z}') # z: 2 162 | 163 | 164 | def main(): 165 | kwargs = {'x': 0, 'y': 1, 'z': 2} 166 | another_function(**kwargs) # another_function(x=0, y=1, z=2) 167 | ``` 168 | 169 | ## 型ヒント 170 | 171 | 引数に特定の型だけを渡せるようにしたければ **型ヒント** を使用します。 172 | 173 | ```python 174 | def another_function(x: int, y: int): 175 | print(f'The value of x * y is {x * y}') 176 | ``` 177 | 178 | 型ヒントを使用しなければどんな型の変数も渡せます。これはしばしば混乱の元となるので、なるべく型ヒントを使うようにしましょう。 179 | 180 | ## 戻り値 181 | 182 | 関数の呼び出し側に値を返却する場合は `return` を使用します。 183 | 184 | ```python 185 | def plus_one(x): 186 | return x + 1 187 | 188 | 189 | def main(): 190 | x = plus_one(5) # x: 6 191 | 192 | print(f'The value of x is {x}') 193 | ``` 194 | 195 | 戻り値として返す型は複数あっても構いません。 196 | 197 | ```python 198 | def another_function(x): 199 | if x == 0: 200 | return x + 3.14 201 | else: 202 | return x + 1 203 | ``` 204 | 205 | この関数は `x` が `0` のときは `float` 型を返し、そうでないときは `int` 型を返します。複数の型を返すことを想定してなければ、型ヒントを使うことで誤って意図しない型を返却してしまうことを防ぐことができます。 206 | 207 | ```python 208 | def plus_one(x: int) -> int: 209 | return x + 1 210 | ``` 211 | 212 | Python の関数は必ず戻り値を持っており、`return` を明示的に使わなかった場合は `None` が返ります。 213 | 214 | ```python 215 | def f(x): 216 | print(f'The value of x is {x}') 217 | ``` 218 | 219 | この関数は下記と同じ意味になります。 220 | 221 | ```python 222 | def f(x): 223 | print(f'The value of x is {x}') 224 | return None 225 | ``` 226 | 227 | ## 空実装 228 | 229 | 関数の実装を空にしたい場合は `pass` と書いておきます。 230 | 231 | ```python 232 | # 何もしない関数 233 | def empty_function(): 234 | pass 235 | ``` 236 | -------------------------------------------------------------------------------- /docs/ch02-03-comments.md: -------------------------------------------------------------------------------- 1 | # コメント 2 | 3 | `#` から始まる行はコメントと見なされます。コメントはプログラム上は無視されるため、自由にメッセージを書くことができます。 4 | 5 | ```python 6 | # この行はコメントです。 7 | ``` 8 | 9 | コードの末尾に置くことも可能です。 10 | 11 | ```python 12 | def main(): 13 | lucky_number = 7 # I’m feeling lucky today. 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/ch02-04-conditions.md: -------------------------------------------------------------------------------- 1 | # 条件文 2 | 3 | ある条件を満たしている時だけ行いたい処理がある場合は条件文を使って処理を書きます。 4 | 5 | ## `if` 6 | 7 | ```python 8 | #!/usr/bin/env python 9 | 10 | 11 | def main(): 12 | check(3) 13 | check(7) 14 | 15 | 16 | def check(x): 17 | if x < 5: 18 | print('condition was true') 19 | else: 20 | print('condition was false') 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | ``` 26 | 27 | `x` の値が `5` 未満かどうかで出力されるメッセージが変化します。`if` に渡す値は必ずしも評価式である必要はありません。次のように値そのものを渡すこともできます。 28 | 29 | ```python 30 | x = 10 31 | 32 | if x: 33 | print('x is not 0') 34 | ``` 35 | 36 | この条件は `x` を `bool` に変換した値が評価されます。値を `bool` に変換した場合の結果は次のように評価されます。 37 | 38 | - `0, 0.0, '', b'', [], (), {}, set(), None` のとき `False` 39 | - それ以外のとき `True` 40 | 41 | ## `elif` 42 | 43 | 複数の条件を見たい場合は `if, elif, else` を使います。 44 | 45 | ```python 46 | def check(number): 47 | if number % 4 == 0: 48 | print('number is divisible by 4') 49 | elif number % 3 == 0: 50 | print('number is divisible by 3') 51 | elif number % 2 == 0: 52 | print('number is divisible by 2') 53 | else: 54 | print('number is not divisible by 4, 3, or 2') 55 | 56 | 57 | def main(): 58 | check(10) 59 | ``` 60 | 61 | `else if` ではなく `elif` であることに注意してください。 62 | 63 | !!! note "switch 文" 64 | Python には switch 文がありませんので `if, elif, else` を使用して下さい。 65 | 66 | ## 空の条件文 67 | 68 | 条件文内の処理を空にしたい場合は `pass` と書いておきます。 69 | 70 | ```python 71 | if x == 0: 72 | pass 73 | ``` 74 | 75 | ## 演算子 76 | 77 | 条件文で使用できる演算子には次のようなものがあります。 78 | 79 | ### `and` 80 | 81 | 複数の条件が成立するかどうかを調べるときに使用します。 82 | 83 | ```python 84 | def check(x): 85 | if x >= 0 and x < 10: 86 | print('x is in [0, 10)') 87 | 88 | 89 | def main(): 90 | check(10) 91 | ``` 92 | 93 | ただし変数の範囲チェックをする場合は `and` を使わなくてもシンプルな書き方ができます。 94 | 95 | ```python 96 | 97 | def check(x): 98 | if 0 <= x < 10: # x >= 0 and x < 10 と同じ 99 | print('x is in [0, 10)') 100 | 101 | 102 | def main(): 103 | check(10) 104 | ``` 105 | 106 | ### `or` 107 | 108 | 複数の条件のうちどれか 1 つが成立するかどうかを調べるときに使用します。 109 | 110 | ```python 111 | def check(x, y): 112 | if x > 0 or y > 0: 113 | print('Either x or y is a positive value') 114 | 115 | 116 | def main(): 117 | check(10, -3) 118 | ``` 119 | 120 | ### `in` 121 | 122 | リスト・タプル・集合内に特定の要素が含まれているかどうかを調べます。 123 | 124 | ```python 125 | def check(data, x): 126 | if x in data: 127 | print(f'{data} contains {x}') 128 | 129 | 130 | def main(): 131 | check([0, 1, 2, 3, 4], 3) 132 | ``` 133 | 134 | 辞書に対して使用するとキーの存在を調べることができます。 135 | 136 | ```python 137 | def check(data, key): 138 | if key in data: 139 | print(f'{data} contains the value of key {key}') 140 | 141 | 142 | def main(): 143 | check({'x': 0, 'y': 1, 'z': 2}, 'x') 144 | ``` 145 | 146 | ### `is` 147 | 148 | `is` は 2 つの変数が同じインスタンスを参照しているかどうかを調べるときに使用します。 149 | 150 | ```python 151 | def check(x, y): 152 | if x is y: 153 | print('x is y == True') 154 | 155 | 156 | def main(): 157 | x = [0, 1, 2] 158 | y = x 159 | z = x 160 | check(y, z) 161 | ``` 162 | 163 | ここでいう「同じ」とは保持している値が等価という意味ではありません。参照しているインスタンスが同じかどうかを意味しています。言い換えれば変数 `x, y` に対して、これらの代入元の変数をたどっていって同じ変数にたどり着くなら `x is y` は `True` になります。 164 | 165 | ```python 166 | x = [0, 1, 2] 167 | y = x 168 | z = y 169 | 170 | x is y # True 171 | y is z # True 172 | z is x # True 173 | 174 | a = [0, 1, 2] 175 | b = a 176 | 177 | x is a # False 178 | x is b # False 179 | ``` 180 | 181 | 変数に `None` が代入されているかどうかを調べるときは `==` ではなく `is` を使用します。 182 | 183 | ```python 184 | x is None 185 | ``` 186 | -------------------------------------------------------------------------------- /docs/ch02-05-loops.md: -------------------------------------------------------------------------------- 1 | # ループ文 2 | 3 | リストの要素に対して繰り返し同様の処理を実行する場合は `for` や `while` を使います。 4 | 5 | ## `for` 6 | 7 | リストに対して `for` を使う場合は次のように書きます。 8 | 9 | ```python 10 | values = [0, 1, 2, 3, 4] 11 | 12 | for value in values: 13 | print(f'The value is {value}') 14 | ``` 15 | 16 | タプルの場合も同様にして `for` に渡すことができます。辞書も渡すことができますが、この場合キーが各ループで参照されます。 17 | 18 | ```python 19 | items = {'a': 1, 'b': 2, 'c': 3} 20 | 21 | for key in items: 22 | print(f'The key is {key}') 23 | ``` 24 | 25 | 値のループあるいはキーと値の両方をループで参照したい場合はそれぞれ `values(), items()` メソッドを使用します。 26 | 27 | ```python 28 | items = {'a': 1, 'b': 2, 'c': 3} 29 | 30 | for value in items.values(): 31 | print(f'The value is {value}') 32 | 33 | for key, value in items.items(): 34 | print(f'The pair of key and value is ({key}, {value})') 35 | ``` 36 | 37 | ### `range()` 38 | 39 | 整数を順にループさせたい場合は `range()` という関数を使って実現できます。 40 | 41 | ```python 42 | for i in range(10): 43 | print(f'The value is {i}') 44 | ``` 45 | 46 | `range(10)` で 0 以上 10 未満(すなわち 0 ~ 9) の整数をループします。`range()` は終了値だけでなく、開始値と刻み幅を指定することもできます。 47 | 48 | ```python 49 | range(stop, start=0, step=1) 50 | ``` 51 | 52 | !!! note "例" 53 | - `range(2, 10)`: 2, 3, 4, 5, 6, 7, 8, 9 54 | - `range(2, 10, 2)`: 2, 4, 6, 8 55 | - `range(10, 0, -1)`: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 56 | 57 | ## `while` 58 | 59 | `while` は特定の条件が `True` である間ループ処理を続けるというものです。 60 | 61 | ```python 62 | x = [10, 20, 30, 40, 50] 63 | index = 0 64 | 65 | while index < 5: 66 | print(f'The value is {x[index]}') 67 | index += 1 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/ch02-06-list-comprehensions.md: -------------------------------------------------------------------------------- 1 | # リスト内包表記 2 | 3 | リスト内包表記とはリストを簡単に作成するための構文のことです。今 `[0, 2, 4, 6, ..., 98]` というリストを作ろうとした場合、 4 | 5 | ```python 6 | x = [] 7 | 8 | for i in range(50): 9 | x.append(i * 2) 10 | ``` 11 | 12 | のようにループ処理をして要素を詰めて作ることができますが、リスト内包表記を使うと下記のようにもっとシンプルに書くことができます。 13 | 14 | ```python 15 | x = [i * 2 for i in range(50)] 16 | ``` 17 | 18 | リスト内包表記は別のコレクション型から変換を行う際によく使われます。例えば下記は辞書からタプルを要素とするリストを作成する例です。 19 | 20 | ```python 21 | x = {'a': 10, 'b': 20, 'c': 30} 22 | y = [(key, val) for key, val in x.items()] # [('a', 10), ('b', 20), ('c', 30)] 23 | ``` 24 | 25 | ループ処理を短く書くことができますので活用するとコードがシンプルになります。 26 | -------------------------------------------------------------------------------- /docs/ch02-07-lambdas.md: -------------------------------------------------------------------------------- 1 | # ラムダ式 2 | 3 | ラムダ式とは関数を変数に代入して扱えるようにするものです。ラムダ式が必要になってくるシーンは関数を別の関数の引数として渡す必要があるときです。 4 | 5 | !!! Tips 6 | 引数として渡される関数のことをコールバックといいます。 7 | 8 | 例として `map()` という組み込み関数について考えてみます。`map()` はリストなどのコレクションの各要素に何らかの変換を加えたコレクションを新たに作成する関数です。 9 | 10 | ```python 11 | a = [1, 2, 3, 4, 5] 12 | ``` 13 | 14 | 上記のリストの各要素を 2 乗して `[1, 4, 9, 16, 25]` というリストを `map()` で作成するには次のようにします。 15 | 16 | ```python 17 | list(map(lambda x: x * x, a)) 18 | ``` 19 | 20 | `map()` の第 1 引数にはラムダ式が渡されています。ラムダ式は機能的には通常の関数と同様の機能を提供しており 21 | 22 | ```python 23 | lambda x: x * x 24 | ``` 25 | 26 | というラムダ式は下記の関数と等価なものになっています。 27 | 28 | ```python 29 | def power(x): 30 | return x * x 31 | ``` 32 | 33 | `map()` は第 2 引数に渡されたコレクションの各要素をラムダ式に渡し、その戻り値を逐次返却していくという振る舞いをします。ちなみに `map()` には上記の `power()` のような通常の関数を渡すこともできます。 34 | 35 | ```python 36 | list(map(power, a)) 37 | ``` 38 | 39 | ラムダ式が通常の関数と異なる点は 40 | 41 | - 名前を持たない(無名関数) 42 | - 必要なときに即時に定義できる 43 | 44 | という点です。`map()` に渡すためのちょっとした処理に対して、わざわざ関数定義をするのも面倒だといったケースではラムダ式が使われます。その他の例もいくつか挙げておきます。 45 | 46 | ```python 47 | list(map(lambda x: 2 * x, a)) # [2, 4, 6, 8, 10] 48 | list(map(lambda x: (x, 2 * x + 1), a)) # [(1, 3), (2, 5), (3, 7), (4, 9), (5, 11)] 49 | ``` 50 | 51 | `map()` の戻り値は [ジェネレータ](./ch07-01-generators.md) というオブジェクトが返ります。ジェネレータ自体はリストではないですが、ループ文に渡すことでリストと同じように変換結果の要素を取り出すことができます。 52 | 53 | ```python 54 | for item in map(lambda x: x * x, a): 55 | print(item) # 1, 4, 9, 16, 25 56 | ``` 57 | 58 | ジェネレータを `list()` に渡すとリストに変換してくれますが、ループ文で要素を参照するだけであればリストに変換する必要はありません。インデックス参照などがしたい場合にリストに変換すると良いでしょう。`map()` のようにコレクションとコールバックを受け取りジェネレータを返す関数は他にもいくつか用意されています。代表的なものには [itertools] があります。 59 | 60 | [itertools]: https://docs.python.org/ja/3/library/itertools.html 61 | -------------------------------------------------------------------------------- /docs/ch03-01-classes.md: -------------------------------------------------------------------------------- 1 | # クラス 2 | 3 | クラスとは新しい型を定義するための仕組みです。クラスを使うことで複数の変数と関数を集約した変数を作ることができるようになります。 4 | 5 | ## 定義 6 | 7 | クラスを定義するには `class` キーワードにクラス名を付けて定義します。下記の `Rectangle` クラスは長方形に関する情報を扱うクラスで、長方形の幅と高さをメンバ変数として持ったクラスになります。 8 | 9 | ```python hl_lines="5 6 7 8" 10 | #!/usr/bin/env python 11 | 12 | 13 | class Rectangle: 14 | def __init__(self, width, height): 15 | self.width = width 16 | self.height = height 17 | 18 | 19 | def main(): 20 | rectangle = Rectangle(10, 20) 21 | 22 | print(f'The value of width is {rectangle.width}') 23 | print(f'The value of height is {rectangle.height}') 24 | 25 | 26 | if __name__ == '__main__': 27 | main() 28 | ``` 29 | 30 | ## `__init__()` 31 | 32 | `__init__()` はいわゆるコンストラクタです。コンストラクタには 2 つの役割があります。 33 | 34 | 1. インスタンス化をするときに最初に呼び出される 35 | 1. クラスのメンバ変数を定義し、それを初期化する 36 | 37 | クラスにどのようなメンバ変数を用意したいかということも含めてコンストラクタで定義することに注意して下さい。 38 | 39 | 引数には初期化に必要な値を渡すことができます。ただし第 1 引数は必ず `self` という引数にして下さい。`self` については後述します。 40 | 41 | クラスで扱うメンバ変数を定義したいときは次のようにします。 42 | 43 | ```python hl_lines="3 4" 44 | class Rectangle: 45 | def __init__(self, width, height): 46 | self.width = width 47 | self.height = height 48 | ``` 49 | 50 | これはクラスのメンバ変数として `width, height` を用意するという意味になります。これで `Rectangle` という型が作成され、その型は `width, height` をメンバ変数として持っていることになります。 51 | 52 | `width` に `10` 、`height` に `20` を渡して `Rectangle` の変数を作るには次のようにします。 53 | 54 | ```python 55 | def main(): 56 | rectangle = Rectangle(10, 20) 57 | 58 | print(f'The value of width is {rectangle.width}') 59 | print(f'The value of height is {rectangle.height}') 60 | ``` 61 | 62 | ## `self` 63 | 64 | `Rectangle` をインスタンス化する際は `10, 20` という 2 つの値しか渡してないのに `__init__()` の第 1 引数には `self` という変数が含まれており、合計 3 つの引数が用意されています。これは一体どういうことでしょうか。この謎はクラスのインスタンス化の仕組みを理解すれば分かってきます。 65 | 66 | `rectangle = Rectangle(10, 20)` という処理をイメージで説明すると、だいたいこんな感じになります。 67 | 68 | ```python 69 | rectangle = (Rectangle クラスの変数を用意) # この時点では rectangle.width, rectangle.height は存在しない 70 | Rectangle.__init__(rectangle, 10, 20) # rectangle.width = width, rectangle.height = height という処理が実行される 71 | ``` 72 | 73 | すなわち `self` というのはクラスインスタンスを表す変数です。 74 | 75 | ## 関数 76 | 77 | クラスには関数を定義することもできます。クラスのメンバとして定義された関数はメソッドとも呼ばれます。 78 | 79 | ```python hl_lines="10 11 16" 80 | #!/usr/bin/env python 81 | 82 | 83 | class Rectangle: 84 | def __init__(self, width, height): 85 | self.width = width 86 | self.height = height 87 | 88 | def area(self): 89 | return self.width * self.height 90 | 91 | 92 | def main(): 93 | rectangle = Rectangle(10, 20) 94 | area = rectangle.area() 95 | 96 | print(f'The area is {area}') 97 | 98 | 99 | if __name__ == '__main__': 100 | main() 101 | ``` 102 | 103 | クラスに関数を定義するときは第 1 引数が `self` である必要があります。これは `rectangle.area()` という呼び出しは実際には `Rectangle.area(rectangle)` のように呼び出されるためです。 104 | 105 | ## 継承 106 | 107 | 定義済みのクラスのメンバを持つ新たなクラスを作成することもできます。 108 | 109 | ```python 110 | #!/usr/bin/env python 111 | 112 | 113 | class Square(Rectangle): 114 | def __init__(self, size): 115 | super().__init__(size, size) 116 | 117 | 118 | def main(): 119 | square = Square(10) 120 | area = square.area() 121 | 122 | print(f'The area is {area}') 123 | 124 | 125 | if __name__ == '__main__': 126 | main() 127 | ``` 128 | 129 | `Square` クラスは `Rectangle` クラスのメンバを全て引き継いだクラスになっており、これをクラスの継承といいます。`Rectangle` が長方形を扱うクラスなのに対し、`Square` は正方形を扱うクラスになっており、`Rectangle` が持つ全てのメンバ `square.width, square.height, square.area()` が参照できます。 130 | 131 | ## インターフェース 132 | 133 | Python にはインターフェースが存在しません。また多態性を実現するのにクラスに継承関係を作る必要もありません。それを確認するために継承関係にない 2 つのクラス `A, B` を作ってみます。またメソッドとして `greet()` という関数をそれぞれのクラスで定義しておきます。 134 | 135 | ```python hl_lines="5 6 13 14" 136 | class A: 137 | def __init__(self): 138 | pass 139 | 140 | def greet(self): 141 | print('This is class A') 142 | 143 | 144 | class B: 145 | def __init__(self): 146 | pass 147 | 148 | def greet(self): 149 | print('This is class B') 150 | ``` 151 | 152 | 次に `greet()` 関数をメンバに持った変数を受け取るような関数 `call()` を次のように作成します。 153 | 154 | ```python 155 | def call(x): 156 | x.greet() 157 | ``` 158 | 159 | この関数に `A, B` の変数をそれぞれ渡してみます。 160 | 161 | ```python 162 | def main(): 163 | call(A()) 164 | call(B()) 165 | ``` 166 | 167 | ソースコードの全体は次のようになります。 168 | 169 | ```python 170 | #!/usr/bin/env python 171 | 172 | 173 | class A: 174 | def __init__(self): 175 | pass 176 | 177 | def greet(self): 178 | print('This is class A') 179 | 180 | 181 | class B: 182 | def __init__(self): 183 | pass 184 | 185 | def greet(self): 186 | print('This is class B') 187 | 188 | 189 | def call(x): 190 | x.greet() 191 | 192 | 193 | def main(): 194 | call(A()) 195 | call(B()) 196 | 197 | 198 | if __name__ == '__main__': 199 | main() 200 | ``` 201 | 202 | このコードは問題なく実行することができます。Python は例え継承関係を持っていなくてもクラスの構造(メンバ変数や関数の名前・引数)が一致していれば同種の型として扱えるようになります。このような多態性の実現方法のことを **ダックタイピング** といいます。 203 | -------------------------------------------------------------------------------- /docs/ch03-02-scopes.md: -------------------------------------------------------------------------------- 1 | # スコープ 2 | 3 | 他の言語ではクラスメンバに対して `public, protected, private` といったアクセス指定子を指定することができますが Python ではそのような指定はできず、すべてのメンバが `public` として扱われます。ですが習慣的に下記のような命名規則でメンバのスコープを区別するようになっています。 4 | 5 | | スコープ | 命名規則 | 6 | |-----------|--------------| 7 | | public | `method()` | 8 | | protected | `_method()` | 9 | | private | `__method()` | 10 | 11 | Python では `__method()` のような private メソッドを作ることはそれほど多くはありません。隠蔽したいメンバを定義するときは `_method()` のような protected メソッドを定義することのほうが多いです。 12 | -------------------------------------------------------------------------------- /docs/ch03-03-special-attributes.md: -------------------------------------------------------------------------------- 1 | # 特殊属性 2 | 3 | クラスには特殊属性と呼ばれるメソッドが存在しており、クラスを使う際に使用される構文はどれも特殊属性の呼び出しに変換されて実行されます。代表的な特殊属性をいくつか紹介します。 4 | 5 | 下記のようなクラスを定義したときの特殊属性には次のようなものがあります。 6 | 7 | ```python 8 | import math 9 | 10 | 11 | class Point: 12 | """Point クラスです。""" 13 | 14 | def __init__(self, x, y): 15 | self.x = x 16 | self.y = y 17 | 18 | def distance(self): 19 | return math.sqrt(self.x * self.x + self.y * self.y) 20 | ``` 21 | 22 | ## `__doc__` 23 | 24 | `Point.__doc__` という変数には `"""Point クラスです。"""` という文字列が入ります。`""" ... """` というトリプルクオテーションでくくられた文字列は改行を含む複数行の文字列を定義できる文字列です。 25 | 26 | ```python 27 | """この文字列は 28 | 一つの文字列として 29 | 扱われます。""" 30 | ``` 31 | 32 | `__doc__` は docstring と呼ばれ、ドキュメンテーションをする際に使用される文字列として扱われます。 33 | 34 | ## `__init__` 35 | 36 | クラスインスタンスを作成するときは `__init__()` が呼び出されます。 37 | 38 | ```python 39 | point = Point(10, 20) # point.__init__(10, 20) 40 | ``` 41 | 42 | ## `__getattribute__` 43 | 44 | クラスのメンバやメソッドを参照したときは `__getattribute__()` が呼び出されます。 45 | 46 | ```python 47 | point = Point(10, 20) 48 | point.x # point.__getattribute__('x') 49 | point.distance() # point.__getattribute__('distance')() 50 | ``` 51 | 52 | ## `__getattr__` 53 | 54 | `__getattribute__()` でのメンバ参照に失敗した場合は `__getattr__()` が呼び出されます。このような事が起こるのはクラスのメンバとして定義されていないものにアクセスしようとしたときに起こります。 55 | 56 | ```python 57 | point.x2 # point.__getattr__('x2') 58 | ``` 59 | 60 | `__getattr__()` は明示的には定義されません。使用するには自分で定義する必要があります。 61 | 62 | ```python 63 | class Point: 64 | ... 65 | 66 | def __getattr__(self, item): 67 | if item == 'x2': 68 | return self.x * self.x 69 | elif item == 'y2': 70 | return self.y * self.y 71 | ``` 72 | 73 | ## `__getitem__` 74 | 75 | クラスインスタンスに対して `[]` を使用した際には `__getitem__()` が呼び出されます。 76 | 77 | ```python 78 | point = Point(10, 20) 79 | point['x'] # point.__getitem__('x') 80 | ``` 81 | 82 | `__getitem__()` を使用するには自分で定義する必要があります。 83 | 84 | ```python 85 | class Point: 86 | ... 87 | 88 | def __getitem__(self, item): 89 | if item == 'x': 90 | return self.x 91 | elif item == 'y': 92 | return self.y 93 | ``` 94 | 95 | ## `__setattr__` 96 | 97 | メンバへの代入が行われた場合は `__setattr__()` が呼び出されます。 98 | 99 | ```python 100 | point = Point(10, 20) 101 | point.x = 30 # point.__setattr__('x', 30) 102 | ``` 103 | 104 | ## `__eq__` 105 | 106 | インスタンスに対して `==` が使用された場合は `__eq__()` が呼ばれます。 107 | 108 | ```python 109 | point1 = Point(10, 20) 110 | point2 = Point(30, 40) 111 | point1 == point2 # point1.__eq__(point2) 112 | ``` 113 | 114 | その他類似の特殊属性が下記の通り用意されています。 115 | 116 | | 特殊属性 | 意味 | 117 | |----------|--------------------| 118 | | `__ne__` | `point1 != point2` | 119 | | `__le__` | `point1 <= point2` | 120 | | `__lt__` | `point1 < point2` | 121 | | `__ge__` | `point1 >= point2` | 122 | | `__gt__` | `point1 > point2` | 123 | 124 | ## `__str__` 125 | 126 | クラスインスタンスに対して文字列型へのキャストを行うと `__str__()` が呼び出されます。明示的なキャストでなくても文字列への変換が必要とされるケースでも同様の振る舞いをします。 127 | 128 | ```python 129 | point = Point(10, 20) 130 | print(point) # print(point.__str__()) 131 | ``` 132 | 133 | `__str__()` はデフォルトで定義されていますが、大抵の場合は有益な文字列にはなっていないので自分で定義したほうが良いです。 134 | 135 | ```python 136 | class Point: 137 | ... 138 | 139 | def __str__(self): 140 | return f'' 141 | ``` 142 | -------------------------------------------------------------------------------- /docs/ch03-04-properties.md: -------------------------------------------------------------------------------- 1 | # プロパティ 2 | 3 | クラスのメンバで変数のように参照することのできる関数のことをプロパティといいます。 4 | 5 | ```python 6 | import math 7 | 8 | 9 | class Point: 10 | def __init__(self, x, y): 11 | self.x = x 12 | self.y = y 13 | 14 | @property 15 | def distance(self): 16 | return math.sqrt(self.x * self.x + self.y * self.y) 17 | ``` 18 | 19 | `@property` のように `@` ではじまるキーワードは **デコレータ** といって関数やクラスに特殊な振る舞いを注入することのできる機能になっています。`distance()` は関数として定義されていますが `@property` デコレータがついていると変数のように参照することができるようになります。 20 | 21 | ```python 22 | point = Point(10, 20) 23 | point.distance # 22.360679775 24 | ``` 25 | 26 | プロパティは `point.distance()` のように呼ぶことはできません。 27 | 28 | メンバ変数 `x, y` を隠蔽して、代わりにプロパティを提供すると代入ができなくなるので安全です。 29 | 30 | ```python 31 | import math 32 | 33 | 34 | class Point: 35 | def __init__(self, x, y): 36 | # _ をつけて隠蔽していることを示す 37 | self._x = x 38 | self._y = y 39 | 40 | @property 41 | def x(self): 42 | return self._x 43 | 44 | @property 45 | def y(self): 46 | return self._y 47 | 48 | @property 49 | def distance(self): 50 | return math.sqrt(self.x * self.x + self.y * self.y) 51 | ``` 52 | 53 | ```python 54 | point = Point(10, 20) 55 | print(point.x, point.y) # _x, _y の参照は可能 56 | point.x = 30 # _x, _y への代入はできない 57 | ``` 58 | 59 | プロパティに対して代入を行いたい場合は `setter` プロパティを定義します。 60 | 61 | ```python 62 | @property 63 | def x(self): 64 | return self._x # getter 65 | 66 | @x.setter 67 | def x(self, value): 68 | self._x = value # setter 69 | ``` 70 | 71 | 72 | ```python 73 | point.x = 30 # OK 74 | ``` 75 | 76 | プロパティの利点は `get_x()` のような関数を定義しなくても、まるで変数を直接参照しているようなコードが書ける点にあります。これはクラスを利用する人からするとシンプルなコードが書けるので便利です。ただしプロパティは変数と同じくらいに気軽に参照するメンバになるので、パフォーマンスが遅い関数をむやみにプロパティとして定義するのは避けたほうが良いです。 -------------------------------------------------------------------------------- /docs/ch04-01-modules.md: -------------------------------------------------------------------------------- 1 | # モジュール 2 | 3 | モジュールとは関数やクラスなどを別ファイルで利用できる状態で整理した Python のソースコードのことです。これまでのソースコードは実行することを念頭に実装しましたが、モジュールは別のファイルから取り込まれることを念頭に実装を行います。 4 | 5 | ## モジュールの書き方 6 | 7 | 拡張子 `.py` を持った通常の Python ファイルとして作成すれば問題ありません。ただしシバンはモジュールを実装するときには不要です。たとえ書いても悪さをすることはありませんが、実行するスクリプトとしては使用しないので書く意味はあまりありません。 8 | 9 | フィボナッチ数列 `0, 1, 1, 2, 3, 5, 8, ...` を出力するような関数 `fib(n)` をモジュールとして書いてみましょう。 10 | 11 | **fib.py** 12 | 13 | ```python 14 | 15 | 16 | def fib(n): 17 | a, b = 0, 1 18 | 19 | while a < n: 20 | print(a, end=' ') 21 | a, b = b, a + b 22 | 23 | print() 24 | ``` 25 | 26 | `fib.py` を別のファイル `main.py` に取り込んでこの関数を実行するには次のように書きます。 27 | 28 | **main.py** 29 | 30 | ```python 31 | #!/usr/bin/env python 32 | 33 | 34 | import fib # fib.py を取り込む 35 | 36 | 37 | def main(): 38 | fib.fib(10) # fib.py 内の fib() を呼び出す 39 | 40 | 41 | if __name__ == '__main__': 42 | main() 43 | ``` 44 | 45 | ## モジュール作成の注意点 46 | 47 | Python のコードは必ずしも関数内に含める必要はなく、ファイル内に直接処理を書くこともできます。 48 | 49 | **fib.py** 50 | 51 | ```python 52 | 53 | 54 | a, b = 0, 1 55 | 56 | while a < n: 57 | print(a, end=' ') 58 | a, b = b, a + b 59 | 60 | print() 61 | ``` 62 | 63 | しかしこの状態で `main.py` から `import fib` をすると import した時点でフィボナッチ数列を出力する処理が突然実行されてしまいます。こういった問題が起こらないようにモジュールとして提供する機能は基本的に関数やクラスにまとめておく必要があります。 64 | 65 | ## `from-import` 66 | 67 | モジュール内の関数やクラスにアクセスするときは `モジュール名.関数名` のように `.` を使ってアクセスしますが、モジュール名や関数名が長くなってくると少し書き方が面倒になります。こういうときは `from` 文を使って参照名を短くすることができます。 68 | 69 | ```python 70 | from (モジュール名) import (関数名) 71 | ``` 72 | 73 | このようにすると `モジュール名.関数名` としていたところが `関数名` だけで参照できるようになります。 74 | 75 | ```python 76 | #!/usr/bin/env python 77 | 78 | 79 | from fib import fib # fib.py 内の fib() をインポート 80 | 81 | 82 | def main(): 83 | fib(10) # fib() を呼び出す 84 | 85 | 86 | if __name__ == '__main__': 87 | main() 88 | ``` 89 | 90 | ただし `import fib` とインポートしたときは `fib.py` 内のすべての関数やクラスが参照できるのに対し、 `from fib import fib` は `fib()` のみが参照できます。複数の定義をインポートしたいときは次のようにします。 91 | 92 | ```python 93 | from (モジュール名) import (関数名), (関数名), ... 94 | ``` 95 | 96 | モジュール内の全定義をまとめて `from` でインポートしたければ次のようにします。 97 | 98 | ```python 99 | from (モジュール名) import * 100 | ``` 101 | 102 | ただしこのインポート文は余計な定義をインポートしてしまう恐れがあるため推奨されません。モジュールのインポート方法は次の優先度で検討して下さい。 103 | 104 | | おすすめ | インポート文 | 105 | |--------------|---------------------------------------| 106 | | :heart_eyes: | `import (モジュール名)` | 107 | | :smiley: | `from (モジュール名) import (関数名)` | 108 | | :confounded: | `from (モジュール名) import *` | 109 | 110 | ## `__name__` 111 | 112 | `__name__` は Python で使用できる隠し変数の 1 つで、モジュール名を表す文字列が入っています。例えば `foo.py` というモジュールであれば `__name__` は `'foo'` になります。 113 | 114 | ```python 115 | import foo 116 | 117 | print(foo.__name__) # 'foo' 118 | ``` 119 | 120 | しかしこの変数がモジュール名になるのは別のモジュールからインポートされたときだけです。`foo.py` を直接実行した場合は `__name__` には `'__main__'` が入ります。 121 | 122 | **foo.py** 123 | 124 | ```python 125 | #!/usr/bin/env python 126 | 127 | print(__name__) 128 | ``` 129 | 130 | ```shell 131 | $ python foo.py 132 | __main__ 133 | ``` 134 | 135 | つまり `__name__` の値がモジュール名になるのか `'__main__'` になるのかを見ることでインポートされようとしているのかどうかを判断することができるようになります。 136 | 137 | [Hello, World!](./ch01-02-hello-world.md) のソースコードで出てきた 138 | 139 | ```python 140 | if __name__ == '__main__': 141 | main() 142 | ``` 143 | 144 | という構文は自分がインポートされてないときだけ `main()` を呼び出すという意味になります。 `__name__` の値を見ることで 1 つのファイルで実行スクリプトとモジュールの両方を実装することができるようになります。 145 | -------------------------------------------------------------------------------- /docs/ch04-02-packages.md: -------------------------------------------------------------------------------- 1 | # パッケージ 2 | 3 | モジュールを使うことで関数やクラスをまとめることができますが、1 つのモジュール内にたくさんの定義を含めてしまうとコードが長くなってしまい分かりづらくなってしまいます。そういうときはファイルを分割して複数のモジュールを作成し、それを 1 つのディレクトリに集約することで綺麗に整理することができます。このように複数のモジュールを集約したディレクトリのことをパッケージといいます。 4 | 5 | モジュールとパッケージの関係はファイルシステムでいうところのファイルとディレクトリの関係に一致します。 6 | 7 | | ファイルシステム | Python | 8 | |------------------|------------| 9 | | ファイル | モジュール | 10 | | ディレクトリ | パッケージ | 11 | 12 | ファイル・ディレクトリのことを Python の世界ではモジュール・パッケージと呼んでいると理解して概ね問題ありません。 13 | 14 | ## パッケージの作り方 15 | 16 | パッケージはディレクトリのことだと説明しましたが、単に Python のソースコードをディレクトリに集約しただけではパッケージにはなりません。ディレクトリをパッケージとして扱うためには `__init__.py` というファイルをディレクトリ内に用意しておく必要があります。 17 | 18 | ``` 19 | example 20 | ├── __init__.py # これがないとパッケージとは呼べない 21 | ├── a.py 22 | └── b.py 23 | ``` 24 | 25 | `__init__.py` は空ファイルとして作成してもらって問題ありません。Python のソースコードをディレクトリに集約するときは必ず `__init__.py` を作るようにしましょう。 26 | 27 | ## パッケージを使用する 28 | 29 | パッケージもモジュールと同様、他のファイルに取り込まれることを想定した機能なので `import` 構文を使って取り込む 30 | ことができます。いま、パッケージの構成が次のようになっていたとしましょう。 31 | 32 | ``` 33 | example 34 | ├── __init__.py 35 | ├── a.py 36 | │ └── def fib(n) 37 | └── b.py 38 | ``` 39 | 40 | このとき、関数 `fib(n)` を呼び出したいときには次のようにします。 41 | 42 | ```python 43 | #!/usr/bin/env python 44 | 45 | 46 | import example.a 47 | 48 | 49 | def main(): 50 | example.a.fib(10) 51 | 52 | 53 | if __name__ == '__main__': 54 | main() 55 | ``` 56 | 57 | `パッケージ名.モジュール名.関数名` のように `.` で区切って関数にアクセスすることができます。ちなみにパッケージ内にパッケージが含まれていても問題ありません。 58 | 59 | !!! warning "モジュール・パッケージ名の命名規則" 60 | モジュール名・パッケージ名に `-` を使用するのは避けましょう。なぜなら `xxx-yyy` というモジュール・パッケージを作ったとき 61 | 62 | ```python 63 | import xxx-yyy 64 | ``` 65 | 66 | のように import することになりますが `-` が引き算だと解釈されてしまいます。一般的に命名規則は次のようなルールになっています。 67 | 68 | - 全て小文字 69 | - パッケージ名に区切り文字は禁止 70 | - モジュール名の区切り文字は `_` を使用 71 | 72 | モジュール名であっても区切り文字はできるだけ使用しない方が好まれます。パッケージ名は例え複数語であっても区切り文字は使用しません。 73 | 74 | ## `__init__.py` 75 | 76 | `__init__.py` というファイルがどのような働きをするのか見ていきたいと思います。上記のサンプルにある 77 | 78 | ```python 79 | import example.a 80 | ``` 81 | 82 | というインポートを下記のように変更すると `example.a.fib()` の参照が正しくできなくなります。 83 | 84 | ```python 85 | import example 86 | ``` 87 | 88 | 両者の違いはインポートにモジュール名 `example.a` を指定しているか、パッケージ名 `example` を指定しているかにあります。インポートでパッケージ名を指定したときは `example` 配下にある `__init__.py` を探してインポートを実行するという振る舞いをします。 89 | 90 | ```python 91 | import example # example/__init__.py を参照する 92 | ``` 93 | 94 | もし `__init__.py` 内に何も記述がなければ `import example` では何も取り込まれないことになります。そのため `example.a.fib()` の参照はエラーになってしまいます。たとえ `example` ディレクトリ配下に `a.py` があったとしても Python からは `__init__.py` の定義に従ってパッケージの階層を参照しているということです。 95 | 96 | これに対して `import example.a` の場合だとうまくいくのは、モジュール `example/a.py` を直接インポートしており `__init__.py` の参照が起こらないからです。 97 | 98 | `import example` として `example.a.fib()` を参照できるようにするためには `__init__.py` 内でモジュール `a` が参照できるようにしてあげる必要があります。これは下記のようにすることで実現できます。 99 | 100 | **`example/__init__.py`** 101 | 102 | ```python 103 | from . import a 104 | ``` 105 | 106 | これは `__init__.py` と同一のディレクトリ階層から `a` をインポートするという構文で **相対インポート** といいます。こうすることで `__init__.py` 内でモジュール `a` が参照できるようになるため `import example` としても `example.a.fib()` が参照できるようになります。 107 | 108 | ## モジュール検索パス 109 | 110 | `__init__.py` 内の記述を 111 | 112 | ```python 113 | import a 114 | ``` 115 | 116 | と書いてもうまくいきません。 `import example` としてもモジュール `a` が見つからないというエラーが起こってしまいます。これは Python がモジュールを探しに行くときに、決まったルールで検索をしているからです。Python はモジュールを探しに行くときに次の順序でモジュールを検索します。 117 | 118 | 1. ビルトインモジュール(Python に最初から組み込まれたモジュール) 119 | 2. `python` コマンドに渡したファイルのあるディレクトリ直下 120 | 121 | 例えばファイル構成が下記のようになっていた場合 `python main.py` と実行して Python がモジュールを探しに行くディレクトリは `root` 直下になります。 122 | 123 | ``` 124 | root # ここ 125 | ├── example # ここは探しに行かない 126 | │ ├── __init__.py 127 | │ └── a.py 128 | └── main.py 129 | ``` 130 | 131 | ソースコードのどの場所で `import a` を書いても `root` 直下から `a.py` を探そうとします。相対インポートはこのルールに従わずにモジュールを検索してくれるため、うまくいくということです。 132 | -------------------------------------------------------------------------------- /docs/ch04-03-pip.md: -------------------------------------------------------------------------------- 1 | # pip 2 | 3 | `pip` はインターネットで公開されている Python パッケージを取得するためのパッケージ管理ツールです。 4 | 5 | ## 使い方 6 | 7 | [requests] という HTTP に関する機能を取り扱う有名なライブラリがあります。これを `pip` で取得して使ってみましょう。 8 | 9 | [requests]: http://docs.python-requests.org/en/master/ 10 | 11 | ターミナル上で次のコマンドを実行して下さい。 12 | 13 | ```shell 14 | $ pip3 install requests 15 | ``` 16 | 17 | 成功したら次のようなソースコードを書いてみましょう。 18 | 19 | ```python 20 | #!/usr/bin/env python 21 | 22 | 23 | import requests 24 | 25 | 26 | def main(): 27 | response = requests.get('http://example.com') 28 | print(response.text) 29 | 30 | 31 | if __name__ == '__main__': 32 | main() 33 | ``` 34 | 35 | 実行すると [http://example.com](http://example.com) のページの HTML 文字列が出力されると思います。 36 | 37 | ## PyPI 38 | 39 | `pip` によってインストールされるパッケージはどこから取得されているのでしょうか。 Python は [PyPI] というパッケージを登録しておける Web サイトがあり、 `pip` を実行すると PyPI からパッケージがインストールされます。上記の requests も下記の通り PyPI に登録されています。 40 | 41 | !!! info "PyPI" 42 | [https://pypi.org/project/requests/](https://pypi.org/project/requests/) 43 | 44 | [PyPI]: https://pypi.org/ 45 | 46 | ## freeze 47 | 48 | インストール済みのパッケージ一覧を確認するには `freeze` というコマンドを実行します。 49 | 50 | ```shell 51 | $ pip3 freeze 52 | certifi==2019.3.9 53 | chardet==3.0.4 54 | idna==2.8 55 | requests==2.21.0 56 | urllib3==1.24.1 57 | ``` 58 | 59 | `requests` とその依存パッケージがバージョン番号とともに表示されます。 60 | 61 | ## requirements.txt 62 | 63 | インストールしたいパッケージをあらかじめファイルに列挙しておき、そのファイルを指定することでもインストールすることができます。インストールしたいパッケージを記述したファイルは通常 `requirements.txt` という名前で保存します。`requirements.txt` を指定してインストールをするには次のようにします。 64 | 65 | ```shell 66 | $ pip3 install -r requirements.txt 67 | ``` 68 | 69 | `requirements.txt` は自分の環境にパッケージをインストールする目的で使用することはあまりありません。むしろ他の人の環境で自分が使っているパッケージをインストールして欲しいときに使用します。また `requirements.txt` は手で作成する必要はなく、`pip freeze` の結果を保存しておくだけで使用できます。`pip freeze` の結果にあるような `==version` という形式のものを `pip` でインストールすると指定されたバージョンをインストールしてくれるため、自分で入れたバージョンと全く同じものを他の人の環境でもインストールしてもらえるようになり、環境差分をなくすことができます。 70 | 71 | まとめるとパッケージ管理は次のような手順で行うことになります。 72 | 73 | **自分の環境** 74 | 75 | ```shell 76 | $ pip3 install name1 name2 ... 77 | $ pip3 freeze > requirements.txt 78 | ``` 79 | 80 | `requirements.txt` は Git などでバージョン管理をしておきます。 81 | 82 | **他の人の環境** 83 | 84 | ```shell 85 | $ pip3 install -r requirements.txt 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/ch04-04-venv.md: -------------------------------------------------------------------------------- 1 | # venv 2 | 3 | `pip` を使ってサードパーティ製のパッケージをインストールすることができましたが、インストールしたいパッケージが稀に競合を起こすことがあります。 4 | 5 | | プログラム | `requests` の要求バージョン | 6 | |------------|-----------------------------| 7 | | program1 | 2.21.0 | 8 | | program2 | 2.20.1 | 9 | 10 | program1 では `requests` 2.21.0 を使おうとしているのに対し program2 では 2.20.1 を使いたかったとすると、システムの中に複数バージョンの `requests` をインストールしておく必要が出てきます。もし使用したいパッケージのバージョンが意図しないものになっているとプログラムが正しく動かない可能性が出てきます。 11 | 12 | ## 仮想環境の作成 13 | 14 | このような競合を解決するために Python には各プログラムで使用したいパッケージを互いに影響がない形で個別に管理するための仕組みが用意されており、仮想環境と呼ばれます。仮想環境を作成するためのツールは `venv` と呼ばれ、Python に標準で同梱されています。 15 | 16 | 試しにこれから 2 つの仮想環境を作成し、それぞれに異なるバージョンの `requests` をインストールしてみます。まず下記のように 2 つのディレクトリを作成します。 17 | 18 | ```shell 19 | $ mkdir program1 program2 20 | ``` 21 | 22 | 次に program1 配下で仮想環境を作成します。 23 | 24 | === "macOS" 25 | 26 | ```shell 27 | $ cd program1 28 | $ python3 -m venv .venv 29 | ``` 30 | 31 | === "Windows" 32 | 33 | ```shell 34 | $ cd program1 35 | $ py -3 -m venv .venv 36 | ``` 37 | 38 | program1 配下に `.venv` というディレクトリが作成されます。仮想環境を有効にするために下記のコマンドを実行します。 39 | 40 | === "macOS" 41 | 42 | ```shell 43 | $ . .venv/bin/activate 44 | (.venv) $ 45 | ``` 46 | 47 | === "Windows" 48 | 49 | ```shell 50 | $ .venv\Scripts\activate.bat 51 | (.venv) $ 52 | ``` 53 | 54 | この状態で `requests` 2.21.0 をインストールしてみます。パッケージをバージョン指定でインストールしたければ次のように実行します。 55 | 56 | ```shell 57 | (.venv) $ pip install requests==2.21.0 58 | ``` 59 | 60 | !!! note 61 | 62 | 仮想環境が有効な場合であれば pip コマンドは `pip` が使用できます。 63 | `pip3` コマンドも使用できますが、どちらを使用しても効果は同じです。 64 | 65 | 一方で仮想環境が無効な状態であれば `pip` は Python 2 系の古い pip を呼び出すことになり 66 | `pip3` とは挙動が異なるので注意してください。 67 | 68 | この `requests` は .venv ディレクトリ配下にインストールされ、仮想環境が有効でない限りは参照することができないようになっています。 `pip freeze` を使ってインストールされているパッケージ一覧を確認してみます。 69 | 70 | ```shell 71 | (.venv) $ pip freeze 72 | certifi==2019.3.9 73 | chardet==3.0.4 74 | idna==2.8 75 | requests==2.21.0 76 | urllib3==1.24.1 77 | ``` 78 | 79 | 仮想環境を無効にする場合は `deactivate` というコマンドを実行します。このコマンドは仮想環境が有効なときだけ使用することができます。 80 | 81 | === "macOS" 82 | 83 | ```shell 84 | (.venv) $ deactivate 85 | $ 86 | ``` 87 | 88 | === "Windows" 89 | 90 | ```shell 91 | (.venv) $ venv\Scripts\deactivate.bat 92 | $ 93 | ``` 94 | 95 | program2 配下でも同様に仮想環境を作成してみます。`requests` のバージョンは 2.20.1 を使用します。 96 | 97 | === "macOS" 98 | 99 | ```shell 100 | $ cd program2 101 | $ python3 -m venv .venv 102 | $ . .venv/bin/activate 103 | (.venv) $ pip install requests==2.20.1 104 | (.venv) $ pip freeze 105 | certifi==2019.3.9 106 | chardet==3.0.4 107 | idna==2.7 108 | requests==2.20.1 109 | urllib3==1.24.1 110 | ``` 111 | 112 | === "Windows" 113 | 114 | ```shell 115 | $ cd program2 116 | $ py -3 -m venv .venv 117 | $ .venv\bin\activate.bat 118 | (.venv) $ pip install requests==2.20.1 119 | (.venv) $ pip freeze 120 | certifi==2019.3.9 121 | chardet==3.0.4 122 | idna==2.7 123 | requests==2.20.1 124 | urllib3==1.24.1 125 | ``` 126 | 127 | program1, program2 配下いずれかの仮想環境を有効にすることで異なるバージョンの `requests` が使用できるようになっています。ちなみに仮想環境が有効でない状態でインストールされたパッケージは全プログラムから参照可能なので競合を起こす可能性があり、使用すべきではありません。そのため、前章の `pip` の章でインストールしたパッケージは下記のように削除しておくことをおすすめします。 128 | 129 | ```shell 130 | $ pip3 freeze > requirements.txt 131 | $ pip3 uninstall -y -r requirements.txt 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/ch04-07-project-structures.md: -------------------------------------------------------------------------------- 1 | # プロジェクト構成 2 | 3 | Python のソースコードを管理する際にディレクトリの構成をちゃんと考えておくことはとても重要なことです。なぜなら Python は適切な構成になっていないとプログラムを正しく動かすことができなくなるからです。そこで Python 開発ではどのような構成で管理すれば問題が起こりにくいのかについて説明します。 4 | 5 | ## The Hitchhiker's Guide to Python 6 | 7 | Python の理想のプロジェクト構成は Kenneth Reitz 氏によって推奨されている構成に従うのがよいでしょう。どのような構成なのかは [The Hitchhiker’s Guide to Python] というサイトの [Structuring Your Project] の章で詳しく書かれていますのでそちらを参考にしてもらうことにして、ここでは特に注意すべきことについてまとめておきます。 8 | 9 | ディレクトリの基本構成は次のとおりです。 10 | 11 | ``` 12 | (project) 13 | ├── (project) ............ プログラムのソースコードディレクトリ 14 | │ ├── __init__.py 15 | │ └── *.py 16 | └── tests ................ 単体テストのソースコードディレクトリ 17 | ├── __init__.py 18 | └── *.py 19 | ``` 20 | 21 | `(project)` の部分は開発する Python プログラムの名前を付けます。`sample` というプログラムを開発するなら 22 | 23 | ``` 24 | sample 25 | ├── sample 26 | │ ├── __init__.py 27 | │ └── *.py 28 | └── tests 29 | ├── __init__.py 30 | └── *.py 31 | ``` 32 | 33 | のようになります。大事なのはプログラムのソースコードは必ず `(project)` 配下の 1 つのディレクトリ内に集約させるということです(単体テストのソースコードは除く)。なぜならディレクトリは Python のパッケージを構成するものなので、複数のディレクトリでソースコードを管理すると複数の Python パッケージを開発していることになります。しかし通常 1 つのプロジェクト内に複数のパッケージを含めて開発することはありません。 34 | 35 | ソースコードが 1 つで十分な場合はディレクトリを用意しなくても大丈夫です。 36 | 37 | ``` 38 | (project) 39 | ├── (project).py ........... プログラムのソースコードディレクトリ 40 | └── test_(project).py ...... 単体テストのソースコードディレクトリ 41 | ``` 42 | 43 | 下記は良くない構成の一例です。 44 | 45 | ``` 46 | sample 47 | ├── sample 48 | │ ├── __init__.py 49 | │ └── *.py 50 | ├── libs ................. よくないディレクトリ 51 | │ ├── __init__.py 52 | │ └── *.py 53 | └── tests 54 | ├── __init__.py 55 | └── *.py 56 | ``` 57 | 58 | 下記は `sample/__init__.py` が用意されていないため問題のある構成です。ディレクトリ内に Python ファイルを入れる場合は必ず `__init__.py` を用意してください。 59 | 60 | ``` 61 | sample 62 | ├── sample 63 | │ └── *.py 64 | └── tests 65 | └── *.py 66 | ``` 67 | 68 | 下記のような構成もやってしまいがちですが適切ではありません。 69 | 70 | ``` 71 | sample 72 | ├── sample 73 | │ ├── __init__.py 74 | │ └── *.py 75 | ├── main.py 76 | └── tests 77 | ├── __init__.py 78 | └── *.py 79 | ``` 80 | 81 | **main.py** 82 | 83 | ```python 84 | #!/usr/bin/env python 85 | 86 | 87 | import sample 88 | 89 | 90 | def main(): 91 | # sample パッケージを使った処理 92 | ... 93 | 94 | 95 | if __name__ == '__main__': 96 | main() 97 | ``` 98 | 99 | この構成の問題点はライブラリとアプリケーションの区別ができていない構成になっているという点です。 100 | 101 | | 構成 | 説明 | 102 | |------------------|------------------------------------------------| 103 | | ライブラリ | 他のプログラムから `import` して使うプログラム | 104 | | アプリケーション | 直接実行するプログラム | 105 | 106 | この例だと `sample` はライブラリで `main.py` はアプリケーションという位置づけになります。もしライブラリとアプリケーションの両方の側面を持つプログラムを書きたいという場合は `main.py` を `sample/__main__.py` という名前で保存してください。 107 | 108 | ``` 109 | sample 110 | ├── sample 111 | │ ├── __init__.py 112 | │ ├── __main__.py 113 | │ └── *.py 114 | └── tests 115 | ├── __init__.py 116 | └── *.py 117 | ``` 118 | 119 | そして `main()` を実行したい場合はターミナル上で次のようにします。 120 | 121 | === "macOS" 122 | 123 | ```shell 124 | $ python3 -m sample 125 | ``` 126 | 127 | === "Windows" 128 | 129 | ```shell 130 | $ py -3 -m sample 131 | ``` 132 | 133 | こうすると `sample/__main__.py` が実行されるようになります。決して 134 | 135 | === "macOS" 136 | 137 | ```shell 138 | $ python3 sample/__main__.py 139 | ``` 140 | 141 | === "Windows" 142 | 143 | ```shell 144 | $ py -3 sample/__main__.py 145 | ``` 146 | 147 | のように実行してはいけません。たとえ動いたとしてもいつもうまくいくと期待しないほうがいいでしょう。 148 | 149 | [The Hitchhiker’s Guide to Python]: https://docs.python-guide.org/ 150 | [Structuring Your Project]: https://docs.python-guide.org/writing/structure/ 151 | -------------------------------------------------------------------------------- /docs/ch05-01-files.md: -------------------------------------------------------------------------------- 1 | # ファイル操作 2 | 3 | ファイルの読み書きをする方法について説明します。 4 | 5 | ## 読み込み 6 | 7 | `file.txt` というファイルを読み込み、1 行ずつプリントするプログラムは次のように書きます。 8 | 9 | **main.py** 10 | 11 | ```python 12 | #!/usr/bin/env python 13 | 14 | 15 | def main(): 16 | f = open('file.txt') 17 | 18 | for line in f: 19 | print(line) 20 | 21 | f.close() 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | ``` 27 | 28 | `for` を使わずに 1 行読み込む場合は `readline()` メソッドを使います。 29 | 30 | ```python 31 | print(f.readline()) 32 | ``` 33 | 34 | 注意が必要なのは読み込んだ各行の文字列は末尾の改行も含んでいるということです。例えば `file.txt` が次のような内容だった場合 35 | 36 | **file.txt** 37 | 38 | ``` 39 | aaa 40 | bbb 41 | ccc 42 | ``` 43 | 44 | `for` や `readline()` で読み込んだ各行の文字列は次のようになります。 45 | 46 | ```python 47 | 'aaa\n' 48 | 'bbb\n' 49 | 'ccc\n' 50 | ``` 51 | 52 | `print()` は文字列をプリントした後に自動で改行を 1 つ書き出すため、これらの文字列をプリントすると改行が 2 つ入った状態で出力されてしまいます。 53 | 54 | ```shell 55 | $ python main.py 56 | aaa 57 | 58 | bbb 59 | 60 | ccc 61 | ``` 62 | 63 | これを解決するには読み込んだ文字列に対して `strip()` というメソッドを呼び出します。 `strip()` は文字列の両端にある空白や改行を削除するメソッドです。 64 | 65 | ```python 66 | #!/usr/bin/env python 67 | 68 | 69 | def main(): 70 | f = open('file.txt') 71 | 72 | for line in f: 73 | print(line.strip()) 74 | 75 | f.close() 76 | 77 | 78 | if __name__ == '__main__': 79 | main() 80 | ``` 81 | 82 | その他次のようなメソッドでも文字列を読み込むことができます。 83 | 84 | | メソッド | 説明 | 例 | 85 | |---------------|-----------------------------------|-------------------------------| 86 | | `readlines()` | 各行をリストで読み込む | `['aaa\n', 'bbb\n', 'ccc\n']` | 87 | | `read()` | 全行を 1 つの文字列として読み込む | `'aaa\nbbb\nccc\n'` | 88 | 89 | ## 書き込み 90 | 91 | ファイルを書き込む処理は次のように書きます。 92 | 93 | ```python 94 | #!/usr/bin/env python 95 | 96 | 97 | def main(): 98 | f = open('file.txt', 'w') 99 | f.write('Hello, World!\n') 100 | f.close() 101 | 102 | 103 | if __name__ == '__main__': 104 | main() 105 | ``` 106 | 107 | `open()` の第 2 引数に `'w'` を付けることで書き込みモードでファイルをオープンします。そして `write()` メソッドに書き出したい文字列を渡すことでファイルに書き出すことができます。 `write()` には文字列しか渡すことができませんので、数値などを書き出したいときは f-string を使って文字列に変換する必要があります。また `write()` は `print()` とは違って改行を自動で付与しないので、改行したいときは明示的に `'\n'` を渡す必要があります。 108 | -------------------------------------------------------------------------------- /docs/ch05-02-contexts.md: -------------------------------------------------------------------------------- 1 | # コンテキスト 2 | 3 | `open()` を使ってファイルの読み書きをした後は必ず `close()` を使ってファイルを閉じる必要があります。しかしファイルの閉じ忘れがよくあるミスの 1 つです。このファイルの閉じ忘れをなくすために Python にはコンテキストマネージャという機能が用意されています。コンテキストマネージャを使えばファイルの読み書きが不要になったときに暗黙的にファイルを閉じてくれるようになります。 4 | 5 | ## `with` 文 6 | 7 | コンテキストマネージャを使うには `with` 文という構文を使用します。 8 | 9 | ```python 10 | f = open('file.txt') 11 | ``` 12 | 13 | のように記述していた部分を 14 | 15 | ```python 16 | with open('file.txt') as f: 17 | ``` 18 | 19 | という構文で書き直します。そうするとファイルインスタンス `f` は `with` のブロック内だけで使用できるようになり、ブロックを抜けると暗黙的に `f.close()` を読んでファイルをクローズしてくれるようになります。下記は `with` を使ってファイルを読み込む例です。 20 | 21 | **main.py** 22 | 23 | ```python 24 | #!/usr/bin/env python 25 | 26 | 27 | def main(): 28 | with open('file.txt') as f: 29 | for line in f: 30 | print(line) 31 | 32 | 33 | if __name__ == '__main__': 34 | main() 35 | ``` 36 | 37 | ファイルの閉じ忘れを防ぐためにもファイル操作を行う際はいつもコンテキストマネージャを用いたほうが良いでしょう。 38 | -------------------------------------------------------------------------------- /docs/ch05-03-csv.md: -------------------------------------------------------------------------------- 1 | # CSV 2 | 3 | Python で CSV の読み書きを行いたい場合は `csv` モジュールを使います。 4 | 5 | ## 読み込み 6 | 7 | ### reader 8 | 9 | CSV ファイルを読み込む場合は `open()` でファイルを開いた後、ファイルオブジェクトを `csv.reader()` に渡します。 10 | 11 | ```python 12 | #!/usr/bin/env python 13 | import csv 14 | 15 | 16 | def main(): 17 | with open('example.csv', newline='') as f: 18 | reader = csv.reader(f) 19 | 20 | for row in reader: 21 | print(row) # row は CSV の各行になる 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | ``` 27 | 28 | !!! warning "注意" 29 | `newline=''` というのはファイルの改行コードをそのまま読み込むというオプションです。これを付けないと CRLF, LF, CR などの改行コードはすべて LF に変換されてファイルが読み込まれますが CSV を読むときはこの変換をすべきでないとされています。 30 | 31 | `row` にはカンマ区切りの要素がリストで保存されます。 32 | 33 | **example.csv** 34 | 35 | ``` 36 | 0,A 37 | 1,B 38 | 2,C 39 | ``` 40 | 41 | 上記のファイルに対しては `row` は下記のようになります。 42 | 43 | ```python 44 | [0, 'A'] 45 | [1, 'B'] 46 | [2, 'C'] 47 | ``` 48 | 49 | ### DictReader 50 | 51 | CSV が下記のようなヘッダを持つ場合は `reader()` の代わりに `DictReader` を使うと各行を辞書で読むことができるようになります。 52 | 53 | **example.csv** 54 | 55 | ``` 56 | id,name 57 | 0,A 58 | 1,B 59 | 2,C 60 | ``` 61 | 62 | ```python 63 | #!/usr/bin/env python 64 | import csv 65 | 66 | 67 | def main(): 68 | with open('example.csv', newline='') as f: 69 | reader = csv.DictReader(f) 70 | 71 | for row in reader: 72 | print(row) 73 | ``` 74 | 75 | 実行結果は下記のようになります。 76 | 77 | ```python 78 | {'id': 0, 'name': 'A'} 79 | {'id': 1, 'name': 'B'} 80 | {'id': 2, 'name': 'C'} 81 | ``` 82 | 83 | ## 書き込み 84 | 85 | ### writer 86 | 87 | CSV ファイルを作成するには `open()` でファイルを開いた後 `csv.writer()` にファイルオブジェクトを渡します。 88 | 89 | ```python 90 | #!/usr/bin/env python 91 | import csv 92 | 93 | 94 | def main(): 95 | with open('example.csv', 'w', newline='') as f: 96 | writer = csv.writer(f) 97 | 98 | writer.writerow([0, 'A']) 99 | writer.writerow([1, 'B']) 100 | writer.writerow([2, 'C']) 101 | ``` 102 | 103 | !!! warning "注意" 104 | 書き込みの場合も `newline=''` は必ず付けるようにしてください。 105 | 106 | ### DictWriter 107 | 108 | ヘッダ付きの CSV を作成したい場合は `writer()` の代わりに `DictWriter` を使うと書き込む要素を辞書で指定できるようになります。 109 | 110 | ```python 111 | #!/usr/bin/env python 112 | import csv 113 | 114 | 115 | def main(): 116 | with open('example.csv', 'w', newline='') as f: 117 | writer = csv.DictWriter(f, fieldnames=['id', 'name']) 118 | 119 | writer.writerow({'id': 0, 'name': 'A'}) 120 | writer.writerow({'id': 1, 'name': 'B'}) 121 | writer.writerow({'id': 2, 'name': 'C'}) 122 | 123 | 124 | if __name__ == '__main__': 125 | main() 126 | ``` 127 | -------------------------------------------------------------------------------- /docs/ch05-04-json.md: -------------------------------------------------------------------------------- 1 | # JSON 2 | 3 | Python で JSON の読み書きを行いたい場合は `json` モジュールを使います。 4 | 5 | ## 読み込み 6 | 7 | ### ファイルから読み込み 8 | 9 | JSON ファイルを読み込む場合は `open()` でファイルを開いた後、ファイルオブジェクトを `json.load()` に渡します。 10 | 11 | ```python 12 | #!/usr/bin/env python 13 | 14 | import json 15 | 16 | 17 | def main(): 18 | with open('example.json') as f: 19 | json_data = json.load(f) 20 | 21 | 22 | if __name__ == '__main__': 23 | main() 24 | ``` 25 | 26 | `json_data` は読み込んだ JSON の構造に応じて Python の辞書やリストになります。 27 | 28 | **example.json** 29 | 30 | ```json 31 | { 32 | "message": "Hello, World!", 33 | "items": [0, 1, 2], 34 | "ok": true 35 | } 36 | ``` 37 | 38 | 上記のファイルに対しては `json_data` は下記のようになります。 39 | 40 | ```python 41 | { 42 | "message": "Hello, World!", 43 | "items": [0, 1, 2], 44 | "ok": True 45 | } 46 | ``` 47 | 48 | ### 文字列から読み込み 49 | 50 | `json.loads()` を使うと文字列で作成された JSON をパースすることができます。 51 | 52 | ```python 53 | json_string = """ 54 | { 55 | "message": "Hello, World", 56 | "items": [ 57 | 0, 58 | 1, 59 | 2 60 | ], 61 | "ok": true 62 | } 63 | """ 64 | 65 | json_data = json.loads(json_string) 66 | ``` 67 | 68 | !!! note 69 | `json.loads()` の `s` は `string` の `s` です。 70 | 71 | ## 書き込み 72 | 73 | ### ファイルに書き込み 74 | 75 | JSON ファイルを作成するには `open()` でファイルを開いた後、`json.dump()` にファイルオブジェクトを渡します。 76 | 77 | ```python 78 | #!/usr/bin/env python 79 | import json 80 | 81 | 82 | def main(): 83 | json_data = { 84 | "message": "Hello, World!", 85 | "items": [0, 1, 2], 86 | "ok": True, 87 | } 88 | 89 | with open('example.json', 'w') as f: 90 | json.dump(json_data, f) 91 | 92 | 93 | if __name__ == '__main__': 94 | main() 95 | ``` 96 | 97 | **example.json** 98 | 99 | ```json 100 | {"items": [0, 1, 2], "message": "Hello, World!", "ok": true} 101 | ``` 102 | 103 | JSON データは改行されずに出力されます。もし整形して出力させたい場合は `json.dump()` に `indent` オプションを渡します。`indent` にはインデント数を渡します。 104 | 105 | ```python 106 | json.dump(json_data, f, indent=4) 107 | ``` 108 | 109 | ```json 110 | { 111 | "items": [ 112 | 0, 113 | 1, 114 | 2 115 | ], 116 | "message": "Hello, World!", 117 | "ok": true 118 | } 119 | ``` 120 | 121 | 出力したいデータ内に日本語が含まれていると JSON に変換するときに `\uXXXX` というエスケープされた形式で変換されてしまいます。 122 | 123 | ```python 124 | json_data = { 125 | "message": "こんにちは", 126 | "items": [0, 1, 2], 127 | "ok": True, 128 | } 129 | ``` 130 | 131 | **結果** 132 | 133 | ```json 134 | { 135 | "message": "\u3053\u3093\u306b\u3061\u306f", 136 | "items": [ 137 | 0, 138 | 1, 139 | 2 140 | ], 141 | "ok": true 142 | } 143 | ``` 144 | 145 | 日本語をエスケープさせたくない場合は `json.dump()` に `ensure_ascii=False` オプションを渡します。 146 | 147 | ```python 148 | json.dump(json_data, f, indent=4, ensure_ascii=False) 149 | ``` 150 | 151 | **結果** 152 | 153 | ```json 154 | { 155 | "message": "こんにちは", 156 | "items": [ 157 | 0, 158 | 1, 159 | 2 160 | ], 161 | "ok": true 162 | } 163 | ``` 164 | 165 | ### 文字列に出力 166 | 167 | JSON を文字列に出力したい場合は `json.dumps()` を使います。 168 | 169 | ```python 170 | json_data = { 171 | "message": "Hello, World!", 172 | "items": [0, 1, 2], 173 | "ok": True, 174 | } 175 | 176 | json_string = json.dumps(json_data, indent=4, ensure_ascii=False) 177 | print(json_string) 178 | ``` 179 | 180 | **結果** 181 | 182 | ```json 183 | { 184 | "message": "Hello, World!", 185 | "items": [0, 1, 2], 186 | "ok": true 187 | } 188 | ``` 189 | -------------------------------------------------------------------------------- /docs/ch06-01-exceptions.md: -------------------------------------------------------------------------------- 1 | # 例外 2 | 3 | Python のプログラムで不正な処理を行ったときは例外と呼ばれる割り込み処理が通常の処理に変わって実行されます。例外が発生するタイミングにはいろいろなものがありますが、代表的なものには次のようなものがあります。 4 | 5 | ## 不正な構文を実行 6 | 7 | ```python 8 | #!/usr/bin/env python 9 | 10 | 11 | def main(): 12 | print('Hello, World!' 13 | 14 | 15 | if __name__ == '__main__': 16 | main() 17 | ``` 18 | 19 | ```shell 20 | $ python main.py 21 | File "main.py", line 8 22 | if __name__ == '__main__': 23 | ^ 24 | SyntaxError: invalid syntax 25 | ``` 26 | 27 | `SyntexError` という種類の例外が発生しています。 28 | 29 | ## 配列のインデックスが不正 30 | 31 | ```python 32 | #!/usr/bin/env python 33 | 34 | 35 | def main(): 36 | x = [0, 1, 2] 37 | print(x[3]) 38 | 39 | 40 | if __name__ == '__main__': 41 | main() 42 | ``` 43 | 44 | ```shell 45 | $ python main.py 46 | Traceback (most recent call last): 47 | File "main.py", line 10, in 48 | main() 49 | File "main.py", line 6, in main 50 | print(x[3]) 51 | IndexError: list index out of range 52 | ``` 53 | 54 | `IndexError` という種類の例外が発生しています。 55 | 56 | ## 存在しないファイルをオープン 57 | 58 | ```python 59 | #!/usr/bin/env python 60 | 61 | 62 | def main(): 63 | with open('file.txt') as f: 64 | print(f.read()) 65 | 66 | 67 | if __name__ == '__main__': 68 | main() 69 | ``` 70 | 71 | ```shell 72 | $ python main.py 73 | Traceback (most recent call last): 74 | File "main.py", line 10, in 75 | main() 76 | File "main.py", line 5, in main 77 | with open('file.txt') as f: 78 | FileNotFoundError: [Errno 2] No such file or directory: 'file.txt' 79 | ``` 80 | 81 | `FileNotFoundError` という種類の例外が発生しています。このように不正な処理が検知された時点で例外が割り込み、プログラムは停止します。 82 | 83 | - `SyntexError` 84 | - `IndexError` 85 | - `FileNotFoundError` 86 | 87 | はすべて例外クラスを呼ばれるものです。 88 | 89 | ## 例外を捕捉する 90 | 91 | 例外が発生したときにプログラムを停止させる代わりに、例外が発生したときに実行する処理を書くこともできます。`try-except` 文を使ってそれが実現できます。 92 | 93 | ```python 94 | #!/usr/bin/env python 95 | 96 | 97 | def main(): 98 | try: 99 | # 例外を監視する処理 100 | with open('file.txt') as f: 101 | print(f.read()) 102 | except FileNotFoundError: 103 | # 例外発生時の処理 104 | print('ファイルが存在しません。') 105 | 106 | 107 | if __name__ == '__main__': 108 | main() 109 | ``` 110 | 111 | ```shell 112 | $ python main.py 113 | ファイルが存在しません。 114 | ``` 115 | 116 | `try` ブロックで例外が発生しうる処理を記述し、`except` ブロックで例外が発生したときに実行する処理を記述します。 `except` ブロックは複数書くこともでき、そうすることで複数の例外を捕捉することができます。 117 | 118 | ```python 119 | #!/usr/bin/env python 120 | 121 | 122 | def main(): 123 | try: 124 | with open('file.txt') as f: # file.txt が存在しなければ FileNotFoundError 125 | contents = f.read() 126 | print(contents[1000]) # ファイル内の文章が 1000 字以上なければ IndexError 127 | except FileNotFoundError: 128 | print('ファイルが存在しません。') 129 | except IndexError: 130 | print('文章は 1000 字以上必要です。') 131 | 132 | 133 | if __name__ == '__main__': 134 | main() 135 | ``` 136 | 137 | 例外を捕捉するメリットには次のようなものがあります。 138 | 139 | - `try-except` を `if-else` のような条件分岐の代わりとして使える 140 | - どのようなエラーが発生しうるかがコード上で明らかになる 141 | - エラー発生時の原因や解決策を `print()` などを使って伝えられる 142 | 143 | ## 例外を送出する 144 | 145 | 自分で例外を送出することもできます。例外を送出するには `raise` を使います。 146 | 147 | ```python 148 | def factorial(n): 149 | if not n >= 0: 150 | raise ValueError('n >= 0 である必要があります') 151 | 152 | ... 153 | ``` 154 | 155 | 自分で例外を送出する場合は独自の例外クラスを用意しておいたほうがエラーの起こった箇所が区別しやすくなります。例外クラスを作成するには `Exception` クラスを継承して作成する必要があります。 156 | 157 | ```python 158 | class MyException(Exception): 159 | pass 160 | ``` 161 | 162 | 例外クラスの実装は空で問題ありません。 `Exception` を継承している組み込みの例外クラスを継承しても良いです。 163 | 164 | !!! tips "組み込み例外の一覧" 165 | 下記ページに例外クラスの一覧が載っていますので参考にして下さい。 166 | [https://docs.python.org/ja/3/library/exceptions.html](https://docs.python.org/ja/3/library/exceptions.html) 167 | -------------------------------------------------------------------------------- /docs/ch07-01-generators.md: -------------------------------------------------------------------------------- 1 | # ジェネレータ 2 | 3 | ジェネレータとは処理を一時停止できる機能を持った関数のことです。処理を一時停止できるということがどういうことなのか、具体的なソースコードで説明していきます。 4 | 5 | ## ジェネレータの例 6 | 7 | まず下記のような処理の挙動について確認しておきます。 8 | 9 | **main.py** 10 | 11 | ```python 12 | #!/usr/bin/env python 13 | 14 | 15 | def f(): 16 | print('開始') 17 | 18 | return 19 | 20 | print('終了') 21 | 22 | 23 | def main(): 24 | f() 25 | f() 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | ``` 31 | 32 | ```shell 33 | $ python main.py 34 | 開始 35 | 開始 36 | ``` 37 | 38 | 関数 `f()` は通常の関数ですが `return` の後ろに `print()` の呼び出しが入っています。`return` があると関数の処理はそこで終わってしまうので `f()` を実行しても `終了` という文字列はプリントされません。 39 | 40 | 次に `return` の箇所を `yield` というキーワードに変更して同様のプログラムを実行するとどうなるでしょうか。 41 | 42 | ```python hl_lines="7" 43 | #!/usr/bin/env python 44 | 45 | 46 | def f(): 47 | print('開始') 48 | 49 | yield 50 | 51 | print('終了') 52 | 53 | 54 | def main(): 55 | f() 56 | f() 57 | 58 | 59 | if __name__ == '__main__': 60 | main() 61 | ``` 62 | 63 | ```shell 64 | $ python main.py 65 | 開始 66 | 終了 67 | ``` 68 | 69 | 2 回目の `f()` の呼び出しで `終了` という文字列がプリントされるはずです。`yield` は `return` とよく似た挙動をしますが `return` が関数の処理を終了するのに対し `yield` はそこで一時停止をして関数を抜けます。再度 `f()` が呼ばれると `yield` の箇所から関数の処理が再開されます。このように `yield` を使った関数のことをジェネレータといいます。 70 | 71 | ## ジェネレータを使ったループ 72 | 73 | ジェネレータは `return` と同様 `yield` で任意の値を返すことができます。 74 | 75 | **main.py** 76 | 77 | ```python 78 | #!/usr/bin/env python 79 | 80 | 81 | def f(): 82 | yield 0 83 | yield 1 84 | yield 2 85 | 86 | 87 | def main(): 88 | print(f()) 89 | print(f()) 90 | print(f()) 91 | 92 | 93 | if __name__ == '__main__': 94 | main() 95 | ``` 96 | 97 | ```shell 98 | $ python main.py 99 | 0 100 | 1 101 | 2 102 | ``` 103 | 104 | さらに `for` にジェネレータを渡して値を取り出すこともできます。 105 | 106 | ```python 107 | def main(): 108 | for x in f(): 109 | print(x) # 0, 1, 2 110 | ``` 111 | 112 | ジェネレータを `for` に渡すとジェネレータに書かれた `yield` の個数分だけループが回ります。つまりジェネレータはリストのようなデータ構造とよく似た振る舞いをします。 113 | 114 | ## ループ処理のカスタマイズ 115 | 116 | 複雑なループ処理はジェネレータを使うことでシンプルに書くことができるようになります。例えば下記のような 2 つのリストの要素の総当たりの組み合わせを得るような処理を考えます。 117 | 118 | ```python 119 | def main(): 120 | xs = [0, 1, 2] 121 | ys = ['a', 'b', 'c'] 122 | 123 | for x in xs: 124 | for y in ys: 125 | print(x, y) 126 | ``` 127 | 128 | このような処理は 2 つのリストを受け取って総当たりの組み合わせを返すジェネレータで書き換えることができます。 129 | 130 | ```python 131 | def product(xs, ys): 132 | for x in xs: 133 | for y in ys: 134 | yield x, y 135 | 136 | 137 | def main(): 138 | xs = [0, 1, 2] 139 | ys = ['a', 'b', 'c'] 140 | 141 | for x, y in product(xs, ys): 142 | print(x, y) 143 | ``` 144 | 145 | `product()` を使うと二重ループが単一ループで書き直せました。実は `product()` は標準ライブラリの [itertools] ですでに用意されているため、わざわざ自分で作らなくても使うことができます。 146 | 147 | [itertools]: https://docs.python.org/ja/3/library/itertools.html#itertools.product 148 | -------------------------------------------------------------------------------- /docs/ch08-01-doctest.md: -------------------------------------------------------------------------------- 1 | # doctest 2 | 3 | Python にはソースコードのドキュメンテーションをサポートするための docstring という文字列を使ってドキュメントを作成することができます。さらに docstring には関数の使用例を記述するための構文が用意されています。またその使用例を示すコードは関数が実際に記述したとおりに振る舞うかどうかをテストする機能も備わっており doctest と呼ばれます。 4 | 5 | **main.py** 6 | 7 | ```python 8 | #!/usr/bin/env python 9 | 10 | 11 | """ 12 | サンプルモジュールです。 13 | 14 | このモジュールは factorial() という関数を提供しており、次のように使用します。 15 | 16 | >>> factorial(5) 17 | 120 18 | """ 19 | 20 | 21 | import math 22 | 23 | 24 | def factorial(n): 25 | """n >=0 であるような整数に対する階乗を計算します。 26 | 27 | >>> [factorial(n) for n in range(6)] 28 | [1, 1, 2, 6, 24, 120] 29 | 30 | >>> factorial(30) 31 | 265252859812191058636308480000000 32 | >>> factorial(-1) 33 | Traceback (most recent call last): 34 | ... 35 | ValueError: n >= 0 である必要があります 36 | 37 | 浮動小数点型も渡せますが、その値は整数値である必要があります。 38 | >>> factorial(30.1) 39 | Traceback (most recent call last): 40 | ... 41 | ValueError: n は整数でなければいけません 42 | >>> factorial(30.0) 43 | 265252859812191058636308480000000 44 | 45 | 極端に大きな整数値を渡しても階乗の計算はできません。 46 | >>> factorial(1e100) 47 | Traceback (most recent call last): 48 | ... 49 | OverflowError: n の値が大きすぎます 50 | """ 51 | 52 | if not n >= 0: 53 | raise ValueError('n >= 0 である必要があります') 54 | if math.floor(n) != n: 55 | raise ValueError('n は整数でなければいけません') 56 | if n + 1 == n: 57 | raise OverflowError('n の値が大きすぎます') 58 | 59 | result = 1 60 | factor = 2 61 | 62 | while factor <= n: 63 | result *= factor 64 | factor += 1 65 | 66 | return result 67 | 68 | 69 | if __name__ == '__main__': 70 | import doctest 71 | doctest.testmod() 72 | ``` 73 | 74 | `"""..."""` のようにトリプルクオテーションで囲まれた文字列が docstring です。通常はソースコード・関数・クラスの先頭部分で記述します。`>>>` から始まる行 75 | 76 | ```python 77 | >>> factorial(5) 78 | 120 79 | ``` 80 | 81 | は Python のコードを記述するための行で、その処理を実行したらどのように振る舞うのかを `>>>` の下部に記述します。実行時に例外を送出する場合はその旨を記述することもできます。 82 | 83 | ```python 84 | >>> factorial(-1) 85 | Traceback (most recent call last): 86 | ... 87 | ValueError: n >= 0 である必要があります 88 | ``` 89 | 90 | docstring 内のコードが記述したとおりに動くかどうかを確認するためには doctest モジュールを使用します。 91 | 92 | ```python 93 | import doctest 94 | doctest.testmod() 95 | ``` 96 | 97 | このように記述しておき 98 | 99 | ```shell 100 | $ python main.py 101 | ``` 102 | 103 | と実行すると doctest が走ります。`factorial()` の実装が期待通りになっている場合は何も表示されませんが、もしバグが混入していた場合はエラーメッセージが出力されます。下記は `result = 1` の部分を `result = 10` と書いてしまっていた場合の doctest の実行結果になります。 104 | 105 | ```shell 106 | $ python main.py 107 | ********************************************************************** 108 | File "main.py", line 9, in __main__ 109 | Failed example: 110 | factorial(5) 111 | Expected: 112 | 120 113 | Got: 114 | 1200 115 | ********************************************************************** 116 | File "main.py", line 20, in __main__.factorial 117 | Failed example: 118 | [factorial(n) for n in range(6)] 119 | Expected: 120 | [1, 1, 2, 6, 24, 120] 121 | Got: 122 | [10, 10, 20, 60, 240, 1200] 123 | ********************************************************************** 124 | File "main.py", line 23, in __main__.factorial 125 | Failed example: 126 | factorial(30) 127 | Expected: 128 | 265252859812191058636308480000000 129 | Got: 130 | 2652528598121910586363084800000000 131 | ********************************************************************** 132 | File "main.py", line 35, in __main__.factorial 133 | Failed example: 134 | factorial(30.0) 135 | Expected: 136 | 265252859812191058636308480000000 137 | Got: 138 | 2652528598121910586363084800000000 139 | ********************************************************************** 140 | 2 items had failures: 141 | 1 of 1 in __main__ 142 | 3 of 6 in __main__.factorial 143 | ***Test Failed*** 4 failures. 144 | ``` 145 | 146 | ## なぜ doctest を使うのか 147 | 148 | doctest を使うことで次のような機能を提供することができます。 149 | 150 | - プログラムの使用例をドキュメントに記述できる 151 | - その使用例が単体テストとして使用できる 152 | - プログラムの実装と使用例が同期しているかどうか用意に確認できる 153 | 154 | プログラムの使い方を記述しておくことはとても重要なことですが、ドキュメントというのは時間が経つと形骸化しやすいものです。しかし doctest を使ってドキュメントを管理しておくとプログラムの内容と同期が取れているかどうかを簡単に確認できるため、形骸化することがなくなります。 155 | -------------------------------------------------------------------------------- /docs/ch08-02-pytest.md: -------------------------------------------------------------------------------- 1 | # pytest 2 | 3 | Python には単体テストを書くためのフレームワークがいくつかあります。 4 | 5 | | フレームワーク | 説明 | 6 | |----------------|--------------------| 7 | | unittest | 標準ライブラリ | 8 | | nose | かつては主流だった | 9 | | pytest | 現在主流のもの | 10 | 11 | 上記の通り Python は標準ライブラリを使って単体テストを書くことができますが、サードパーティ製の pytest の使い勝手が良いため、pytest を使って書かれることが多いです。そこでここでは pytest の簡単な使い方について説明します。 12 | 13 | ## 準備 14 | 15 | [プロジェクト構成](./ch04-07-project-structures.md) を参考に、単体テストのソースコードは `tests` 配下に作成するようにします。ソースコードが 1 つで十分な場合はディレクトリを作らなくても構いません。 16 | 17 | 下記のような構成でファイルを作成し、素数判定のコードをテストしてみましょう。 18 | 19 | ```shell 20 | prime 21 | ├── prime.py 22 | └── test_prime.py 23 | ``` 24 | 25 | !!! warning "注意" 26 | テストを複数ファイルに分割して書く場合は `tests` ディレクトリを作成し、その中に `__init__.py` を含めるようにしてください。`__init__.py` がないとテストが正しく実行できなくなります。詳しくは [ディレクトリ構成](./ch04-07-project-structures.md) を参考にしてください。 27 | 28 | ## インストール 29 | 30 | pipenv を使ってインストールします。 31 | 32 | ```shell 33 | $ mkdir fibonacci 34 | $ cd fibonacci 35 | $ pipenv install -d pytest 36 | ``` 37 | 38 | `-d` というのは開発時にだけ必要となるパッケージをインストールするときに指定するフラグです。単体テストは通常開発時にしか必要ないため大抵のケースで pytest は `-d` を指定してインストールするのが良いでしょう。 39 | 40 | ## テストの書き方 41 | 42 | まず素数判定を行う関数を書きます。 43 | 44 | **prime.py** 45 | 46 | ```python 47 | def is_prime(n: int) -> bool: 48 | if n <= 1: 49 | return False 50 | 51 | if n == 2: 52 | return True 53 | 54 | if n % 2 == 0: 55 | return False 56 | 57 | i = 3 58 | 59 | while i * i <= n: 60 | if n % i == 0: 61 | return False 62 | 63 | i += 2 64 | 65 | return True 66 | ``` 67 | 68 | 今回はこのプログラムを直接実行するわけではないため、シバンや `main()` は不要です。次にこの関数に対するテストを下記のように記述します。 69 | 70 | **test_prime.py** 71 | 72 | ```python 73 | from prime import is_prime 74 | 75 | 76 | def test_is_prime(): 77 | assert not is_prime(1) 78 | assert is_prime(2) 79 | assert is_prime(3) 80 | assert not is_prime(4) 81 | assert is_prime(5) 82 | assert not is_prime(6) 83 | assert is_prime(7) 84 | assert not is_prime(8) 85 | assert not is_prime(9) 86 | assert not is_prime(10) 87 | ``` 88 | 89 | pytest は `test_` で始まるファイル・関数を単体テストのコードとみなします。テストしたい関数を `import` 文で取り込み、`assert` という文の後ろにテストしたい式を記述します。 90 | 91 | ## テスト実行 92 | 93 | テストを実行するには `pytest` というコマンドを使います。 94 | 95 | ```shell 96 | $ pipenv shell 97 | (prime) $ pytest test_prime.py 98 | ============================================== test session starts ============================================== 99 | platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 100 | rootdir: /Users/kenichiro-ida/Documents/github.com/rinatz/prime 101 | collected 1 item 102 | 103 | test_prime.py . [100%] 104 | 105 | =========================================== 1 passed in 0.02 seconds ============================================ 106 | ``` 107 | 108 | 素数判定が正しく実装されていなかった場合の挙動を確認するため、`is_prime()` から次の行を無効にして再度テストを実行してみます。 109 | 110 | **prime.py** 111 | 112 | ```python hl_lines="8 9" 113 | def is_prime(n: int) -> bool: 114 | if n <= 1: 115 | return False 116 | 117 | if n == 2: 118 | return True 119 | 120 | # if n % 2 == 0: 121 | # return False 122 | 123 | i = 3 124 | 125 | while i * i <= n: 126 | if n % i == 0: 127 | return False 128 | 129 | i += 2 130 | 131 | return True 132 | ``` 133 | 134 | ```shell 135 | (prime) $ pytest test_prime.py 136 | ============================================== test session starts ============================================== 137 | platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 138 | rootdir: /Users/kenichiro-ida/Documents/github.com/rinatz/prime 139 | collected 1 item 140 | 141 | test_prime.py F [100%] 142 | 143 | =================================================== FAILURES ==================================================== 144 | _________________________________________________ test_is_prime _________________________________________________ 145 | 146 | def test_is_prime(): 147 | assert not is_prime(0) 148 | assert not is_prime(1) 149 | assert is_prime(2) 150 | assert is_prime(3) 151 | > assert not is_prime(4) 152 | E assert not True 153 | E + where True = is_prime(4) 154 | 155 | test_prime.py:9: AssertionError 156 | =========================================== 1 failed in 0.09 seconds ============================================ 157 | ``` 158 | 159 | `is_prime(4)` が `True` になっているというエラーメッセージが出力されています。 160 | 161 | ## パラメータ化したテスト 162 | 163 | 上記の例では `is_prime(4)` のテストに失敗すると、その時点でテストが終わってしまうため、`is_prime(5)` 以降のテストがどうなるかは分かりませんでした。このようなケースでは **パラメータ化したテスト** を作ることで `1~10` までのすべての値をテストできるようになります。 164 | 165 | パラメータ化したテストはテスト内で使用するパラメータを関数の引数として渡せるように書き直したテストのことです。パラメータ化したテストでテストを記述した場合は、すべてのパラメータのテストを実行するまでテストが続行されます。チュートリアルの `test_is_prime()` をパラメータ化したテストで書き直すと次のようになります。 166 | 167 | ```python 168 | import pytest 169 | 170 | from prime import is_prime 171 | 172 | 173 | @pytest.mark.parametrize(('number', 'expected'), [ 174 | (1, False), 175 | (2, True), 176 | (3, True), 177 | (4, False), 178 | (5, True), 179 | (6, False), 180 | (7, True), 181 | (8, False), 182 | (9, False), 183 | (10, False), 184 | ]) 185 | def test_is_prime(number, expected): 186 | assert is_prime(number) == expected 187 | ``` 188 | 189 | `@pytest.mark.parametrize()` はデコレータと呼ばれるもので、これにテストで使用するパラメータを記述します。デコレータの最初の引数 `('number', 'expected')` はテスト関数に渡すパラメータの引数名になります。第 2 引数は実際に渡すパラメータの値をタプルのリストとして記述します。 190 | 191 | ```python 192 | @pytest.mark.parametrize(('number', 'expected'), [ 193 | (1, False), 194 | ]) 195 | ``` 196 | 197 | のように記述すると `test_is_prime(1, False)` が実行されます。複数記述すればその分だけ `number, expected` に値が渡され `test_is_prime()` が実行されます。 198 | 199 | ### 注意点 200 | 201 | デコレータの書き方には注意して下さい。次のいずれも正しい書き方ではありません。 202 | 203 | **スペルミス** 204 | 205 | !!! warning "誤" 206 | `@pytest.mark.parameterized` 207 | 208 | !!! check "正" 209 | `@pytest.mark.parametrized` 210 | 211 | **文字列をタプルにしていない** 212 | 213 | !!! warning "誤" 214 | ```python 215 | @pytest.mark.parametrized('number', 'expected', [ 216 | ... 217 | ]) 218 | ``` 219 | 220 | !!! check "正" 221 | ```python 222 | @pytest.mark.parametrized(('number', 'expected'), [ 223 | ... 224 | ]) 225 | ``` 226 | 227 | **タプルをリストの要素としない** 228 | 229 | !!! warning "誤" 230 | ```python 231 | @pytest.mark.parametrized(('number', 'expected'), 232 | (1, False), 233 | (2, True), 234 | ... 235 | ) 236 | ``` 237 | 238 | !!! check "正" 239 | ```python 240 | @pytest.mark.parametrized(('number', 'expected'), [ 241 | (1, False), 242 | (2, True), 243 | ... 244 | ]) 245 | ``` 246 | 247 | ## フィクスチャ 248 | 249 | フィクスチャはテストの実行前後で行いたい前処理・後処理を記述するために使用する関数のことです。各テストで同じ前処理・後処理を行う必要がある場合に暗黙的にそれが実行できるようになります。 250 | 251 | ### ファイルを扱うテスト 252 | 253 | ファイルを扱う関数はフィクスチャが有効です。今ファイルから整数を受け取り、それを昇順に読み込む関数を考えます。 254 | 255 | ```python 256 | from typing import List 257 | 258 | 259 | # List[int] で要素が int のリスト型を表す型ヒントになる 260 | def load_numbers_sorted(txt: str) -> List[int]: 261 | numbers = [] 262 | 263 | with open(txt) as f: 264 | numbers = sorted(map(lambda e: int(e), f)) 265 | 266 | return numbers 267 | ``` 268 | 269 | この関数は入力値としてファイルのパスを受け取ります。そのため、事前にファイルを用意しなければいけません。このファイルを用意するためにフィクスチャが利用できます。 270 | 271 | !!! warning "注意" 272 | 関数がファイルを必要とするからと言ってテスト用のファイルをあらかじめリポジトリにコミットするようなことは避けるべきです。そのようなことをするとテストパターンが増えるたびにファイルも増えてしまい、管理が複雑になります。 273 | 274 | ### 前処理の書き方 275 | 276 | 下記のような整数を保存したファイルを用意して `load_numbers_sorted()` のためのテスト `test_load_numbers_sorted()` を作成してみます。 277 | 278 | **numbers.txt** 279 | 280 | ``` 281 | 2 282 | 5 283 | 4 284 | 3 285 | 1 286 | ``` 287 | 288 | `test_load_numbers_sorted()` が実行される前にファイルを用意する必要があるため次のようにフィクスチャを使ってファイルを作成します。 289 | 290 | ```python 291 | import pytest 292 | 293 | 294 | @pytest.fixture 295 | def txt() -> str: 296 | with open('numbers.txt', 'w') as f: 297 | for n in [2, 5, 4, 3, 1]: 298 | f.write('{}\n'.format(n)) 299 | 300 | yield 'numbers.txt' 301 | ``` 302 | 303 | `numbers.txt` というファイルを作り、そのファイル名を返却しています。このフィクスチャを使って `test_load_numbers_sorted()` を実行するには次のようにします。 304 | 305 | ```python 306 | def test_load_numbers_sorted(txt): 307 | assert load_numbers_sorted(txt) == [1, 2, 3, 4, 5] 308 | ``` 309 | 310 | テスト関数にフィクスチャと同じ名前の引数 `txt` を渡します。すると `txt` にはフィクスチャ `txt()` の戻り値 `numbers.txt` が入ってきます。このコードを実行すると 311 | 312 | 1. `txt()` が呼ばれる 313 | 1. `numbers.txt` が作成される 314 | 1. `test_load_numbers_sorted('numbers.txt')` が呼ばれる 315 | 316 | という振る舞いをします。 317 | 318 | ### 後処理の書き方 319 | 320 | `numbers.txt` はテストが終われば不要なため、後処理としてファイルを削除してあげましょう。ファイルを削除するにはフィクスチャ `txt()` に次の行を追加します。 321 | 322 | ```python 323 | import os 324 | 325 | 326 | @pytest.fixture 327 | def txt() -> str: 328 | ... 329 | 330 | yield 'numbers.txt' 331 | 332 | os.remove('numbers.txt') 333 | ``` 334 | 335 | こうするとテストが終わると `os.remove('numbers.txt')` が呼び出され、ファイルが削除されます。つまりフィクスチャは 336 | 337 | ```python 338 | @pytest.fixture 339 | def txt(): 340 | # 前処理 341 | 342 | yield ... # テスト関数に何らかの値を渡す 343 | 344 | # 後処理 345 | ``` 346 | 347 | という構造をしています。`test_load_numbers_sorted(txt)` の引数 `txt` はフィクスチャ `txt()` で 348 | 何を返したかで型が決まります。 349 | 350 | ### フィクスチャの連携 351 | 352 | フィクスチャから別のフィクスチャを呼び出すこともできます。 353 | 354 | ```python 355 | @pytest.fixture 356 | def txt_and_list(txt) -> Tuple[str, List[int]]: 357 | yield txt, [1, 2, 3, 4, 5] 358 | 359 | 360 | def test_load_numbers_sorted(txt_and_list): 361 | assert load_numbers_sorted(txt_and_list[0]) == txt_and_list[1] 362 | ``` 363 | 364 | この場合 `txt() -> txt_and_list()` の順にフィクスチャが実行され、その結果が `test_load_numbers_sorted()` に渡されます。 365 | 366 | ### テンポラリの作成 367 | 368 | pytest には安全にテンポラリを作成するための `tmpdir` というフィクスチャがあらかじめ用意されています。先に見た例ではファイルがローカルに作られるため、大量のファイルが作られるとディレクトリが汚れてしまいますが `tmpdir` を使うと `/tmp` 配下にファイルを作成するため、ファイル管理がスマートになります。 369 | 370 | `tmpdir` の使い方は次のとおりです。 371 | 372 | ```python 373 | @pytest.fixture 374 | def txt(tmpdir) -> str: 375 | tmpfile = tmpdir.join('numbers.txt') 376 | 377 | with tmpfile.open('w') as f: 378 | for n in [2, 5, 4, 3, 1]: 379 | f.write('{}\n'.format(n)) 380 | 381 | yield str(tmpfile) 382 | 383 | tmpfile.remove() 384 | ``` 385 | 386 | ### フィクスチャのスコープ 387 | 388 | 通常フィクスチャはテスト単位で呼び出されます。 389 | 390 | ```python 391 | def test_sample1(txt): 392 | ... 393 | 394 | 395 | def test_sample2(txt): 396 | ... 397 | ``` 398 | 399 | この場合、フィクスチャ `txt()` は各テスト関数を実行するたびに毎回呼び出されます。場合によってはこれが非効率で冗長になることもあります。このような場合はフィクスチャが呼び出されるタイミングを次のようにして変更することができます。 400 | 401 | ```python 402 | @pytest.fixture(scope='module') 403 | def txt(tmpdir) -> str: 404 | ... 405 | ``` 406 | 407 | `scope` に指定できる値は次のとおりです。 408 | 409 | | scope | 説明 | 410 | | -------- | ----------------------------------------------------------- | 411 | | function | テスト関数ごとにフィクスチャを実行(デフォルト) | 412 | | module | 同一モジュール(ソースコード)内で1回だけフィクスチャを実行 | 413 | | class | 同一クラス内で1回だけフィクスチャを実行 | 414 | | session | テスト実行時に1回だけフィクスチャを実行 | 415 | 416 | ただフィクスチャのスコープはむやみに広げないほうが良いです。フィクスチャの設定をテスト間で共有すると依存関係が生まれてしまい、不意にテストが成功してしまうケースがあるからです。テスト関数ごとにフィクスチャを実行しても問題ない場合はそのようにすべきです。 417 | 418 | ### conftest.py 419 | 420 | 複数のファイルをまたいで共通のフィクスチャを使用したいこともあると思います。そのような時はフィクスチャを `conftest.py` というファイルに定義しましょう。`conftest.py` 内のフィクスチャは pytest によって自動的にインポートされ、`conftest.py` があるディレクトリ配下で暗黙的に参照できるようになります。 421 | 422 | ``` 423 | . 424 | └─tests 425 | ├─conftest.py .............. 全テストで参照可能 426 | ├─test_sample1 427 | │ ├─conftest.py .......... test_sample2.py, test_sample3.py で参照可能 428 | │ ├─test_sample2.py 429 | │ └─test_sample3.py 430 | └─test_sample4 431 | ├─conftest.py .......... test_sample5.py, test_sample6.py で参照可能 432 | ├─test_sample5.py 433 | └─test_sample6.py 434 | ``` 435 | 436 | ## 標準出力のキャプチャ 437 | 438 | 標準出力にメッセージを出力する関数をテストしたい時には標準出力をキャプチャして出力されたメッセージを確認することができます。例えば次のようなフィボナッチ数列を出力する関数を考えます。 439 | 440 | ```python 441 | def fibonacci(n: int): 442 | a = 0 443 | b = 1 444 | 445 | for _ in range(n): 446 | print(b) 447 | 448 | a, b = b, a + b 449 | ``` 450 | 451 | この関数を `fibonacci(5)` として呼び出すと標準出力には 452 | 453 | ``` 454 | 1 455 | 1 456 | 2 457 | 3 458 | 5 459 | ``` 460 | 461 | と出力されます。本当にこのように出力されるかどうかをテストしたい時には次のように書きます。 462 | 463 | ```python 464 | def test_fibonacci(capsys): 465 | fibonacci(5) 466 | 467 | out, _ = capsys.readouterr() 468 | 469 | assert out == ( 470 | '1\n' 471 | '1\n' 472 | '2\n' 473 | '3\n' 474 | '5\n' 475 | ) 476 | ``` 477 | 478 | `capsys` は標準出力と標準エラー出力をキャプチャするためのフィクスチャです。 479 | 480 | ```python 481 | capsys.readouterr() 482 | ``` 483 | 484 | はキャプチャした標準出力と標準エラー出力の文字列をタプルとして返します。 485 | 486 | ## モック 487 | 488 | モックとは関数やクラスが相互に依存して動作する時に、依存する関数やクラスが正しく使われているかどうかをテストする時に使われるオブジェクトのことです。例えば次のコードを見てみましょう。 489 | 490 | **interaction.py** 491 | 492 | ```python 493 | def send(message: str): 494 | receive(message) 495 | 496 | 497 | def receive(message: str): 498 | print('received: {}'.format(message)) 499 | ``` 500 | 501 | いま関数 `send()` は引数で受け取った文字列を `receive()` にそのまま渡さなければならないという仕様があったとします。このとき、 `send()` が仕様どおりに実装されているかどうかをテストするためには 502 | 503 | - `send()` は `receive()` を1回だけ呼び出しているか? 504 | - `send()` が受け取った文字列は `receive()` にそのまま渡されているか? 505 | 506 | を確認する必要があります。もしこの振る舞いをテストで確認することができれば 507 | 508 | ```python 509 | def send(message: str): 510 | receive('[1]: {}'.format(message)) 511 | receive('[2]: {}'.format(message)) 512 | ``` 513 | 514 | のように仕様に沿っていない実装を間違った実装として検出できるようになります。モックを使うとこのような確認がテストできるようになります。 515 | 516 | ### モックの使い方 517 | 518 | モックを使うには [pytest-mock] という pytest のプラグインを使用します。インストールは pipenv で次のようにできます。 519 | 520 | ```shell 521 | $ pipenv install -d pytest-mock 522 | ``` 523 | 524 | `send()` が正しい形式で `receive()` を呼び出しているかどうかを確認するためには `receive()` が受け取った引数と呼び出し回数を記憶する仕組みが必要になります。それを実現するために `receive()` を偽の実装にすり替えて、引数や呼び出し回数を保存できるオブジェクト(すなわちモック)にするというアプローチを取ります(これをモンキーパッチといいます)。`pytest-mock` をインストールすると `mocker` というフィクスチャが使用できるようになります。この `mocker` を使って次のように `receive()` をモックにすることができます。 525 | 526 | ```python 527 | def test_send(mocker): 528 | receive = mocker.patch('studies.interaction.receive') 529 | ``` 530 | 531 | `mocker.patch()` は引数で受け取った文字列の関数をモック化して返す関数です。`mocker.patch()` を呼び出した後では `send()` が呼び出す `receive()` は `interaction.py` で定義された `receive()` の代わりにモック化された偽の `receive()` が呼び出されるようになります。 532 | 533 | ```python 534 | def test_send(mocker): 535 | receive = mocker.patch('studies.interaction.receive') 536 | 537 | send('Hello World!') 538 | ``` 539 | 540 | ここで呼び出した `send()` は内部で `receive()` を呼んでいますがその `receive()` は `mocker.patch()` が作成したモック化された `receive` になります。そしてこの `receive` は引数で受け取った値や呼び出し回数を記録したオブジェクトになっています。 541 | 542 | さらに次のような行をテストコードに追加してみましょう。 543 | 544 | ```python 545 | receive.assert_called_once_with('Hello World!') 546 | ``` 547 | 548 | これは `receive()` が `'Hello World!'` という文字列を受け取って 1 回だけ呼び出されたかどうかを確認するテストになります。テストコード全体は次のとおりになります。 549 | 550 | ```python 551 | def test_send(mocker): 552 | receive = mocker.patch('studies.interaction.receive') 553 | 554 | send('Hello World!') 555 | 556 | receive.assert_called_once_with('Hello World!') 557 | ``` 558 | 559 | 試しに `send()` の実装をわざと間違えた実装にしてみましょう。`receive.assert_called_once_with()` のところでテストが失敗するはずです。 560 | 561 | ### 呼び出し履歴の確認 562 | 563 | モック `receive` は自分がどのような引数で何回呼ばれたのかを履歴として残しています。その呼び出し履歴を参照するには `receive.call_args_list` を参照します。 564 | 565 | ```python 566 | >>> receive.call_args_list 567 | [call('Hello World!')] 568 | ``` 569 | 570 | これは `receive()` が `'Hello World!'` を引数として1回だけ呼ばれたことを意味します。このリストの内容を確認しても `send()` が `receive()` を正しく呼んだかどうかをテストすることができます。 571 | 572 | ```python 573 | def test_send(mocker): 574 | receive = mocker.patch('studies.interaction.receive') 575 | 576 | send('Hello World!') 577 | 578 | assert receive.call_args_list == [ 579 | mocker.call('Hello World!'), 580 | ] 581 | ``` 582 | 583 | 例えば `send()` が次のように実装されていたとすると 584 | 585 | ```python 586 | def send(message: str): 587 | receive('[1]: {}'.format(message)) 588 | receive('[2]: {}'.format(message)) 589 | ``` 590 | 591 | 呼び出し履歴のテストは 592 | 593 | ```python 594 | assert receive.call_args_list == [ 595 | mocker.call('[1]: Hello World!'), 596 | mocker.call('[2]: Hello World!'), 597 | ] 598 | ``` 599 | 600 | と書くことができます。 601 | 602 | ### 戻り値の定義 603 | 604 | `send()` の振る舞いが `receive()` の戻り値に依存して変わるケースを考えます。 605 | 606 | ```python 607 | def send(message: str): 608 | ok = receive(message) 609 | 610 | if ok: 611 | print('success') 612 | else: 613 | print('failure') 614 | 615 | 616 | def receive(message: str) -> bool: 617 | print('received: {}'.format(message)) 618 | 619 | return True 620 | ``` 621 | 622 | この場合 `receive()` の戻り値に応じて `send()` が出力するメッセージが変わることをテストで確認する必要が出てきます。サンプルの `receive()` は常に `True` しか返さないので、 `False` を返した時の 623 | `send()` の振る舞いが確認できません。このような場合でもモックを使って `receive()` の戻り値を上手く制御することができます。 624 | 625 | ```python 626 | def test_send(mocker, capsys): 627 | receive = mocker.patch('studies.interaction.receive', return_value=False) 628 | 629 | send('Hello World!') 630 | 631 | receive.assert_called_once_with('Hello World!') 632 | 633 | out, _ = capsys.readouterr() 634 | 635 | assert out == 'failure\n' 636 | ``` 637 | 638 | `mocker.patch()` の引数に `return_value=False` を渡すと `send()` 内で呼び出している `receive()` は `False` を返すように偽装されます。 639 | 640 | ### スパイ 641 | 642 | モックを使うと `receive()` の実装は完全に別物に置き換わりますが場合によっては本物の `receive()` を呼びつつ、呼び出し回数を確認したいこともあると思います。そのような場合はスパイを作成することで実現できます。例えば `receive()` が `studies/interaction.py` に定義されている場合 643 | 644 | ```python 645 | import studies.interaction 646 | 647 | receive = mocker.spy(studies.interaction, 'receive') 648 | ``` 649 | 650 | とすることでスパイを作成することができます。`mocker.spy()` が返却する関数は本物の `receive()` に `assert_called_once_with()` などのメソッドが追加されたインスタンスになります。使い方はモンキーパッチの場合と同様です。 651 | 652 | [pytest]: https://docs.pytest.org/en/latest/ 653 | [pytest-mock]: https://github.com/pytest-dev/pytest-mock 654 | -------------------------------------------------------------------------------- /docs/ch09-01-tools.md: -------------------------------------------------------------------------------- 1 | # ツール一覧 2 | 3 | Python には pip や venv 以外にも開発で使用するツールが多く存在しており、ネットで調べるときに混乱することが多いです。 4 | そこでどんなツールが使われているのかを整理して解説します。 5 | 6 | ネットで検索して見つかる Python ツールは、おおむね次の 3 つに分類されます。 7 | 8 | 1. Python 自体のバージョンを管理する 9 | 2. 仮想環境を管理する 10 | 3. パッケージやパッケージ間の依存関係を管理する 11 | 12 | ツールによっては 2. と 3. の両方をサポートするようなツールもあるため、どういうシーンで利用すればよいのかがわかりにくいところがあります。 13 | 14 | ネットで検索して見つかるツールには次のようなものがあります。 15 | 16 | | ツール | Python バージョン管理 | 仮想環境管理 | パッケージ管理 | 依存関係解決 | ロックファイル生成 | パフォーマンス | 備考 | 17 | | ----------------------------- | --------------------- | -------------------------------- | -------------------------------- | ------------------ | ------------------ | ------------------ | ------------------------------ | 18 | | pyenv | :white_check_mark: | | | | | :star: | Python のバージョン管理 | 19 | | [venv](./ch04-04-venv.md) | | :white_check_mark: | | | | :star: | Python 標準ライブラリ | 20 | | virtualenv | | :white_check_mark: | | | | :star::star: | `venv` で代用可能 | 21 | | [pip](./ch04-03-pip.md) | | | :white_check_mark: | | | :star: | Python 標準のパッケージ管理 | 22 | | pip-tools | | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :star: | `requirements.txt` 強化 | 23 | | pipx | | | :white_check_mark:(グローバル) | | | :star::star: | CLI ツール専用 | 24 | | [pipenv](./ch09-02-pipenv.md) | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :octicons-star-24: | 遅いため最近は非推奨気味 | 25 | | [poetry](./ch09-03-poetry.md) | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :star::star: | 近年の推奨ツール | 26 | | rye | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :star::star: | 試験的だが統合ツールとして注目 | 27 | | uv | | :white_check_mark:(内部で管理) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :star::star::star: | `pip-tools` の超高速代替 | 28 | 29 | ## どのツールを使うべきか 30 | 31 | どのツールを使う場合でも `requirements.txt` は生成しておくのがベターです 32 | (どのツールでも生成コマンドが用意されています)。 33 | `requirements.txt` があれば、Python の標準機能である pip を使ってパッケージのインストールができるようになるためです。 34 | 35 | `requirements.txt` さえ生成していれば、どのツールを使ってもよいですが、使い分けのおおまかな方針は次のようになります。 36 | 37 | ### シンプルな開発 38 | 39 | - `venv` + `pip` (標準機能) 40 | - `pip-tools` (依存関係を固定したい場合) 41 | 42 | ### より便利な管理を求める場合 43 | 44 | - `poetry` (仮想環境+パッケージ管理) 45 | - `pipx` (CLI ツール管理) 46 | 47 | ### 最新のトレンドを取り入れたい場合 48 | 49 | - `rye` (統合環境管理の試験的ツール) 50 | - `uv` (超高速なパッケージ管理) 51 | 52 | ### Python のバージョン管理をしたい場合 53 | 54 | - `pyenv` (複数の Python バージョンを使いたい場合) 55 | -------------------------------------------------------------------------------- /docs/ch09-02-pipenv.md: -------------------------------------------------------------------------------- 1 | # pipenv 2 | 3 | `pipenv` は `pip` と `venv` の両方の機能を兼ね備えたサードパーティ製のパッケージ管理ツールです。`venv` で仮想環境を作成してから `pip` でパッケージをインストールするまでの手順では下記のように異なるコマンドを実行する必要がありますが、これを 1 つのコマンドで実行できるようにしたものが `pipenv` です。 4 | 5 | | 操作 | コマンド (macOS, Linux) | コマンド (Windows) | 6 | |--------------------------|-------------------------|--------------------------------| 7 | | 仮想環境の作成 | `python -m venv .venv` | `py -m venv .venv` | 8 | | 仮想環境を有効にする | `. .venv/bin/activate` | `.venv/Scripts/activate.bat` | 9 | | 仮想環境を無効にする | `deactivate` | `.venv/Scripts/deactivate.bat` | 10 | | パッケージのインストール | `pip3 install [name]` | `py -3 -m pip install [name]` | 11 | 12 | 各操作に応じて実行するコマンドも異なれば OS ごとにも異なっており、とても複雑です。しかし `pipenv` を使うと上記の操作は次のようになります。 13 | 14 | | 操作 | コマンド | 15 | |--------------------------|-------------------------| 16 | | 仮想環境の作成 | `pipenv --python 3` | 17 | | 仮想環境を有効にする | `pipenv shell` | 18 | | 仮想環境を無効にする | `exit` | 19 | | パッケージのインストール | `pipenv install [name]` | 20 | 21 | このように `pipenv` というコマンド 1 つで仮想環境の作成とパッケージのインストールの両方が実行できるため、操作がシンプルになります。また実行するコマンドは OS によらず同じです。 22 | 23 | ## 特徴 24 | 25 | `pipenv` はパッケージのインストールを必ず仮想環境内で実行するように作られています。そのため仮想環境を有効にしていない状態で 26 | 27 | ```shell 28 | $ pipenv install [name] 29 | ``` 30 | 31 | というコマンドを打っても自動的に仮想環境を作成して、それを有効にした上でパッケージのインストールを実行します。 32 | 33 | ## インストール 34 | 35 | `pipenv` のインストールは下記のようにします。 36 | 37 | ```shell 38 | $ pip3 install pipenv 39 | ``` 40 | 41 | !!! note 42 | `pipenv` のインストールは仮想環境内で行う必要はありません。なぜなら `pipenv` 自体が仮想環境を作成するツールだからです。 43 | 44 | ## 使い方 45 | 46 | まず作業用ディレクトリを用意します。 47 | 48 | ```shell 49 | $ mkdir sandbox 50 | ``` 51 | 52 | `pipenv` を使ってサードパーティライブラリの `requests` をインストールするには次のようにします。 53 | 54 | ```shell 55 | $ cd sandbox 56 | $ pipenv install requests 57 | ``` 58 | 59 | 自動的に仮想環境が作成され、その仮想環境内に `requests` がインストールされます。次に `requests` を使用した次のようなソースコードを用意します。 60 | 61 | **main.py** 62 | 63 | ```python 64 | #!/usr/bin/env python 65 | 66 | 67 | import requests 68 | 69 | 70 | def main(): 71 | response = requests.get('http://example.com') 72 | print(response.text) 73 | 74 | 75 | if __name__ == '__main__': 76 | main() 77 | ``` 78 | 79 | ソースコードを作成したら仮想環境を有効にして実行してみます。 80 | 81 | ```shell 82 | $ pipenv shell 83 | (sandbox) $ python main.py 84 | ``` 85 | 86 | 上記の 2 行のコマンドは次のように 1 行で実行することもできます。 87 | 88 | ```shell 89 | $ pipenv run python main.py 90 | ``` 91 | 92 | `pipenv run [command]` は仮想環境を有効にした上で `[command]` を実行してくれる機能です。 93 | 94 | ## Pipfile 95 | 96 | `pip` にはインストールしたいパッケージをテキストファイルに記述しておく `requirements.txt` という仕組みがありましたが、`pipenv` はこれの代替として `Pipfile` というテキストファイルが使用できます。先程の `requests` をインストールするとディレクトリ内に `Pipfile` が作成されていると思います。 97 | 98 | **Pipfile** 99 | 100 | ```shell 101 | [[source]] 102 | name = "pypi" 103 | url = "https://pypi.org/simple" 104 | verify_ssl = true 105 | 106 | [dev-packages] 107 | 108 | [packages] 109 | requests = "*" 110 | 111 | [requires] 112 | python_version = "3.7" 113 | ``` 114 | 115 | `pipenv` はパッケージをインストールすると `Pipfile` にインストールしたパッケージを記録するようになっています。もしディレクトリ内に `Pipfile` がある場合、下記のコマンドを実行すると `Pipfile` 内に記述されたパッケージをインストールしてくれます。 116 | 117 | ```shell 118 | $ pipenv install 119 | ``` 120 | 121 | つまり `Pipfile` をバージョン管理しておくことで他の人の環境でも自分がインストールしたパッケージと同じものをインストールできるようになります。 122 | 123 | ## Pipfile.lock 124 | 125 | `Pipfile` と合わせて `Pipfile.lock` というファイルも作成されていると思います。このファイルはインストールしたパッケージのバージョンを保存しているファイルです。`pipenv install` でパッケージをインストールすると、インストールされるパッケージのバージョンはその時点での最新版が取得されるようになっているため、自分がインストールしたパッケージのバージョンとは厳密には異なるバージョンのパッケージが他の人の環境にインストールされる可能性があります。もしバージョンも含めて完全に一致するものをインストールしたい場合は `Pipfile` の代わりに `Pipfile.lock` を使ってインストールすることで実現できます。 126 | 127 | `Pipfile.lock` を使ってインストールするには次のようにします。 128 | 129 | ```shell 130 | $ pipenv sync 131 | ``` 132 | 133 | ## 公式サイト 134 | 135 | `pipenv` はここで説明した機能以外にも便利な機能がたくさんあります。詳細は公式サイトに説明がありますので参考にしてみてください。 136 | 137 | !!! note "pipenv 公式サイト" 138 | [https://pipenv.readthedocs.io/en/latest/](https://pipenv.readthedocs.io/en/latest/) 139 | -------------------------------------------------------------------------------- /docs/ch09-03-poetry.md: -------------------------------------------------------------------------------- 1 | # poetry 2 | 3 | `poetry` は `pipenv`と同様の課題を解決するために作られたサードパーティ製のパッケージ管理ツールです。 4 | 5 | !!! note "poetry 公式サイト" 6 | [https://python-poetry.org/](https://python-poetry.org/) 7 | 8 | poetry を使った場合の仮想環境の作成からパッケージのインストールまでの手順は下記のとおりです。 9 | 10 | | 操作 | コマンド | 11 | |--------------------------|-------------------------| 12 | | 仮想環境の作成 | なし(暗黙的に作られる)| 13 | | 仮想環境を有効にする | `poetry shell` | 14 | | 仮想環境を無効にする | `exit` | 15 | | パッケージのインストール | `poetry add [name]` | 16 | 17 | ## インストール 18 | 19 | `poetry` のインストールは下記のようにします。 20 | 21 | === "Linux, macOS, Windows (WSL)" 22 | 23 | ```shell 24 | $ curl -sSL https://install.python-poetry.org | python3 - 25 | ``` 26 | 27 | === "Windows (Powershell)" 28 | 29 | PowerShell 上で下記を実行します。 30 | 31 | ```shell 32 | (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py - 33 | ``` 34 | 35 | ## 使い方 36 | 37 | まず作業ディレクトリを用意します。 38 | 39 | ```shell 40 | $ mkdir sandbox 41 | ``` 42 | 43 | 次に poetry の設定ファイルを生成するコマンドを実行します。 44 | 45 | ```shell 46 | $ poetry init -n 47 | ``` 48 | 49 | コマンドを実行すると `pyproject.toml` というファイルが作成されます。 50 | 51 | 次に `pipenv` のときと同様に `requests` をインストールしてみます。 52 | 53 | ```shell 54 | $ poetry add requests 55 | ``` 56 | 57 | `pipenv` の使い方で使用したソースコードを実行するには下記のようにします。 58 | 59 | ```shell 60 | $ poetry shell 61 | (.venv) $ python main.py 62 | ``` 63 | 64 | 上記の 2 行のコマンドは次のように 1 行で実行することもできます。 65 | 66 | ```shell 67 | $ poetry run python main.py 68 | ``` 69 | 70 | ## pyproject.toml と poetry.lock 71 | 72 | `pyproject.toml` は poetry の設定ファイルです。`pipenv` でいうところの `Pipfile` と同じ位置づけのファイルになります。 73 | 74 | **pyproject.toml** 75 | 76 | ```toml 77 | [tool.poetry] 78 | name = "sandbox" 79 | version = "0.1.0" 80 | description = "" 81 | authors = ["..."] 82 | 83 | [tool.poetry.dependencies] 84 | python = "^3.6" 85 | requests = "^2.24.0" 86 | 87 | [tool.poetry.dev-dependencies] 88 | 89 | [build-system] 90 | requires = ["poetry>=0.12"] 91 | build-backend = "poetry.masonry.api" 92 | ``` 93 | 94 | `poetry.lock` はインストールしたパッケージのバージョンを保存しているファイルです。`pipenv` でいうところの `Pipfile.lock` と同じ位置づけのファイルになります。 95 | 96 | `pipenv` の場合 `Pipfile` や `Pipfile.lock` の内容をもとにパッケージをインストールするには下記のようにコマンドを使い分ける必要がありました。 97 | 98 | | 使用ファイル | コマンド | 99 | |----------------|------------------| 100 | | `Pipfile` | `pipenv install` | 101 | | `Pipfile.lock` | `pipenv sync` | 102 | 103 | `poetry` にも同等の機能があるのですが `pyproject.toml` の内容をもとにパッケージをインストールする場合も `poetry.lock` の内容をもとにパッケージをインストールする場合もコマンドは同じです。 104 | 105 | ```shell 106 | $ poetry install 107 | ``` 108 | 109 | `pyproject.toml` と `poetry.lock` の両方がある場合は `poetry.lock` の内容が優先されるという仕組みになっています。 110 | 111 | ## pipenv との違い 112 | 113 | 詳細は割愛しますが `poetry` の方が `pipenv` よりも機能が豊富です。また `pyproject.toml` は `poetry` 専用の設定ファイルではなく Python が公式に策定したパッケージ管理用の設定ファイルになっているため、他のパッケージ管理ツールの設定ファイルとしても使われます。`pip` も新しいバージョンでは `pyproject.toml` を使用することができるようになっています。 114 | -------------------------------------------------------------------------------- /docs/img/python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/img/vscode-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinatz/python-book/be023357b139f6320e14d2b8abcd87ba79537985/docs/img/vscode-01.png -------------------------------------------------------------------------------- /docs/img/vscode-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinatz/python-book/be023357b139f6320e14d2b8abcd87ba79537985/docs/img/vscode-02.png -------------------------------------------------------------------------------- /docs/img/vscode-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinatz/python-book/be023357b139f6320e14d2b8abcd87ba79537985/docs/img/vscode-03.png -------------------------------------------------------------------------------- /docs/img/vscode-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinatz/python-book/be023357b139f6320e14d2b8abcd87ba79537985/docs/img/vscode-04.png -------------------------------------------------------------------------------- /docs/img/vscode-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rinatz/python-book/be023357b139f6320e14d2b8abcd87ba79537985/docs/img/vscode-05.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # ゼロから学ぶ Python 2 | 3 |
4 | 5 |
6 | 7 | このサイトは Python を学ぶ人向けのオンライン学習サイトです。 8 | 9 | ## 対象 10 | 11 | - Python を初めて学ぶ人 12 | - プログラム言語を 1 つ以上経験したことのある人 13 | 14 | 関数やクラス・オブジェクト指向に対する知識をある程度前提にします。 15 | 16 | ## Python の特徴 17 | 18 | - 学習コストが低い 19 | - 標準ライブラリが非常に豊富 20 | - インデントをすることが言語仕様になっている 21 | 22 | ## Python のバージョンについて 23 | 24 | - `2.x.x`: 2020 年 1 月 1 日でサポート終了 25 | - `3.x.x`: 現行バージョン 26 | 27 | `2.x.x` 系は新規開発では使用すべきではありません。このサイトでは `3.x.x` をベースに説明を行います。 28 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ゼロから学ぶ Python 2 | site_description: ゼロから学ぶ Python 3 | site_author: IDA Kenichiro 4 | site_url: http://rinatz.github.io/python-book 5 | repo_name: rinatz/python-book 6 | repo_url: https://github.com/rinatz/python-book 7 | copyright: Copyright © 2025 IDA Kenichiro 8 | 9 | theme: 10 | name: material 11 | palette: 12 | # ライトモード 13 | - media: "(prefers-color-scheme: light)" 14 | scheme: default 15 | primary: orange 16 | accent: blue 17 | toggle: 18 | icon: material/weather-sunny 19 | name: ダークモードに切り替え 20 | # ダークモード 21 | - media: "(prefers-color-scheme: dark)" 22 | scheme: slate 23 | primary: orange 24 | accent: blue 25 | toggle: 26 | icon: material/weather-night 27 | name: ライトモードに切り替え 28 | font: 29 | text: Noto Sans 30 | code: Inconsolata 31 | language: ja 32 | logo: img/python.svg 33 | favicon: img/python.svg 34 | icon: 35 | repo: fontawesome/brands/github 36 | features: 37 | - navigation.instant 38 | 39 | markdown_extensions: 40 | - toc: 41 | permalink: true 42 | - admonition 43 | - pymdownx.details 44 | - pymdownx.superfences 45 | - pymdownx.highlight 46 | - pymdownx.inlinehilite 47 | - pymdownx.tabbed 48 | - footnotes 49 | - pymdownx.emoji: 50 | emoji_index: !!python/name:material.extensions.emoji.twemoji 51 | emoji_generator: !!python/name:material.extensions.emoji.to_svg 52 | - pymdownx.arithmatex: 53 | generic: true 54 | - meta 55 | 56 | nav: 57 | - ホーム: index.md 58 | - 1. Python を始める: 59 | - 1.1. インストール: ch01-01-installation.md 60 | - 1.2. Hello, World!: ch01-02-hello-world.md 61 | - 2. 基本仕様: 62 | - 2.1. 変数: ch02-01-variables.md 63 | - 2.2. 関数: ch02-02-functions.md 64 | - 2.3. コメント: ch02-03-comments.md 65 | - 2.4. 条件文: ch02-04-conditions.md 66 | - 2.5. ループ文: ch02-05-loops.md 67 | - 2.6. リスト内包表記: ch02-06-list-comprehensions.md 68 | - 2.7. ラムダ式: ch02-07-lambdas.md 69 | - 3. クラス: 70 | - 3.1. クラス: ch03-01-classes.md 71 | - 3.2. スコープ: ch03-02-scopes.md 72 | - 3.3. 特殊属性: ch03-03-special-attributes.md 73 | - 3.4. プロパティ: ch03-04-properties.md 74 | - 4. モジュールとパッケージ: 75 | - 4.1. モジュール: ch04-01-modules.md 76 | - 4.2. パッケージ: ch04-02-packages.md 77 | - 4.3. pip: ch04-03-pip.md 78 | - 4.4. venv: ch04-04-venv.md 79 | - 4.7. プロジェクト構成: ch04-07-project-structures.md 80 | - 5. ファイル操作: 81 | - 5.1. ファイル操作: ch05-01-files.md 82 | - 5.2. コンテキスト: ch05-02-contexts.md 83 | - 5.3. CSV: ch05-03-csv.md 84 | - 5.4. JSON: ch05-04-json.md 85 | - 6. 例外: 86 | - 6.1. 例外: ch06-01-exceptions.md 87 | - 7. ジェネレータ: 88 | - 7.1. ジェネレータ: ch07-01-generators.md 89 | - 8. テスト: 90 | - 8.1. doctest: ch08-01-doctest.md 91 | - 8.2. pytest: ch08-02-pytest.md 92 | - 9. 便利ツール: 93 | - 9.1. ツール一覧: ch09-01-tools.md 94 | - 9.2. pipenv: ch09-02-pipenv.md 95 | - 9.3. poetry: ch09-03-poetry.md 96 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "python-book" 3 | version = "1.0.0" 4 | description = "ゼロから学ぶPython" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "mkdocs-material>=9.5.50", 9 | ] 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile pyproject.toml 3 | babel==2.16.0 4 | # via mkdocs-material 5 | certifi==2024.12.14 6 | # via requests 7 | charset-normalizer==3.4.1 8 | # via requests 9 | click==8.1.8 10 | # via mkdocs 11 | colorama==0.4.6 12 | # via mkdocs-material 13 | ghp-import==2.1.0 14 | # via mkdocs 15 | idna==3.10 16 | # via requests 17 | jinja2==3.1.5 18 | # via 19 | # mkdocs 20 | # mkdocs-material 21 | markdown==3.7 22 | # via 23 | # mkdocs 24 | # mkdocs-material 25 | # pymdown-extensions 26 | markupsafe==3.0.2 27 | # via 28 | # jinja2 29 | # mkdocs 30 | mergedeep==1.3.4 31 | # via 32 | # mkdocs 33 | # mkdocs-get-deps 34 | mkdocs==1.6.1 35 | # via mkdocs-material 36 | mkdocs-get-deps==0.2.0 37 | # via mkdocs 38 | mkdocs-material==9.5.50 39 | # via python-book (pyproject.toml) 40 | mkdocs-material-extensions==1.3.1 41 | # via mkdocs-material 42 | packaging==24.2 43 | # via mkdocs 44 | paginate==0.5.7 45 | # via mkdocs-material 46 | pathspec==0.12.1 47 | # via mkdocs 48 | platformdirs==4.3.6 49 | # via mkdocs-get-deps 50 | pygments==2.19.1 51 | # via mkdocs-material 52 | pymdown-extensions==10.14.1 53 | # via mkdocs-material 54 | python-dateutil==2.9.0.post0 55 | # via ghp-import 56 | pyyaml==6.0.2 57 | # via 58 | # mkdocs 59 | # mkdocs-get-deps 60 | # pymdown-extensions 61 | # pyyaml-env-tag 62 | pyyaml-env-tag==0.1 63 | # via mkdocs 64 | regex==2024.11.6 65 | # via mkdocs-material 66 | requests==2.32.3 67 | # via mkdocs-material 68 | six==1.17.0 69 | # via python-dateutil 70 | urllib3==2.3.0 71 | # via requests 72 | watchdog==6.0.0 73 | # via mkdocs 74 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "babel" 6 | version = "2.16.0" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 }, 11 | ] 12 | 13 | [[package]] 14 | name = "certifi" 15 | version = "2024.12.14" 16 | source = { registry = "https://pypi.org/simple" } 17 | sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 } 18 | wheels = [ 19 | { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 }, 20 | ] 21 | 22 | [[package]] 23 | name = "charset-normalizer" 24 | version = "3.4.1" 25 | source = { registry = "https://pypi.org/simple" } 26 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, 29 | { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, 30 | { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, 31 | { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, 32 | { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, 33 | { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, 34 | { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, 35 | { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, 36 | { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, 37 | { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, 38 | { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, 39 | { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, 40 | { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, 41 | { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, 42 | { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, 43 | { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, 44 | { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, 45 | { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, 46 | { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, 47 | { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, 48 | { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, 49 | { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, 50 | { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, 51 | { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, 52 | { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, 53 | { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, 54 | { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, 55 | ] 56 | 57 | [[package]] 58 | name = "click" 59 | version = "8.1.8" 60 | source = { registry = "https://pypi.org/simple" } 61 | dependencies = [ 62 | { name = "colorama", marker = "sys_platform == 'win32'" }, 63 | ] 64 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 65 | wheels = [ 66 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 67 | ] 68 | 69 | [[package]] 70 | name = "colorama" 71 | version = "0.4.6" 72 | source = { registry = "https://pypi.org/simple" } 73 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 74 | wheels = [ 75 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 76 | ] 77 | 78 | [[package]] 79 | name = "ghp-import" 80 | version = "2.1.0" 81 | source = { registry = "https://pypi.org/simple" } 82 | dependencies = [ 83 | { name = "python-dateutil" }, 84 | ] 85 | sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } 86 | wheels = [ 87 | { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, 88 | ] 89 | 90 | [[package]] 91 | name = "idna" 92 | version = "3.10" 93 | source = { registry = "https://pypi.org/simple" } 94 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 95 | wheels = [ 96 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 97 | ] 98 | 99 | [[package]] 100 | name = "jinja2" 101 | version = "3.1.5" 102 | source = { registry = "https://pypi.org/simple" } 103 | dependencies = [ 104 | { name = "markupsafe" }, 105 | ] 106 | sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } 107 | wheels = [ 108 | { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, 109 | ] 110 | 111 | [[package]] 112 | name = "markdown" 113 | version = "3.7" 114 | source = { registry = "https://pypi.org/simple" } 115 | sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 } 116 | wheels = [ 117 | { url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 }, 118 | ] 119 | 120 | [[package]] 121 | name = "markupsafe" 122 | version = "3.0.2" 123 | source = { registry = "https://pypi.org/simple" } 124 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } 125 | wheels = [ 126 | { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, 127 | { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, 128 | { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, 129 | { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, 130 | { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, 131 | { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, 132 | { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, 133 | { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, 134 | { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, 135 | { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, 136 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, 137 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, 138 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, 139 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, 140 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, 141 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, 142 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, 143 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, 144 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, 145 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, 146 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, 147 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, 148 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, 149 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, 150 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, 151 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, 152 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, 153 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, 154 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, 155 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, 156 | ] 157 | 158 | [[package]] 159 | name = "mergedeep" 160 | version = "1.3.4" 161 | source = { registry = "https://pypi.org/simple" } 162 | sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } 163 | wheels = [ 164 | { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, 165 | ] 166 | 167 | [[package]] 168 | name = "mkdocs" 169 | version = "1.6.1" 170 | source = { registry = "https://pypi.org/simple" } 171 | dependencies = [ 172 | { name = "click" }, 173 | { name = "colorama", marker = "sys_platform == 'win32'" }, 174 | { name = "ghp-import" }, 175 | { name = "jinja2" }, 176 | { name = "markdown" }, 177 | { name = "markupsafe" }, 178 | { name = "mergedeep" }, 179 | { name = "mkdocs-get-deps" }, 180 | { name = "packaging" }, 181 | { name = "pathspec" }, 182 | { name = "pyyaml" }, 183 | { name = "pyyaml-env-tag" }, 184 | { name = "watchdog" }, 185 | ] 186 | sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } 187 | wheels = [ 188 | { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, 189 | ] 190 | 191 | [[package]] 192 | name = "mkdocs-get-deps" 193 | version = "0.2.0" 194 | source = { registry = "https://pypi.org/simple" } 195 | dependencies = [ 196 | { name = "mergedeep" }, 197 | { name = "platformdirs" }, 198 | { name = "pyyaml" }, 199 | ] 200 | sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239 } 201 | wheels = [ 202 | { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521 }, 203 | ] 204 | 205 | [[package]] 206 | name = "mkdocs-material" 207 | version = "9.5.50" 208 | source = { registry = "https://pypi.org/simple" } 209 | dependencies = [ 210 | { name = "babel" }, 211 | { name = "colorama" }, 212 | { name = "jinja2" }, 213 | { name = "markdown" }, 214 | { name = "mkdocs" }, 215 | { name = "mkdocs-material-extensions" }, 216 | { name = "paginate" }, 217 | { name = "pygments" }, 218 | { name = "pymdown-extensions" }, 219 | { name = "regex" }, 220 | { name = "requests" }, 221 | ] 222 | sdist = { url = "https://files.pythonhosted.org/packages/c7/16/c48d5a28bc4a67c49808180b6009d4d1b4c0753739ffee3cc37046ab29d7/mkdocs_material-9.5.50.tar.gz", hash = "sha256:ae5fe16f3d7c9ccd05bb6916a7da7420cf99a9ce5e33debd9d40403a090d5825", size = 3923354 } 223 | wheels = [ 224 | { url = "https://files.pythonhosted.org/packages/ee/b5/1bf29cd744896ae83bd38c72970782c843ba13e0240b1a85277bd3928637/mkdocs_material-9.5.50-py3-none-any.whl", hash = "sha256:f24100f234741f4d423a9d672a909d859668a4f404796be3cf035f10d6050385", size = 8645274 }, 225 | ] 226 | 227 | [[package]] 228 | name = "mkdocs-material-extensions" 229 | version = "1.3.1" 230 | source = { registry = "https://pypi.org/simple" } 231 | sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } 232 | wheels = [ 233 | { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, 234 | ] 235 | 236 | [[package]] 237 | name = "packaging" 238 | version = "24.2" 239 | source = { registry = "https://pypi.org/simple" } 240 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 241 | wheels = [ 242 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 243 | ] 244 | 245 | [[package]] 246 | name = "paginate" 247 | version = "0.5.7" 248 | source = { registry = "https://pypi.org/simple" } 249 | sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } 250 | wheels = [ 251 | { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, 252 | ] 253 | 254 | [[package]] 255 | name = "pathspec" 256 | version = "0.12.1" 257 | source = { registry = "https://pypi.org/simple" } 258 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 259 | wheels = [ 260 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, 261 | ] 262 | 263 | [[package]] 264 | name = "platformdirs" 265 | version = "4.3.6" 266 | source = { registry = "https://pypi.org/simple" } 267 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 268 | wheels = [ 269 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 270 | ] 271 | 272 | [[package]] 273 | name = "pygments" 274 | version = "2.19.1" 275 | source = { registry = "https://pypi.org/simple" } 276 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 277 | wheels = [ 278 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 279 | ] 280 | 281 | [[package]] 282 | name = "pymdown-extensions" 283 | version = "10.14.1" 284 | source = { registry = "https://pypi.org/simple" } 285 | dependencies = [ 286 | { name = "markdown" }, 287 | { name = "pyyaml" }, 288 | ] 289 | sdist = { url = "https://files.pythonhosted.org/packages/e7/24/f7a412dc1630b1a6d7b288e7c736215ce878ee4aad24359f7f67b53bbaa9/pymdown_extensions-10.14.1.tar.gz", hash = "sha256:b65801996a0cd4f42a3110810c306c45b7313c09b0610a6f773730f2a9e3c96b", size = 845243 } 290 | wheels = [ 291 | { url = "https://files.pythonhosted.org/packages/09/fb/79a8d27966e90feeeb686395c8b1bff8221727abcbd80d2485841393a955/pymdown_extensions-10.14.1-py3-none-any.whl", hash = "sha256:637951cbfbe9874ba28134fb3ce4b8bcadd6aca89ac4998ec29dcbafd554ae08", size = 264283 }, 292 | ] 293 | 294 | [[package]] 295 | name = "python-book" 296 | version = "1.0.0" 297 | source = { virtual = "." } 298 | dependencies = [ 299 | { name = "mkdocs-material" }, 300 | ] 301 | 302 | [package.metadata] 303 | requires-dist = [{ name = "mkdocs-material", specifier = ">=9.5.50" }] 304 | 305 | [[package]] 306 | name = "python-dateutil" 307 | version = "2.9.0.post0" 308 | source = { registry = "https://pypi.org/simple" } 309 | dependencies = [ 310 | { name = "six" }, 311 | ] 312 | sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } 313 | wheels = [ 314 | { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, 315 | ] 316 | 317 | [[package]] 318 | name = "pyyaml" 319 | version = "6.0.2" 320 | source = { registry = "https://pypi.org/simple" } 321 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 322 | wheels = [ 323 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 324 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 325 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 326 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 327 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 328 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 329 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 330 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 331 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 332 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 333 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 334 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 335 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 336 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 337 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 338 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 339 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 340 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 341 | ] 342 | 343 | [[package]] 344 | name = "pyyaml-env-tag" 345 | version = "0.1" 346 | source = { registry = "https://pypi.org/simple" } 347 | dependencies = [ 348 | { name = "pyyaml" }, 349 | ] 350 | sdist = { url = "https://files.pythonhosted.org/packages/fb/8e/da1c6c58f751b70f8ceb1eb25bc25d524e8f14fe16edcce3f4e3ba08629c/pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb", size = 5631 } 351 | wheels = [ 352 | { url = "https://files.pythonhosted.org/packages/5a/66/bbb1dd374f5c870f59c5bb1db0e18cbe7fa739415a24cbd95b2d1f5ae0c4/pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069", size = 3911 }, 353 | ] 354 | 355 | [[package]] 356 | name = "regex" 357 | version = "2024.11.6" 358 | source = { registry = "https://pypi.org/simple" } 359 | sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494 } 360 | wheels = [ 361 | { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781 }, 362 | { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455 }, 363 | { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759 }, 364 | { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976 }, 365 | { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077 }, 366 | { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160 }, 367 | { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896 }, 368 | { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997 }, 369 | { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725 }, 370 | { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481 }, 371 | { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896 }, 372 | { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138 }, 373 | { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692 }, 374 | { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135 }, 375 | { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567 }, 376 | { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525 }, 377 | { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324 }, 378 | { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617 }, 379 | { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023 }, 380 | { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072 }, 381 | { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130 }, 382 | { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857 }, 383 | { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006 }, 384 | { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650 }, 385 | { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545 }, 386 | { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045 }, 387 | { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182 }, 388 | { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733 }, 389 | { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122 }, 390 | { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545 }, 391 | ] 392 | 393 | [[package]] 394 | name = "requests" 395 | version = "2.32.3" 396 | source = { registry = "https://pypi.org/simple" } 397 | dependencies = [ 398 | { name = "certifi" }, 399 | { name = "charset-normalizer" }, 400 | { name = "idna" }, 401 | { name = "urllib3" }, 402 | ] 403 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 404 | wheels = [ 405 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 406 | ] 407 | 408 | [[package]] 409 | name = "six" 410 | version = "1.17.0" 411 | source = { registry = "https://pypi.org/simple" } 412 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } 413 | wheels = [ 414 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, 415 | ] 416 | 417 | [[package]] 418 | name = "urllib3" 419 | version = "2.3.0" 420 | source = { registry = "https://pypi.org/simple" } 421 | sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } 422 | wheels = [ 423 | { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, 424 | ] 425 | 426 | [[package]] 427 | name = "watchdog" 428 | version = "6.0.0" 429 | source = { registry = "https://pypi.org/simple" } 430 | sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } 431 | wheels = [ 432 | { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, 433 | { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, 434 | { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, 435 | { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, 436 | { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, 437 | { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, 438 | { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, 439 | { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, 440 | { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, 441 | { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, 442 | { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, 443 | { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, 444 | { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, 445 | { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, 446 | { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, 447 | { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, 448 | ] 449 | --------------------------------------------------------------------------------