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

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

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

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

itertools.groupbyを使ったグルーピング・ブレイク処理

何かと気づきが得られるモジュールitertoolsですが、あるリストを走査しながら特定の条件でブレイクして出力を行うと行った処理を上手く記述する方法を検討します。

例えば次のようなリスト(deptでソート済み)があり、dept別にファイルに出力するケースの場合、どのように行うか考えてみます。

emp_list = [
  {'dept': 10, 'ename': 'scott'},
  {'dept': 10, 'ename': 'john'},
  {'dept': 10, 'ename': 'tom'},
  {'dept': 20, 'ename': 'bob'},
  {'dept': 20, 'ename': 'maria'},
]

簡単に思いつくのは、順番に走査しながらdeptが変わったタイミングでファイルに出力する or ファイルを切り替えるといったものです。
以下のコードは、emp_listを上から走査しながら、deptが変わったタイミングでファイルに出力。変わらなかったら貯めこむという例です。

# deptが変わるまで溜め込んでから、ファイルに書き出す例
out = []
for emp in emp_list:
  if len(out) > 0 and out[0]['dept'] != emp['dept']:
    # deptがブレイクした時
    with open("dept-%d.txt" % out[0]['dept'], "wt") as f:
      f.write("\n".join(str(e) for e in out))
    out.clear()
  out.append(emp)
if len(out) != 0:
  # 残ったoutを出力
  with open("dept-%d.txt" % out[0]['dept'], "wt") as f:
    f.write("\n".join(str(e) for e in out))

見た感じ分かりにくいコードですが、この方式のイマイチな点は、ブレイク条件「deptが変わる」でファイル出力する時と、ブレイクしないまま最後に残ったものをファイルに出力する時のコードが重複している点です。groupbyを使うともっと綺麗に書けます。

from itertools import groupby

dept_emp_groups = groupby(emp_list, lambda e:e['dept'])
for dept, dept_group in dept_emp_groups:
    with open("dept-%d.txt" % dept, "wt") as f:
        f.write("\n".join(str(e) for e in dept_group))

groupbyを使うと指定したリスト(listでなくてイテレーション可能なもの)から、特定のキー別にグルーピングした結果を得ることができます。
(実際にはlistではなくイテレータですが)こんなイメージです。

[
  (10, [{'dept': 10, 'ename': 'scott'},{'dept': 10, 'ename': 'john'},  {'dept': 10, 'ename': 'tom'}],
  (20, [{'dept': 20, 'ename': 'bob'},  {'dept': 20, 'ename': 'maria'}],
]

deptをキーにグルーピングしているため、存在するdept別に処理を行います。deptが0件であっても問題ありません。

参考:10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.5.3 ドキュメント

秒から時刻表記、時刻の正規化

100秒を 1min 40secと表記したり、時間の構成要素から人間が理解しやすい時刻表記に正規化する方法。

標準モジュールではないですが、定番のdateutilsモジュールを使うのが楽です。

$ pip install dateutils

100sec -> 1min 40sec

>>> from dateutil.relativedelta import relativedelta
>>> relativedelta(seconds=100).normalized()
relativedelta(minutes=+1, seconds=+40)

123456sec -> 1day 10hours 17minues 36sec

>>> relativedelta(seconds=123456).normalized()
relativedelta(days=+1, hours=+10, minutes=+17, seconds=+36)

normalizedで正規化しているので、1.5hoursは1hour 30minになる

>>> relativedelta(hours=1.5).normalized()
relativedelta(hours=+1, minutes=+30)

文字列として時刻表記にする例

>>> "{0.hours:02}:{0.minutes:02}:{0.seconds:02}".format(relativedelta(seconds=85))
'00:01:25'

日付に月単位で加算減算する (relativedelta)

日付に加算・減算する際には、timedelta が使えますが、月の加算はできません。

日付の加算 減算 timedelta | Python Snippets

標準ライブラリではありませんが、日付処理のデファクトスタンダードともいえる dateutils モジュールを使用することで月の加算ができる relativedeltaが使えます。

ライブラリをインストールします。

$ pip install dateutils
Collecting dateutils
  Downloading dateutils-0.6.6.tar.gz
....
Successfully built dateutils
Installing collected packages: argparse, six, python-dateutil, pytz, dateutils
Successfully installed argparse-1.4.0 dateutils-0.6.6 python-dateutil-2.5.3 pytz-2016.4 six-1.10.0

relativedelta(months=1) とすることで直感的に1ヶ月加算することができます。

>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> datetime.datetime(2016, 12, 1) + relativedelta(months=1)  #年をまたいだ1ヶ月加算
datetime.datetime(2017, 1, 1, 0, 0)

これを応用することで簡単に月末日を得ることもできます。

>>> datetime.datetime(2015, 2, 1) + relativedelta(months=1, days=-1)  # 2/1から1ヶ月加算して1日減算
datetime.datetime(2015, 2, 28, 0, 0)
>>> datetime.datetime(2016, 2, 1) + relativedelta(months=1, days=-1)
datetime.datetime(2016, 2, 29, 0, 0)