日々のプログラミングに役立つPythonのテクニックや小ネタ、小技をまとめました。
Python 3.8.0
で動作確認しています。
3値以上の比較
1 == 2 == 3 # -> False
1 < 2 < 3 < 4 # -> True
時間(datetime/date)の比較
マジックメソッドのおかげで柔軟な比較が出来ます。
参考:[Python] 独自クラスで比較演算ができるようにする
from datetime import date
feb1 = date(2020, 2, 1)
feb2 = date(2020, 2, 2)
feb3 = date(2020, 2, 3)
feb1 < feb2 <= feb2 < feb3 # -> True
時間の最大/最小
from datetime import date
# 意図的に逆順にしてます
dates = [
date(2020, 2, 3),
date(2020, 2, 2),
date(2020, 2, 1),
]
min(dates) # -> datetime.date(2020, 2, 1)
max(dates) # -> datetime.date(2020, 2, 3)
時間の計算
# Input
from datetime import datetime
start = datetime(2020, 2, 1, 10)
goal = datetime(2020, 2, 3, 12)
t = goal - start
print(f'あなたの記録は{t.days}日と{t.seconds}秒です')
# Output
'あなたの記録は2日と7200秒です'
時間を連想配列のキーにする
日時ベースの集計をする時に便利です。
# Input
from datetime import date
counts = {
date(2020, 2, 1): 0,
date(2020, 3, 1): 0,
}
counts[date(2020, 2, 1)] += 1
counts[date(2020, 2, 1)] += 1
counts[date(2020, 3, 1)] += 1
print(counts)
# Output
{datetime.date(2020, 2, 1): 2, datetime.date(2020, 3, 1): 1}
キーが連想配列に含まれるか
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print('foo' in d) # -> True
連想配列からキーを抽出
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print(list(d)) # -> ['foo', 'bar', 'baz']
連想配列から値を抽出
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
print(list(d.values())) # -> [1, 2, 3]
連想配列からキーと値のペアを抽出
d = {
'foo': 1,
'bar': 2,
'baz': 3,
}
for key, value in d.items():
print(key, value)
# Output
foo 1
bar 2
baz 3
2重配列を連想配列へ変換
# Input
l = [
['Yamada', 'baseball'],
['Tanaka', 'soccer'],
['Sato', 'tennis'],
]
dict(l)
# Output
{'Yamada': 'baseball', 'Tanaka': 'soccer', 'Sato': 'tennis'}
多重配列のループ
# Input
rows = [
['yamada', 20],
['tanala', 18],
['sato', 18],
]
for name, age in rows:
print(f'{name}さんは{age}歳です')
else:
print('紹介終わり')
# Output
'yamadaさんは20歳です'
'tanalaさんは18歳です'
'satoさんは18歳です'
'紹介終わり'
多重配列から必要な要素だけ取り出しつつループ
# Input
l = [
['Yamada', 'Taro', 20, 'baseball'],
['Tanaka', 'Jiro', 18, 'circle'],
]
# 先頭を取り出す
for last_name, *others in l:
print(last_name, others)
print()
# 末尾を取り出す
for *others, circle in l:
print(circle, others)
print()
# 最初の2要素を取り出す
# (他の要素が要らない場合はダブルアンダースコアを指定するのがPython流)
for last_name, first_name, *__ in l:
print(last_name, first_name)
# Output
Yamada ['Taro', 20, 'baseball']
Tanaka ['Jiro', 18, 'circle']
baseball ['Yamada', 'Taro', 20]
circle ['Tanaka', 'Jiro', 18]
Yamada Taro
Tanaka Jiro
このような事もできます。
# Input
l = [
['a', 'b', 'c', ['d', 'e', 'f']],
]
for one, *__, (*__, two) in l:
print(one, two)
# Output
a f
カウンター付きループ
# Input
rows = [
['Yamada', 20],
['Tanaka', 18],
['Sato', 16],
]
for i, (name, age) in enumerate(rows, start=1):
print(f'{i}行目 : 氏名={name}, 年齢={age}')
# Output
'1行目 : 氏名=Yamada, 年齢=20'
'2行目 : 氏名=Tanaka, 年齢=18'
'3行目 : 氏名=Sato, 年齢=16'
連想配列のキー無しエラーを防ぐ
いちいちif 'キー' in dict
と書く必要が無くなります。
d = {
'Yamada': 20,
'Tanaka': 18
}
d.get('Yamada') # -> 20
d.get('Sato') # -> None
d.get('Sato', '年齢なし') # -> 年齢なし
配列/連想配列を展開して関数へ渡す
# Input
def func(a, b, c=None, d=None):
print(a)
print(b)
print(c)
print(d)
l = ['aaa', 'bbb']
d = {'c': 'ccc', 'd': 'ddd'}
func(*l, **d)
# Output
aaa
bbb
ccc
ddd
all関数
配列の全要素がTrueと評価出来るならTrueを返してくれます。
l = [
True,
1,
"foo",
]
all(l) # -> True
l = [
True,
1,
"",
]
all(l) # -> False
any関数
配列の要素の内どれか1つでもTrueと評価出来るならTrueを返してくれます。
l = [
False,
0,
"foo",
]
any(l) # -> True
l = [
False,
0,
"",
]
any(l) # -> False
any関数の注意点
以下の例では計算コストが高い処理(heavy_task
)が無駄に3回も実行されています。
# Input
def heavy_task(ret):
print(ret)
return ret
if any([heavy_task(True), heavy_task(False), heavy_task(False)]):
print('Complete')
# Output
True
False
False
Complete
この場合はifを使ったほうが効率的です。
# Input
def heavy_task(ret):
print(ret)
return ret
if heavy_task(True) or heavy_task(False) or heavy_task(False):
print('Complete')
# Output
True
Complete
集合演算(Set型)
重複要素を削除する
# Input
l = ['yamada', 'tanaka', 'yamada', 'sato']
print(set(l))
# Output
{'sato', 'tanaka', 'yamada'}
重複要素を抜き出す(積集合)
# Input
l1 = ['yamada', 'tanaka', 'yamada', 'sato']
l2 = ['tanaka']
print(set(l1) & set(l2))
# Output
{'tanaka'}
結合した上で重複を削除する(和集合)
# Input
l1 = ['yamada', 'tanaka']
l2 = ['yamada', 'sato']
print(set(l1) | set(l2))
# Output
{'sato', 'tanaka', 'yamada'}
他方から自身に含まれる要素を削除する(差集合)
# Input
l1 = ['yamada', 'tanaka']
l2 = ['tanaka']
print(set(l1) - set(l2))
# Output
{'yamada'}
collections / itertools パッケージ
「このループ処理、愚直に書けば出来るけど面倒くさい・・・」という時に便利なのがcollections
とitertools
パッケージです。
collections.Counter
要素の出現回数をカウントし、多い順にソートしてくれます。
# Input
import collections
l = ['a', 'b', 'c', 'a', 'a', 'c']
c = collections.Counter(l)
print(c.most_common())
# Output
[('a', 3), ('c', 2), ('b', 1)]
collections.defaultdict
連想配列に対し、キーの有無に関係なく処理を実行出来るようになります。
何を言っているか分からないと思いますのでコード例をご確認ください。
# Input
import json
import collections
# defaultdict()の引数には関数(callable)ならなんでも渡せる
groups = collections.defaultdict(list)
# 普通の連想配列だと "baseballというキーは存在しない" といったエラーが発生する
groups['baseball'].append('yamada')
groups['tennis'].append('tanaka')
groups['baseball'].append('sato')
print(json.dumps(groups))
# Output
{"baseball": ["yamada", "sato"], "tennis": ["tanaka"]}
defaultdictが特に便利だと感じるのは何らかの集計処理を作成する時です。
いちいちキーの存在を確認しなくて良いため処理がシンプルです。
# Input
import collections
products = ['ノート', 'えんぴつ', 'けしごむ']
sales = [
['ノート', 1],
['けしごむ', 2],
['けしごむ', 1],
]
aggregates = collections.defaultdict(int)
for product, cnt in sales:
aggregates[product] += cnt
print('--- 本日の販売実績 ---')
for product in products:
print('%s : %d個' % (product, aggregates[product]))
# Output
--- 本日の販売実績 ---
ノート : 1個
えんぴつ : 0個
けしごむ : 3個
関数(callable)ならなんでも渡せる
という特性を利用すると、多重連想配列もシンプルに実装できます。
# Input
import json
from collections import defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested['a']['a'] += 1
nested['a']['a'] += 1
nested['a']['b'] += 1
nested['b']['c'] += 1
print(json.dumps(nested))
# Output
{"a": {"a": 2, "b": 1}, "b": {"c": 1}}
itertools.product
2個以上の配列から「組み合わせ」を網羅的に生成してくれます。
# Input
import itertools
a = ['a1', 'a2']
b = ['b1', 'b2', 'b3']
c = ['c1']
list(itertools.product(a, b, c))
# Output
[('a1', 'b1', 'c1'),
('a1', 'b2', 'c1'),
('a1', 'b3', 'c1'),
('a2', 'b1', 'c1'),
('a2', 'b2', 'c1'),
('a2', 'b3', 'c1')]
itertools.chain.from_iterable
2次元配列を1次元配列に変換してくれます(flatten)。
# Input
import itertools
l = [
['a1', 'a2'],
['b1', 'b2', 'b3'],
]
list(itertools.chain.from_iterable(l))
# Output
['a1', 'a2', 'b1', 'b2', 'b3']
ioパッケージ
ファイルっぽく扱える仮想オブジェクトを提供してくれます。
テストを書く時に重宝します。
# Input
import io
def writer(f, text):
f.write(text)
def printer(f):
print(f.read())
sio = io.StringIO()
writer(sio, 'foo\n')
writer(sio, 'bar\n')
writer(sio, 'baz\n')
sio.seek(0)
printer(sio)
# Output
foo
bar
baz
タプル(tuple)
タプル(tuple)
は配列と似ていますが、不変(immutable)であるため値の再代入が行えません。
不変であるが故に、連想配列のキーとして利用できます。
yamada = ('Yamada', 'Taro') # これがタプル
tanaka = ('Tanaka', 'Jiro') # これもタプル
print(yamada[0]) # -> Yamada
yamada[0] = 'Tanaka' # TypeError: 'tuple' object does not support item assignment
ages = {
yamada: 20,
tanaka: 18,
}
print(ages[tanaka]) # -> 18
この特性を利用すると、類似データのグルーピングを効率的に行なえます。
# Input
from collections import defaultdict
data = [
{'circle': 'baseball', 'name': 'yamada', 'age': 10},
{'circle': 'baseball', 'name': 'sato', 'age': 10},
{'circle': 'baseball', 'name': 'suzuki', 'age': 11},
{'circle': 'tennis', 'name': 'tanaka', 'age': 10},
]
per_circle_age = defaultdict(list)
for v in data:
k = (v['circle'], v['age']) # (サークル名, 年齢) というタプルを生成
per_circle_age[k].append(v['name']) # タプルをキーにして集計
for (circle, age), members in per_circle_age.items():
print(f'{circle}に所属している{age}歳のメンバー:{members}')
# Output
"baseballに所属している10歳のメンバー:['yamada', 'sato']"
"baseballに所属している11歳のメンバー:['suzuki']"
"tennisに所属している10歳のメンバー:['tanaka']"
dataclassesパッケージ
Python3.7から使える機能です。
コンストラクタにちまちまとself.name = name
の様な代入処理を書かなくて良いので便利です。
import dataclasses
# frozen=True とすると不変(immutable)なオブジェクトとして扱える
@dataclasses.dataclass(frozen=True)
class User:
last_name: str
first_name: str
def full_name(self):
return f'{self.last_name} {self.first_name}'
yamada = User(last_name='Yamada', first_name='Taro')
tanaka = User(last_name='Tanaka', first_name='Jiro')
yamada.full_name() # -> Yamada Taro
# 簡単に連想配列へ変換出来ます
dataclasses.asdict(yamada) # -> {'last_name': 'Yamada', 'first_name': 'Taro'}
# 比較が可能です
yamada2 = User(last_name='Yamada', first_name='Taro')
yamada == yamada2 # -> True
yamada == tanaka # -> False
yamada in [yamada2] # -> True
# "frozen=True" とした場合は値の再代入は出来ません
yamada.last_name = 'Sato' # -> FrozenInstanceError: cannot assign to field 'last_name'
# immutableなので連想配列のキーとして使えます
d = {yamada: 'foo', tanaka: 'bar'}
# 集合演算(Set型)も可能です
{yamada, tanaka} & {yamada2} # -> {User(last_name='Yamada', first_name='Taro')}