Pythonの組み込み関数69個を制覇する 第6回 p～r

Pythonでは、非常に多くの組み込み関数(Built-in Functions)が用意されており、ライブラリをimportせずとも様々な処理を行うことができます。
基本に立ち返って、組み込み関数の使い方を整理して行きたいと思います。

尚、実行例はWindows Subsystem for Linux 2 (WSL2)のUbuntu18.04にインストールしたPython3.7.5で作成しています。

(注意事項)
更新の期間が空いた結果、第4回までの記事とPythonのマイナーバージョンおよび実行環境が異なっていますのでご注意ください。
また、執筆時現在、Python3.8.0がリリースされていますが、本シリーズではPython3.7系での記述を行っていますので、そちらも留意いただければと思います。

[Pythonの組み込み関数69個を制覇する 第1回 a～b](https://qiita.com/ichi_taro3/items/cd71a8e43040abb446a1)
[Pythonの組み込み関数69個を制覇する 第2回 c～d](https://qiita.com/ichi_taro3/items/696b355d79d01a810381)
[Pythonの組み込み関数69個を制覇する 第3回 e～g](https://qiita.com/ichi_taro3/items/c866b7124415464e8ef1)
[Pythonの組み込み関数69個を制覇する 第4回 h～i](https://qiita.com/ichi_taro3/items/ee55257338b32c073bad)
[Pythonの組み込み関数69個を制覇する 第5回 l～o](https://qiita.com/ichi_taro3/items/f2cb744a879a084e49af)


## 関数の使い方
各関数の使い方を記述していきます。詳細な使い方は他の記事に譲るとして、ここでは簡単な使用例を中心に掲載していきます。

#### pow()

与えた引数の冪乗を計算してくれます。
`pow(x, y)` のように2引数を与えた場合は、x ** y と等価です。

```python
>>> # 整数同士
... pow(2, 3)
8
>>> # 第2引数が負の値
... pow(10, -3)
0.001
>>> # 第2引数が小数
pow(3, 2.5)
15.588457268119896
```

3番めの引数を与えると、剰余を計算します。
ドキュメントによると、`pow(x, y) % z` よりも、`pow(x, y, z)` のほうが効率が良いそうです。

```python
>>> # 2の3乗の5の剰余
... pow(2, 3, 5)
3
>>> # 第2引数が負の場合は第3引数を与えられない
... pow(2, -3, 5)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: pow() 2nd argument cannot be negative when 3rd argument specified
>>> # 剰余計算はすべての引数が整数である必要がある
... pow(2, 1/2, 3)
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: pow() 3rd argument not allowed unless all arguments are integers
```

ちなみに、python3.8からは指数が負の値でも剰余計算ができるようになります。

```python
$ docker container run -it python:3.8
Python 3.8.1 (default, Dec 20 2019, 21:56:21)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> pow(2, -3, 5)
2
```

#### print()

与えられたオブジェクトを文字列に変換した上で、標準出力に書き込みます。
複数の値を渡すことができます。引数sepにより区切り文字を、引数endにより行末を指定します。

```python
>>> # 有名なやつ。endのデフォルトは改行文字(\n)
... print('Hello, world!')
Hello, world!
>>> # 複数を渡した場合。sepのデフォルトはスペース( )
... print('Good Bye', 'Python2')
Good Bye Python2
>>> # 区切り文字、改行文字を設定できる。
... print('foo', 'bar', 'piyo', 'hoge', sep='!!!, ', end='!?\n')
foo!!!, bar!!!, piyo!!!, hoge!?
>>> # 数値
... print(1, 1, 2, 3, 5, 8, 13)
1 1 2 3 5 8 13
>>> # リスト
... print(['H', 'e', 'l', 'l', 'o'])
['H', 'e', 'l', 'l', 'o']
>>> # 自作のクラス
... class MyClass1: pass
...
>>> print(MyClass1())
<__main__.MyClass1 object at 0x7f1c8720a450>
>>> # __str__を実装すると文字列化する値を制御できる
... class MyClass2:
...     def __str__(self):
...         return 'This is ' + self.__class__.__name__
...
>>> print(MyClass2())
This is MyClass2
```

デフォルトでは、`print()`関数は標準出力(sys.stdout)に値を書き込みますが、file引数で書き込み先を変更することができます。

```python
>>> # 標準エラー出力に書き込み
... print('Error has occured!!', file=sys.stderr)
Error has occured!!
>>> # ファイルに書き込み
... with open('hello.txt', 'w') as f:
...     print('Hello, my file', file=f)
...
>>> # 確かにファイルに書き込まれている。
... with open('hello.txt', 'r') as f:
...     f.read()
...
'Hello, my file\n'
>>> # インメモリのファイルストリーム
... import io
>>> memfile = io.StringIO()
>>> print('Into the memory', file=memfile)
>>> memfile.getvalue()
'Into the memory\n'
```

print関数により出力される値は、バッファリングされている場合があります。例えば、書き込まれる値に改行がない場合、追加のデータかファイルのクローズを待つため、即座には出力されないことがあります。
ロギングツールなど即座に出力結果が欲しい場合には、引数に`flush=True`を与えて実行時に即座に書き込ませるようにするのが良いかもしれません。

```python
$ cat counter.py
import time

def counter(n):
    for i in range(1, n+1):
        print(i, end=' ')  # <== 行末を改行ではなくスペースにする
        time.sleep(1)

counter(10)

$ # バッファリングされてしまうので、10秒後に一気に出力される 
$ python3 counter.py
1 2 3 4 5 6 7 8 9 10 
```

```python
$ cat counter2.py
import time

def counter2(n):
    for i in range(1, n+1):
        print(i, end=' ', flush=True)  # <== flush=Trueとすることで強制的に出力が行われる
        time.sleep(1)

counter2(10)

$ # 1秒おきに数字が表示される
$ python3 counter2.py
1 2 3 4 5 6 7 8 9 10 
```


#### property()

オブジェクト指向プログラミングにおけるカプセル化を実現するため、属性のgetter, setter, deleterを実装したくなる事があると思います。`property()` はこれらを簡単に実装できるようにするための便利な手段を提供してくれます。
[property(公式ドキュメント)](https://docs.python.org/ja/3/library/functions.html#property)

以下では、プライベートな属性 `_name` へのパブリックなアクセス手段を、propertyを使って実装しています。

```python
$ cat book.py
class Book:

    def __init__(self, name):
        self._name = name

    def _get_name(self):
        """getter"""
        return self._name

    def _set_name(self, name):
        """setter"""
        self._name = name

    def _delete_name(self):
        """deleter"""
        del self._name

    name = property(_get_name, _set_name, _delete_name, 'This is name property')
```

```python
>>> from book import Book
>>> book = Book('Clean Code')
>>> # getter
... book.name
'Clean Code'
>>> # deleter
... del book.name
>>> book.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/kotaro/book.py", line 8, in _get_name
    return self._name
AttributeError: 'Book' object has no attribute '_name'
>>> # setter
... book.name = 'The Pragmatic Programmer'
>>> book.name
'The Pragmatic Programmer'
```

同じ処理を、デコレータ記法を使って以下のように記述することができます。

```python
class Book:

    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        """This is name property"""
        return self._name

    @name.setter
    def name(self, name):
        """setter"""
        self._name = name

    @name.deleter
    def name(self):
        """deleter"""
        del self._name
```

property の引数に与える属性はメソッドなので、データの変換処理などを書くこともできます。

```python
    @property
    def uppercase(self):
        """ uppercase book name"""
        return self._name.upper()
```

```python
>>> book = Book('Clean Code')
>>> book.name
'Clean Code'
>>> book.uppercase
'CLEAN CODE'
```

#### range()

与えられた引数から、[range型](https://docs.python.org/ja/3/library/stdtypes.html#ranges) のオブジェクトを生成して返却します。
range型は、start, stop, stepの3つの整数の引数をとり、[start, stop)の区間を step刻みで変化する整数のシーケンスを表現します。
1引数のみ与えた場合は、`start=0` `step=1` として、引数の値-1まで0から1刻みで増加するシーケンスを生成します。

```python
>>> r1 = range(10)
>>> r1
range(0, 10)
>>> list(r1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> # インデックスによるアクセスが可能
... r1[3]
3
>>> r1[-2]
8
>>> # スライスが可能。指定範囲の新たなrangeオブジェクトが生成される。
... r1[2:6]
range(2, 6)
>>> r1[:]
range(0, 10)
>>> r1[100:10000]
range(10, 10)
>>> r1[0:0]
range(0, 0)
>>> # イミュータブルなシーケンスなので、変更は不可能
... r1[1] = 10000
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: 'range' object does not support item assignment
>>> # 負の値を与えられるが、無限ループにはならない
... list(range(-100))
[]
>>> # 整数型として扱える値である必要がある。
... range(1.1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
>>> # 2引数の場合は、start, stop
... list(range(3, 7))
[3, 4, 5, 6]
>>> # 3引数の場合は、start, stop, step
... list(range(1, 10, 2))
[1, 3, 5, 7, 9]
>>> # 降順のrange
... list(range(10, -1, -1))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> # 比較が可能。シーケンスの中身を比較する
... range(10) == range(10)
True
>>> range(10) == range(9)
False
>>> # どちらもシーケンスの長さは0なので真になる
... range(0) == range(-100)
True
>>> # シーケンスの要素は [1, 3, 5, 7, 9] なので等価
... range(1, 10, 2) == range(1, 11, 2)
True
```

rangeオブジェクトはpythonで指定回数のループを実現するためにもよく使われます。

```python
>>> for i in range(5):
...     print(i)
...
0
1
2
3
4
```

#### repr()

渡されたオブジェクトの文字列を生成して返却します。
可能な場合は、eval()に渡せば元のオブジェクトと同じオブジェクトとして扱えるような文字列になります。

```python
>>> # 文字列
... repr('Hello')
"'Hello'"
>>> eval(repr('Hello'))
'Hello'
>>> # 数値
... repr(12345)
'12345'
>>> eval(repr(12345))
12345
>>> # リスト
... repr(['This', 'is', 'a', 'list', 'object'])
"['This', 'is', 'a', 'list', 'object']"
>>> eval(repr(['This', 'is', 'a', 'list', 'object']))
['This', 'is', 'a', 'list', 'object']
>>> # range
... repr(range(1, 1000))
'range(1, 1000)'
```

eval()で評価できるような文字列を返却できないときは、`<>` に囲まれたオブジェクトの情報を表示します。

```python
>>> class MyClass1: pass
...
>>> repr(MyClass1())
'<__main__.MyClass1 object at 0x7f7659690690>'
```

`__repr__()` メソッドを実装することで、reprの戻り値を制御することができます。

```python
>>> class MyClass2:
...     def __repr__(self):
...         return self.__class__.__name__ + '()'
...
>>> repr(MyClass2())
'MyClass2()'
>>> eval(repr(MyClass2()))
MyClass2()
```

#### reversed()

シーケンスを渡すと、逆順のシーケンスを返却するイテレータを返します。

```python
>>> reversed([1, 2, 3])
<list_reverseiterator object at 0x7f76596908d0>
>>> list(reversed([1, 2, 3]))
[3, 2, 1]
>>> reversed(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: reversed expected 1 arguments, got 3
>>> list(reversed(range(1, 10)))
[9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> import string
>>> for char in reversed(string.ascii_lowercase):
...     print(char, end=' ')
...
z y x w v u t s r q p o n m l k j i h g f e d c b a
```

独自で実装するクラスでも、`__reversed__()`を実装することでreversed()を利用可能になります。

```python
>>> class MySequence:
...     def __init__(self):
...         self._data = []
...     def append(self, value):
...         self._data.append(value)
...     def __iter__(self):
...         return iter(self._data)
...     def __reversed__(self):
...         return reversed(self._data)
...
>>> sequence = MySequence()
>>> sequence.append(1)
>>> sequence.append(3)
>>> sequence.append(5)
>>> list(sequence)
[1, 3, 5]
>>> reversed(sequence)
<list_reverseiterator object at 0x7f7659699610>
>>> list(reversed(sequence))
[5, 3, 1]
```

#### round()

数値を与えると、指定した小数桁で丸め処理を行います。

```python
>>> # 引数が１つなら整数にする
... round(1.1)
1
>>> round(1.5)
2
>>> # 小数点以下第３位
... round(3.14159265, 3)
3.142
>>> # 小数点以下第５位
... round(2.71828182846, 5)
2.71828
```

組み込み型の丸め処理のルールは**四捨五入ではない**ので注意が必要です。

> 0 のマイナス ndigits 乗の倍数の中で最も近いものに丸められます; 二つの倍数が同じだけ近いなら、偶数を選ぶ方に (そのため、例えば round(0.5) と round(-0.5) は両方とも 0 に、 round(1.5) は 2 に) 丸められます。
[公式ドキュメント#float](https://docs.python.org/ja/3/library/functions.html#round)

```python
>>> # 四捨五入ではないので注意
... round(0.5)
0
>>> round(-0.5)
0
>>> round(-1.5)
-2
```

また、float型の丸め処理では、誤差が発生するのでその点も注意が必要です。

```python
>>> # 2.68ではなく2.67になる
... round(2.675, 2)
2.67
```

10進数の小数演算をより正確に行いたい場合は、標準ライブラリの[Decimal型](https://docs.python.org/ja/3/library/decimal.html)などの利用を検討する必要があるかもしれません。

## 第７回に続く

次回は set() から