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

標準入力から読み込む 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)

pythonでループのネストを減らす定石 itertools

pythonでのネストされたループを減らすためのよく知られた方法を考察。
2重のネストループ程度であればありがちな例でも問題ないが、3重、4重となってくるとitertoolsのありがたみがわかる。

10×10の座標を全走査するときなど

//ありがちなネストループの例
for x in range(10):
  for y in range(10):
    print("%d, %d" % (x,y))

itertools.productを使って全通りの組み合わせを出す。

import itertools
for x, y in itertools.product(range(10), range(10)): #Xの10通り、Yの10通りの全組み合わせ
  print("%d,%d" % (x,y))

※itertoolsはイテレータを生成しているので、全通りの操作をするために膨大な件数の組み合わせを作ったとしてもメモリを大量に消費することはない。

x,y,zの3つのネストでも同じようにネストせずに記述できる。

import itertools
for x, y,z in itertools.product(range(10), range(10), range(10)):
  print("%d,%d,%d" % (x,y,z))

配列、文字列の部分列を全通り取得したい場合

例えば、文字列”ABCDE”の部分文字列を、"A", "AB", "ABC", "ABCD", "ABCDE", "B", "BC"... のように取得したい場合。

//ありがちなネストループの例
text = "ABCDE"
l = len(text)

for s in range(l+1):   #開始インデックスを外側のループに
  for e in range(s+1, l+1):   #終了インデックスを開始インデックスから始めて末端まで
    print(text[s:e])   #部分文字列取得

itertools.combinationsを使って、開始、終了インデックスの決定のパターンを取得する例。

// itertools.combinationsをつかったインデックスの組み合わせ取得
text = "ABCDE"
l = len(text)

for s, e in itertools.combinations(range(l+1), 2):
  print(text[s:e])

参考:組み合わせや順列の列挙に便利なitertoolsの機能

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

リスト(list), 辞書(dict), setのソート(sorted)

リストをソートするにはsorted関数を使用する。
sorted関数は、ソートしたリストを返す関数で渡したリストは変更されない。(リストにはsortメソッドがありリスト自体をソートする操作もできるがsortedを覚えておけば十分)

>>> l = [3, 2, 5, 4, 1]
>>> sorted(l)
>>> [1, 2, 3, 4, 5]
>>> l
[3, 2, 5, 4, 1]

sorted関数は辞書(dict)のキーのソートやsetのソートにも使える。

>>> d = {'c': 10, 'b':1, 'a':30}
>>> sorted(d)   #ソートされたキーのリストを返す
['a', 'b', 'c']

>>> d = set([3,1,2])
>>> d
set([1, 2, 3])
>>> sorted(d)
[1, 2, 3]

sorted関数のreverseをTrueにすれば逆順ソート。

>>> l = [3, 2, 5, 4, 1]
>>> sorted(l, reverse=True)
[5, 4, 3, 2, 1]

sorted関数のkeyを指定することで、任意の条件でソートすることができる。

key関数が返す値でソートされる。以下の例はHogeオブジェクトのname属性を返す関数を指定したことで、name属性でソートしている。

>>> class Hoge:
...   def __init__(self, i, name):
...     self.i = i
...     self.name = name
...   def __repr__(self):
...     return "%d:%s" % (self.i, self.name)
...
>>> l2 = [Hoge(2, 'c'), Hoge(3, 'a'), Hoge(1, 'b')]
>>> l2
[2:c, 3:a, 1:b]
>>> sorted(l2, key=lambda h: h.name) #name属性を返す
[3:a, 1:b, 2:c]

上の例では、name属性を返す関数をlambda式で指定したが、特定の属性を返す関数は、operatorモジュールのattrgetter関数を使えば簡単に作成できる。

>>> import operator
>>> l2 = [Hoge(2, 'c'), Hoge(3, 'a'), Hoge(1, 'b')]
>>> sorted(l2, key=operator.attrgetter('name'))
[3:a, 1:b, 2:c]

with文で使えるコンテキストマネージャー型を作成する

openの時のようにwith文を使えるようにするには、contextlibモジュールのcontextmanagerデコレータを使えば簡単に実装できます。

openの時の例

with open('hoge.txt') as f:
    f.read()
# withブロックを抜けるとファイルclose

contextmanagerを使用した例

from contextlib import contextmanager
@contextmanager
def myfunc():
    print('前処理')
    yiled  #ここで処理が中断
    print('後処理')

# with文で使ってみる。
with myfunc():
    print(1+2)

# 前処理
# 3
# 後処理

abcモジュールで抽象クラス、抽象メソッドを作成する

Pythonは動的型づけというゆるい感じの片付けなので、サブクラスにメソッド実装を強制するような作りが(言語仕様としては)できないが、abcモジュールを使うことで抽象クラス・抽象メソッドを作成することができる。

抽象クラスは、metaclassにabc.ABCMetaを指定して作成し、抽象メソッドには@abstractmethodデコレータを指定する。

from abc import ABCMeta, abstractmethod

class Shape(metaclass=ABCMeta):
    @abstractmethod
    def area(self):
        pass

具象クラスは、普通にスーパークラスに指定して作成する。

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return pi * self.r ** 2

c = Circle(10)
c.area()

areaメソッドは、抽象メソッドになっているので実装しない場合には、インスタンス生成時にTypeErrorが発生する。

TypeError: Can't instantiate abstract class Circle with abstract methods area

エラーが発生するのは、インスタンス生成時でclassを定義した時点では発生しないので注意が必要。

29.7. abc — 抽象基底クラス — Python 3.4.3 ドキュメント

argparseでコマンドライン引数の解析

argparseは、コマンドラインプログラムの引数を解析するのに便利なモジュールで、C言語のgetopt的なものです。似たような機能をもつモジュールとしてgetoptモジュール(C言語のものと同等)、optparseモジュールがありますがそれらは古いモジュールで、argparseを使うのが推奨されてるようです。

基本的な使い方

import argparse
p = argparse.ArgumentParser()
p.add_argument("foo") # 位置引数fooを定義
args = p.parse_args()   # デフォルトでsys.argvを解析
print(args.foo)

このように作成したtest.pyファイルを、引数なしで実行してみます。

$ python test.py
usage: test.py [-h] foo
test.py: error: too few arguments

エラーが発生して、簡易的なヘルプメッセージが表示されました。

引数を指定することで解析は成功し、args.fooから引数の値を参照することができます。

$ python test.py hello
hello

オプション引数

引数に、-fや–fooのようにハイフンで始まるようにするとオプション引数(位置で決まらない引数)になる。

p = argparse.ArgumentParser()
p.add_argument('-f', '--foo')    #短い引数-f 長い引数--fooを定義
args = p.parse_args(['--foo', 'hello'])
print(args.foo)    # hello

よく使う使い方

短い引数、長い引数、ヘルプあたりを指定するのが楽。

p.add_argument("-b", "--bar", help="bar argument")

type指定, choice指定

p.add_argument('-n',  type=int)   #整数のみ
p.add_argument('-f', type=open) #ファイル
p.add_argument('-a',  type=int, choices=[1,2,3])   #整数1,2,3のどれか

default指定

引数が指定されたなかった時のデフォルト値を定義。

p.add_argument('-n', default=1)
args = p.parse_args([])
print(args.n)   # 1

const指定

フラグの指定のみで固定値が引数に設定される。

p.add_argument("-n", const=9, nargs="?")  #nargsは引数の数が0 or 1

args = p.parse_args(["-n"])  # 0の時はconstが使用される
print(args.n) #9

args = p.parse_args(["-n", "1"])
print(args.n) # 1

action指定

action指定では、解析した引数をどのように格納するかを指定できる。
store_trueアクションは、フラグが指定されたことでTrueが設定されるアクション。

p.add_argument("-y", action="store_true")
print(p.parse_args(["-y"]))  #True
print(p.parse_args([]))    # False

-hを置き換えながら–helpはヘルプとして利用したい場合

-h, –helpはデフォルトでヘルプ機能になっているが、これが邪魔な場合は、add_help=Falseで作成すると良い。

邪魔で消したものの、他のオプション引数でヘルプを使いたい場合は、action=”help”で追加すれば良い。

-hは消して、–helpだけでヘルプを使う場合は、add_help=Falseで追加しないようにしてから、あらためて–helpをaction=”help”で追加する。

p = argparse.ArgumentParser(add_help=False)
p.add_argument("--help", action="help")
p.add_argument("-h", "--host", help="some hostname")

16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.4.3 ドキュメント