たまに使うPythonの便利な組み込み関数

この記事は[Takumi Akashiro ひとり Advent Calendar 2020](https://qiita.com/advent-calendar/2020/takumi_akashiro "ひとり Advent Calendar 2020")の19日目の記事です。


## 始めに

土曜は練習がてらにAEのスクリプトを作りました！ってストックしていたネタを使おうと思っていましたが、
記事を書く前に調べたところ、同様のことがAEの標準機能でできることが判明し、見事に没になったので、
代わりに、たまに使うPythonの組み込み関数について書きます。

正直、[公式ドキュメント](https://docs.python.org/ja/3/library/functions.html)を毎日10分1関数ずつでも読んだ方が有用です。
みんなも読もう！公式ドキュメント！


## Pythonの組み込み関数

### `zip(*iterables)`
> それぞれのイテラブルから要素を集めたイテレータを作ります。
> [組み込み関数 — Python 3.9.1 ドキュメント](https://docs.python.org/ja/3/library/functions.html) より

```python
>>> src_paths = ["c:/nanka", "c:/unagi", "c:/ankimo"]
>>> dst_paths = ["d:/nanka", "f:/unagi", "e:/ankimo"]
>>> 
>>> list(zip(src_paths, dst_paths))
[('c:/nanka', 'd:/nanka'), ('c:/unagi', 'f:/unagi'), ('c:/ankimo', 'e:/ankimo')]
>>> 
>>> dst_files = ["ohagi", "ooiwa", "karasumi"]
... for src_path, dst_path, dst_file in zip(src_paths, dst_paths, dst_files):
...     print(src_path, dst_path, dst_file)
c:/nanka d:/nanka ohagi
c:/unagi f:/unagi ooiwa
c:/ankimo e:/ankimo karasumi
```

割とよく使いますね。たまに使う、の括りに入れるべきではないかもしれませんが、
知らない人は知らないままかけちゃうので今回紹介します。

個人的には`zip()`と`dict()`の合わせ技`dict(zip(keys, values))`が結構好みです。

### `sorted(iterable, *, key=None, reverse=False)`
イテレート可能なオブジェクトをソートして`list`として返します。
いや、それだけなら知ってるよという方もいると思いますが、今回紹介したいのは`key`引数です。
`key`には引数が一つの関数を渡せます。その結果を使ってソート可能、ということです。

```python
>>> sorted(["A", "a", "d", "B"])
['A', 'B', 'a', 'd']
>>> sorted(["A", "a", "d", "B"], reverse=True)
['d', 'a', 'B', 'A']
>>> 
>>> sorted(["A", "a", "d", "B"], key=str.lower)
['A', 'a', 'B', 'd']
>>> 
>>> sorted(["A", "a", "d", "B"], key=lambda x: {"A": 99, "a": 0, "d": 3}.get(x, 100))
['a', 'd', 'A', 'B']
>>> 
>>> sorted({"onaka": 2, "udon": 0}.items(), key=lambda item: item[1])
[('udon', 0), ('onaka', 2)]
```

例にもあるように、辞書をlistに変えたときにvalue側の値でソートしたい、みたいなケースで使えますね。

### `hasattr(object, name)`
オブジェクトが`name`と名のついた属性を持っているか確認します。


```python
>>> import datetime

>>> class SampleClass(object):
...     # __init__ は実装しないので省略。
...     def print_sample(self):
...         if not hasattr(self, "_text"):
...             print("Generate!")
...             self._text = str(datetime.datetime.now())
...         print(self._text)
...
>>> sample = SampleClass()
>>> sample.print_sample()
Generate!
2020-12-19 23:16:19.197768
>>> sample.print_sample()
2020-12-19 23:16:19.197768
>>> sample.print_sample()
2020-12-19 23:16:19.197768
```

~~個人的にはクソコード生成に便利な関数~~
インスタンス変数を既に定義されてるか確認して、定義してなかったら初期化する～みたいなときに使いますね。

便利ですが、TPOをわきまえないと保守性を下げることに繋がります。
自分もシングルトンもどきを作りたいとき以外は使わないですかね。



### `globals()`
> 現在のグローバルシンボルテーブルを表す辞書を返します。これは常に現在のモジュール (関数やメソッドの中では、それを呼び出したモジュールではなく、それを定義しているモジュール) の辞書です。
> [組み込み関数 — Python 3.9.1 ドキュメント](https://docs.python.org/ja/3/library/functions.html) より

```python
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__spec__': None, '__annotations__': {},'__builtins__': <module 'builtins' (built-in)>}
>>> import sys
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None,
'__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>,
'sys': <module 'sys' (built-in)>}
```

MayaのScriptEditorで`globals()`を使うと、インタプリタで宣言されているオブジェクトを取得できます。
なので、あのモジュールimportしたかな？と思ったときに叩きます。大体は叩く前にimportしちゃいますが。

### `id(object)`
オブジェクトのidを返す関数です。
idが同一のオブジェクトは同一です。(当然ですが)

`hoge is huga`の`is`比較はこのidを比較しています。

```python
>>> sample_str_a = "apple"
>>> sample_str_b = "apple"
>>> 
>>> print(id(sample_str_a) == id(sample_str_b), id(sample_str_a), id(sample_str_b))
True 1289401480176 1289401480176
>>> 
>>> sample_str_a += " pie"
False 1289401480240 1289401480176
# str型は += すると別のオブジェクトを返している。
>>> 
>>> sample_list_a = []
>>> sample_list_b = []
>>> 
>>> sample_list_a == sample_list_b
True
>>> sample_list_a is sample_list_b
False

>>> print(id(sample_list_a) == id(sample_list_b), id(sample_list_a), id(sample_list_b))
False 1289401452736 1289401452800
>>> # isと同じ結果が出る
>>> 
>>> sample_list_c = sample_list_d = []
>>> sample_list_c += ""
>>> print(id(sample_list_c) == id(sample_list_d), id(sample_list_c), id(sample_list_d))
True 1289401453376 1289401453376
>>> # 同一オブジェクトである
>>> 
>>> sample_list_c += ["lorem ipsum"]
>>> print(id(sample_list_c) == id(sample_list_d), id(sample_list_c), id(sample_list_d))
True 1289401453376 1289401453376
>>> # まだ同一オブジェクト
>>> 
>>> print(sample_list_c, sample_list_d)
['sample'] ['sample']
>>> # 中身も同じ
```

前はデバッグで使っていましたが、ptvsdの出現でめっきり使わなくなりました。

## 番外編

### `try-finally`文
例外の有無に関わらず、必ず最後に実行します。

```python
>>> try:
...     sample = 0 / 0
... finally:
...     print("--- Fin ---")
--- Fin ---
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero

>>> try:
...     sample = 0 / 0
... except:
...     pass
... finally:
...     print("---- Fin 2 ----")
---- Fin 2 ----
```

処理が実行できたかに関わらず、セーブしたい[^1]って場合に使えます。
`del` で必ず参照を切らないと不味い場合にも使えます。

[^1]: そんなときあるか？

### `for-else`文
`for`が正常に終了したときに`else`の内容を実行します。
`break`でループを抜けた場合は実行しません。

```python
>>> for x in range(3):
...     print(x)
... else:
...     print("Fin !")
...
0
1
2
Fin !

>>> for x in range(3):
...     print(x)
...     break
... else:
...     print("Fin !")
0
```

検索して見つからなかった場合にデフォ値を設定したいときですかね？
それなら先にデフォ値をセットして、見つかった値で上書きすればいいしなあ……

……うーん使った覚えがない。~~ならここに乗せンなや！~~


## 締め

こういうネタに興味があれば、Fluent PythonとかEffective Pythonとかオススメです。
Fluent Python、1ページ7.8円なのでページ換算すると安いですよ！832ページあるけど。

それに比べて、Effective Pythonは456ページしかない！読みやすい！京極夏彦の文庫版「魍魎の匣」の半分以下！
こっちは割とオススメです。Pythonに手慣れてきたけど、もうちょっと理解を深めてみたいな……って思った時が買い時ですね。