作成者別アーカイブ: python-tksssk

python3.5以降の型ヒント(typeing)の使用 – 基本

python3.5以降から使用可能な型ヒントを使う。これを使えばpythonでも静的言語なるように見えるが、実行時には完全に無視されチェックはなにも行われない。
現状では型ヒントのチェックツールでの構文チェックや、PyCharmなどの開発環境でのコード補完のためのヒントとなる点に注意する。

チェックツールmypyのインストール

これを使わないと型ヒントの情報はなにも活かせない。

$ pip install mypy
$ mypy -V
mypy 0.511

型ヒントなしのコード

型ヒントなしのコードとして、こんな関数があったとする。

def concat(a, b):
    return a + b

a,bの双方に文字列を渡すケースを想定しているが、数値を渡すと(意図した操作ではないが)加算した結果が得られる。文字列と数値を渡すと実行時エラーになる。

concat("Hello", "World")    # "HelloWorld"
concat(1, 2)                # 3
concat("s", 2)              # TypeError: must be str not int

型ヒントの付加

意図してない操作を除外したい。実行する前に静的に構文エラーとしたい。そのために型ヒントをつける。このように記述できるのはpython3.5以降。

def concat(s:str, n:str)->str:
    return s + n

引数の型。関数の戻り値の型が情報として付加された。

mypyでの静的チェック

これをmypyでチェックすることで、プログラムの実行前に関数の使用法のエラーとして検出可能になる。

$ mypy type-hint.py
type-hint.py:8: error: Argument 1 to "concat" has incompatible type "int"; expected "str"
type-hint.py:8: error: Argument 2 to "concat" has incompatible type "int"; expected "str"
type-hint.py:9: error: Argument 2 to "concat" has incompatible type "int"; expected "str"

PyCharmなどのIDEでは型ヒントの情報から、あらかじめ警告表示するサポートがある。

また戻り値が文字列であることが分かるため、関数の実行結果に対しての操作が、文字列に対する操作になるように補完される。

その他にもジェネリック型など他の静的言語と比べても遜色ない型ヒント情報が指定できる。

参考:26.1. typing — 型ヒントのサポート

pythonでのflatmap(flattenする)

他の言語で言うところのflatMapだとかflattenする方法。

複数のイテレータなどから連続した要素を得る場合の方法

itertools.chainで複数のiterableなオブジェクトを連結

import itertools

r1 = range(0, 3)
r2 = range(3, 6)

stream = (r1, r2)
print(stream)


flat = itertools.chain(r1, r2)
for n in flat:
    print(n)

実行結果

(range(0, 3), range(3, 6))
0
1
2
3
4
5

chainの場合は複数のiterableなオブジェクトを列挙して指定するが、すでにそれがiterableなコンテナに入っている場合は、
itertools.chain.from_iterableが使用可能。

stream = (r1, r2)
for n in itertools.chain.from_iterable(stream)
    print(n)

参考

10.1. itertools — 効率的なループ実行のためのイテレータ生成関数

pythonで並列処理 (thread)

threadを使って簡易的に並列処理を実装することができる。

threading.Threadクラスの生成時にtarget引数に指定する関数を、別スレッドで並列的に実行できる。

※並列して処理が動作するため、複数のスレッドでデータを共有する場合にはロックなどを使用してスレッドセーフにする必要がある。

# 1秒おきと2秒おきにprintする処理をthreadで実行
import threading
import time

def worker(interval):
    for n in range(3):
        time.sleep(interval)
        print("%s --> %d" % (threading.current_thread().name, n))

th1 = threading.Thread(name="a", target=worker, args=(1,))
th2 = threading.Thread(name="b", target=worker, args=(2,))

th1.start()
th2.start()

参考:17.1. threading — スレッドベースの並列処理

※threadで実行しても、完全に同時に2つの処理が動作するわけではなく、CPython実装ではマルチコアを活かした並行処理はmultiprocessingを使わないとできない。

可変長引数の設定と呼び出し

通常の引数を可変長引数に

pythonで可変長引数を指定するには、仮引数に*を指定する。関数の実装側では、仮引数をタプルとして扱える。

def func1(a, *others):
    print(a, others)

func1(1)        # 1, ()
func1(1, 2)     # 1, (2,)
func1(1, 2, 3)  # 1, (2, 3)

逆の使い方として、タプルやリストなどのシーケンス型に*をつけると実引数に展開して呼び出すことができる。

以下の例ではa, b, cの引数をそれぞれ指定するのではなく、リストの要素を展開してa,b,cに割り当てている。要素数が引数の数と合わない場合はエラーになる(多くても少なくても)。

def func2(a, b, c):
    print("a={}, b={}, c={}".format(a, b, c))

params = [1, 2, 3])
func2(*params)    # a=1, b=2, c=3

キーワード引数を可変長引数に

pythonでは引数を名前付きで指定することもできる。任意のキーワードで引数を受け付けるには、**をつける。

この場合はdictで扱える。

def func3(a, **others):
    print(a, others)

func3(1, b=2, c=3)   # 1 {'b':2, 'c': 3}

通常の引数と同じように実引数のほうに **をつけることで、dict型をキーワード引数に展開できる。

def func2(a, b, c):
    print("a={}, b={}, c={}".format(a, b, c))

params = {'a':1, 'b':2, 'c':3}
func2(**params)    # a=1, b=2, c=3

任意の引数を受け取れる関数

以下のように リスト形式の可変長引数*argsと、 dict形式の引数**kwargs を受け取るようにすると、どのような引数でも受け付けられるようになる。

def func(*args, **kwargs):
    print(args, kwargs)

文字列からbool値に変換する

文字列からbool値に変換するのは、一見bool()関数でできそうですが、これは真偽値判断をしてくれる関数であって、空文字列がFalse。それ以外がTrueになってしまいます。

単純に文字列→boolとなる関数は標準ライブラリには用意されていないようです。

実際に変換したいのはいろいろな文字列→bool値の仕様があると思いますが、以下のような仕様での実装例を考えてみます。

文字列からboolへの変換の例

文字列 bool値
TRUE True
true True
T True
yes True
1 True
それ以外 False

Trueとなる文字列を列挙してマッチするかどうか

def str2bool(s):
     return s.lower() in ["true", "t", "yes", "1"]

# 確認コード
for s in ["TRUE", "FALSE", "true", "false", "T", "F", "yes", "no", "1", "0"]:
    print(s, str2bool(s))

True対象でなければFalseとなるので、これで文字列からboolへの変換が実装できます。

distutils.util.strtoboolを使用

標準ライブラリにはないと書きましたが、別の用途で使用するモジュールにひっそり似たようなものがあります。
これを使用するのも楽です。

from distutils.util import strtobool

# 確認コード
for s in ["TRUE", "FALSE", "true", "false", "T", "F", "yes", "no", "1", "0"]:
    print(s, " ==> ", strtobool(s))    # Trueのとき1, Falseのとき0を返す

1と0の数値で返却されますが、数値の真偽値判別では0はFalse, 0以外はTrueとなるので問題ありません。先ほど挙げた変換例ともマッチするようです。

dictを値でソートして出力する

dictオブジェクトを走査するときなどに値でソートされた結果を得たい場合は、
sortedのkey引数に値を返す関数を指定すれば良い。(key引数はソートしたい値を返す関数を指定できるので、ここを実装すれば任意のソート方法でソートできる)

d = {"a": 3, "b": 2, "c": 1}
for k in sorted(d, key=lambda k:d[k]):
    print(k, d[k])    #c,b,aの順で出力

参考

ソート HOW TO

外部コマンドの実行

外部コマンドを実行するにはsubprocessモジュールを使用する。

subprocess.callで実行できるが、python3ではsubprocess.runを使用することが推奨される。(callはまだ使えるが将来的には廃止の予定)

python3の場合 subprocess.run

たくさんのオプション引数を指定できるので詳細は公式ドキュメント参照。
subprocess.run

import subprocess
subprocess.run(["ls", "-l"])

python2の場合 subprocess.call

たくさんのオプション引数を指定できるので詳細は公式ドキュメント参照。
subprocess.call

import subprocess
subprocess.call(["ls", "-l"])

標準入力や、標準出力をキャプチャしたり、タイムアウトを設けたり、実行エラーをハンドリングしたりといったことが引数を指定することで可能になる。

ファイル・ディレクトリの存在チェック

ファイルやディレクトリのパスが存在するかどうかをチェックするには、低レベルなos.pathや、オブジェクトライクな操作が可能なpathlib(python3)が使用可能。

os.path.exists

文字列でパスを指定。

import os
print(os.path.exists('/tmp/exist-file'))      # ==> True
print(os.path.exists('/tmp/not-exist-file'))  # ==> False

pathlib.Path

python3ではpathlibモジュールでオブジェクト指向的なアプローチが可能なPathオブジェクトから存在チェックができる。

import pathlib
p = pathlib.Path.home() / '.ssh' / 'config'     # $HOME/.ssh/config
print(p.exists())

existsメソッドはファイルでもディレクトリでもパスとして存在すればTrueになる。
ファイルとして、ディレクトリとして、チェックする場合はis_file(), is_dir()が使用可能。

標準入力から読み込む stdin , input

標準入力stdin

sysモジュールのstdinから標準入力のファイルオブジェクトが取得できる

# 標準入力から読み取って出力
import sys
for line in sys.stdin:
   print("--> " + line)

ファイルオブジェクトなのでreadやreadlinesなどの関数も使用可能。

コンソールから入力を受付, input (raw_input: python2)

CLIでのプロンプト入力待ちみたいなのはもっと便利な関数inputがあります。inputはpython3での関数でpython2のときはraw_inputでした。
ビルドイン関数なのでimportは不要。

>>> a = input("input your name: ")
input your name: taro
>>> a
'taro'

ランダムに要素をシャッフルしたり取り出したりする

リストからランダムに要素を1つとりだす

>>> import random
>>> random.choice([1, 2, 3])
2
>>> random.choice([1, 2, 3])
3

リストから重複ありでランダムに要素をn回とりだす

重複するのでサイコロをn回振るイメージ

choiceをリスト内包表記で繰り返す

>>> import random
>>> [random.choice([1, 2, 3, 4, 5, 6]) for _ in range(3)]
[3, 1, 3]

※Python3.6からはchoicesが使える

>>> import random
>>> random.choices([1, 2, 3, 4, 5, 6], k=3)
[5, 3, 3]

リストから重複なしでランダムに要素をn個取り出す

重複しないので、くじ引きをn回するイメージ

>>> import random
>>>random.sample([1,2,3,4,5,6], 3)
[3, 2, 1]

リストの要素の順序をシャッフルする

混ぜるだけなので、要素の構成は変わらない。トランプカードのシャッフルやプレイリストのランダム再生など。

>>> import random
>>> l =[1, 2, 3, 4, 5, 6]
>>> import random.shuffle(l)
>>> l
[3, 6, 4, 5, 2, 1]

シャッフルした新たなリストを返すのではなくリストそのものを変更する。シャッフルされた新たなリストを返したい場合は、前述のsampleを使ってもよい。

>>>random.sample([1,2,3,4,5,6], 6)
[3, 6, 4, 5, 2, 1]

A 以上 B以下のランダムな整数

>>> import random
>>> random.randint(1, 12)
3

0.0以上1.0未満のランダムな実数

>>> import random
>>> random.random()
0.7483361925423171