ゼロから始めるPython(7)

ゼロから始めるPythonの7回目になります。
チュートリアル見たらまだ半分行ってなかった・・・がんばります。
[6回目はこちら](https://qiita.com/wind0411/items/2f3845468cd3c0a7f508)

#おさらい
前回は`モジュール`と`パッケージ`、そして`__init__.py`の役割などを説明しました。
読まれていない方はぜひ一度読んでみてくださいね。

#チュートリアル
[Python3.7 チュートリアル](https://docs.python.org/ja/3.7/tutorial/index.html)

#15. 入出力

これまで、値を出力するときには`print`を使用してきました。
ですが、printではただ値を順番に並べて出力するだけでしたね。

```python
>>> last_name = 'yamada'
>>> first_name = 'taro'
>>> print('my name is ',first_name,last_name)
my name is  taro yamada
```

#15.1. フォーマット済み文字列リテラル

ですが、出力する値を制御したいケースもあります。
そこで、`フォーマット済み文字列リテラル(fまたはF)`で出力してみます。

```python
>>> f'my name is {first_name} {last_name}'
'my name is taro yamada'
>>> F'私の名前は{last_name} {first_name}です。'
'私の名前はyamada taroです。
```
このように、`{}`を用いた式によって式内部の変数やリテラル値を参照することが出来ます。
順番なども意図的に変更できますしね。

なお、`{}`式内で評価前に値を変換する際には
`:` + 整数 = 最小の文字幅指定
`!a` = ascii()関数
`!s` = str()関数
`!r` = repr()関数
を使用します。

詳しくは、[書式指定ミニ言語仕様ガイド](https://docs.python.org/ja/3.7/library/string.html#formatspec)でご確認ください。

##15.2. format()

`format()`関数では、`{}`内部の式に詳細な操作や出力を適用できます。

```pyhon
>>> numerator = 1_000_000
>>> denominator = 10_000_000
>>> percentage = numerator / denominator
>>> '{:-9} : {:2.2%}'.format(numerator, percentage)
'  1000000 : 10.00%'
```

なお、数値リテラルに使われているアンダースコアはPython3.6以降で使用でき、`,(カンマ)`とともに桁区切り文字として認識されます。
(アンダースコアは他にも別の用途として使われていますが、ここでは割愛します）

そして、キーワード引数、順序引数、\**記法によって値を参照することもできます。

```python:
>>> '{one} + {two} = {three}'.format(one=1,two=2,three=3)
'1 + 2 = 3'
>>> '{0} - {1} = {2}'.format(3,1,2)
'3 - 1 = 2'
>>> dic = {'a':1, 'b':2, 'c':3}
>>> '{a} + {b} = {c}'.format(**dic)
'1 + 2 = 3'
```

##15.3. str()、repr()

デバッグする目的で変数を表示したい時、次の関数を用いて文字列に変換することが出来ます。

■[参考(Pythonのstr'()とrepr()の使い分け)](https://gammasoft.jp/blog/use-diffence-str-and-repr-python/)
■[参考([Python] str()関数とrepr()関数)](https://python.g.hatena.ne.jp/muscovyduck/20080816/p1)

`str()`では引数のオブジェクトを「**人間に読みやすい文字列**」として返し、`repr()`では引数に与えられた値と等価な値を返し、インタプリタが解釈できる値に変換します。

```python
>>> import datetime
>>> today = datetime.date.today()
>>> str(today)
'2018-12-23'
>>> repr(today)
'datetime.date(2018, 12, 23)'
>>> d = eval(repr(today))
>>> today - d
datetime.timedelta(0)
```

なお、eval()は文字列を式として評価・実行する関数になりますが、プログラムのセキュリティが低下するため安易に使用しないでくださいね。

##15.4. ファイルの読み書き

###15.4.1. open()

`open()`は、対象のファイルの`file object`を返します。
>open(filename, mode)

modeは次の指定ができます。
`r` : 読み出し専用(既定値)
`w` : 書き込み専用で、同名ファイルが有れば消去
`a` : 同名ファイルがあれば追記し、なければ作成して記述
`r+`: 読み書き可能
`b` : バイナリモード

他にも細かな制御ができますので、[こちら](https://docs.python.org/ja/3.7/library/functions.html#open)を参照ください。

なお、通常はテキストモードで開かれます。
これはエンコーディングでエンコードされたファイルに対して読み書きするモードで、未指定の場合はプラットフォームに依存します。
テキストファイル以外の場合にはバイナリモードで開くようにしましょう。

###15.4.2. close()

`close()`は、対象の`file object`を閉じてリソースを解放します。

> file_object.close()

なお、`with`キーワード(クローズが必要な処理を安全に記述する機能で、ブロック終了時にオブジェクトの終了処理が自動で実行される）がない場合に、明示的に実行する必要があります。

###15.4.2. read()

`read()`は、`file object`に対してread処理を行います。

> file_object.read(size)

引数のsizeが省略される、またsizeが負の数の場合には内容を全て読みだして返却します。

###15.4.3. readline()

`readline()`はfile objectから1行読み取ります。

>file_object.readline()

###15.4.4. 複数行の読み取り

ファイルから複数行を取り出したい場合には、
・`file object`に対してループ処理を行う
・list(file_object)を使用する
・file_object.readlines()を使用する
など、色々な方法があります。

```python:for文
>>> f = open("example.txt")
>>> for line in f:
...     print(line)
...
おはようございます。外は雲ひとつない青空です。

こんにちは。急に雲がかかってきて雨が降ってきました。

こんばんは。さっきまでの天気が嘘のように晴れて、星空が見えます。
```

```python:list
>>> f = open("example.txt")
>>> list(f)
['おはようございます。外は雲ひとつない青空です。\n', 'こんにちは。急に雲がかかってきて雨が降ってきました。\n', 'こん ばんは。さっきまでの天気が嘘のように晴れて、星空が見えます。']
```

```python:readlines
>>> f = open("example.txt")
>>> f.readlines()
['おはようございます。外は雲ひとつない青空です。\n', 'こんにちは。急に雲がかかってきて雨が降ってきました。\n', 'こん ばんは。さっきまでの天気が嘘のように晴れて、星空が見えます。']
```

```python:read
>>> f = open("example.txt")
>>> f.read()
'おはようございます。外は雲ひとつない青空です。\nこんにちは。急に雲がかかってきて雨が降ってきました。\nこんばんは。さっきまでの天気が嘘のように晴れて、星空が見えます。'
```

###15.4.5. write()

`write()では、引数の文字列をファイルに書き込み、その文字数を返します。

>file_object.write(文字列)

###15.5. JSON

~~13日の金曜日のあの人~~
**JSON(JavaScript Object Notation)**の略で、軽量なデータ記述言語です。
JSONは他の言語などでも使用されるデータ記述言語の為、Pythonでも同様にデータの相互交換に使用すべきでしょう。

使用するには、jsonパッケージをインポートします。

>import json

下記に使用の一例を記載しますが、詳細を知るには[jsonモジュールリファレンス](https://docs.python.org/ja/3.7/library/json.html#module-json)を参照ください。

```python
>>> import json
# エンコーディング
>>> j = json.dumps([1, 2, 3, {'a':4, 'b':5}, 6], separators=(',', ':'))
>>> j
'[1,2,3,{"a":4,"b":5},6]'
#デコーディング
>>> json.loads(j)
[1, 2, 3, {'a': 4, 'b': 5}, 6]
```

#16. エラーと例外

それとなくエラーについては触れてきましたが、これから掘り下げて行きたいと思います。

##16.1. 構文エラー

構文エラーはいわずもがな「**`構文解析した結果のエラー`**」です。

下記の例では、`print()`関数の引数の文字列を示すための'(シングルクォート)がないことが示されています。

```python
>>> print(Hello World')
  File "<stdin>", line 1
    print(Hello World')
                    ^
SyntaxError: invalid syntax
```

##16.2. 例外

式や構文が正しい場合であっても、実行中に例外が発生することがあります。
例えば、ゼロによる除算が行われる場合は計算ができないため、例外がスローされます。

```python
>>> 1 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
```

例外の内容を確認するとどのような事が起こったのかがわかります。
この例外を適切に処理することが重要になってきます。

##16.3. try-except

例外を適切に処理するために、`try-except`節を使用します。
try-except節ではtry節で処理を実行し、その中で発生した例外をexcept節で捕捉します。

```python:try.py
try:
    x = int("example")
except ValueError:
    print("value error")
```

```console
$ python try.py
value error
```

ここでは、明示的に「ValueError」を捕捉し、try節で発生した場合の処理を記述しています。
もちろん、任意のエラー、例外ごとに捕捉が可能なため、柔軟に対応させることもできます。

##16.4. raise

システムで発生する例外だけでなく、実装者によって意図的に例外をスローすることもできます。
次の例では、try節で意図的に`ValueError`をスローしています。

```python:raise.py
try:
    raise ValueError('throw value error')
except ValueError:
    print('value error')
```

```console
$ python3 raise.py
value error
```

##16.5. ユーザ定義例外

事前に用意されている例外だけでなく、ユーザが作成した例外を使用したい場面もあります。
この場合は、Exceptionクラス(classに関しては別項で記載します)を直接・間接的に派生して作成します。

```python:error.py
class Error(Exception):
    """Base class for exceptions in this module. """
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, messge):
        self.expression = expression
        self.message = messge

class TransitionError(Error):
    """ Raised when an operation attempts a state transition that's not allowd

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        messge -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.messge = message
```

見ていただくとわかりますが、基底の`Error`クラスはExceptionクラスを継承しています。
これがユーザ定義例外の基底となるクラスです。
その基底クラスを継承した`InputError`、`TransitionError`は、それぞれで属性を持ちます。
このように基底クラスは単純なものにし、派生クラスは提供するための属性だけを保持させて
例外ハンドル時にエラーに関する情報を取得できるようにする程度の実装で良いです。
また、複数の別々の例外をスローするようなモジュールでは、モジュールで定義されている例外の基底クラス＋派生クラスの作成というのが一般的な流れだそうです。

なお、ほとんどの例外の名前には`Error`で終わる名前が定義されています。

##16.6. クリーンアップ動作

例えば、tryの結果によって動作を変更する、結果にかかわらず最終的に行いたい動作がある、といった場合に対して用意されている節があります。

###16.6.1. finally

`finally`節は例外が発生したかどうかにかかわらず、try節(except節)のあとに必ず実行されます。
ただし、except節で例外を処理していない場合、`finally`節実行後に例外を再送出します。

```python:
>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("ゼロ除算")
...     else:
...         print(result)
...     finally:
...         print("演算の終了")
...
>>> divide(2,1)
2.0
演算の終了
>>> divide(2,0)
ゼロ除算
演算の終了
>>> divide("2","1")
演算の終了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
```

### 16.6.2.with文
他の言語でも同じようなものがあるかと思いますが(`using`など)
例えば、オブジェクトの利用後に必ずインスタンスを破棄したい場合などがあります。

この時、`with`文を用いることで、オブジェクトの利用後に必ず破棄することを保証します。

```python:
with open("file.txt") as f:
    for line in f:
        print(line, end="")
```

この文の実行後、ファイル`f`は必ずcloseされます。

#まとめ

例外の中の細かな動きなどは抑えておいたほうがよいかと思いました。