(初/中級者向け) Python開発ですぐに役立ちそうなTIPS10選
roadmap.shのPython Developerを眺めながら、
復習がてら色々調べていたのですが、せっかくなので使えそうなモノを10個ピックアップしました!
※ 分野や紹介するTIPSの粒度が整理されてないです。筆者の思いつきで書いてますので注意を
※ 下のスクラップから抜粋してます
1.pytest実行時のimportエラーの回避策
1.1と1.2セットでやることで効果を発揮します!
1.1 ディレクトリ構成のトップレベルで"src tests"を使い分ける
ツリー階層で表すと以下のような形です。
project
src
__init__.py
module.py
tests
test_module.py
ポイント
- srcディレクトリをtestsと同階層に設けることで相対importせずに
from src import ・・・
ができるようになります。 - src直下に
__init__.py
を置くことでpackageとして認識させるようにしています。 - testsディレクトリに
__init__.py
を置かないようにしています。- テストコードは通常、単体で実行するためにディレクトリで構成されており、パッケージとして扱われる必要はないと考えます
- テストディレクトリ内に別のモジュールを定義した場合、テストが実行される前にそのモジュールが読み込まれ、テストの結果が誤ってしまう可能性があります
1.2 pytestではなくpython -m pytestで呼び出す
python -m pytest
について
ポイント
- python -m pytestで呼び出すと、カレントディレクトリはをsys.pathに追加してくれます
- 上記の例ではsrc/をカレントディレクトリの配下に置くことでimportを行うことができます
これについてはpytestのGood Integration Practicesにて言及されています。
If you don’t have a setup.py file and are relying on the fact that Python by default puts the current directory in sys.> path to import your package, you can execute python -m pytest to execute the tests against the local copy directly, > without using pip.
setup.py ファイルがなく、Python がデフォルトでカレントディレクトリを sys.path に置いてパッケージをインポートすることに依存している場合、> python -m pytest を実行すると pip を使わずに直接ローカルコピーに対してテストを実行することができます。
他にもInvoking pytest versus python -m pytestをみると理解が深まります。
そもそもsys.pathとは
sys.pathは、Pythonがモジュールを検索するために使用するディレクトリのリストを保持する変数です。
Pythonがモジュールをインポートするとき、指定されたモジュール名を検索し、見つけた場合はその場所からモジュールを読み込みます。
余談ですが、python3.8
が標準ライブラリ, python3.8/site-packages
がサードパーティライブラリのパスです。こうしたパスがあるためこれらのライブラリが使えます。
実践してみたい方向け
こちらにお試し用の環境を作成しました
参考
2. breakpoint
ソースコードの任意の行にbreakpoint()と書きプログラム実行すると、その行で一旦処理をストップして対話モードに入ります。
これに関しては以下の記事がまとまっているので参照すると良いです
3.all/ any
all(iterable)
...全てのiterableなオブジェクトの中身がTrueかどうか
any(iterable)
...一つでもiterableなオブジェクトの中身がTrueかどうか
not any(iterable)
... 全てのiterableなオブジェクトの中身がFalseかどうか
こちらはリスト内包表記を応用して任意の条件をallやanyすることができます
li = [0, 1, 2, 3, 4]
print(all([i > 2 for i in li]))
# False
print(any([i > 2 for i in li]))
# True
もちろん、iterableであればいいのでジェネレータで同様なことができます。
(ジェネレータはリストと異なり逐次処理されるので、処理時間やメモリ使用量を抑えられるというメリットがあります)
print(type(i > 2 for i in l))
# <class 'generator'>
print(all(i > 2 for i in l))
# False
print(any(i > 2 for i in l))
# True
4.itertools.group_by
itertools.groupby(iterable, key=None)
はiterableの連続した要素をグループ化します。
iterableは、事前にkey関数でソートされている必要があります。
import itertools
from datetime import datetime
data = [
{"name": "Alice", "birthday": "2000/11/22"},
{"name": "Bob", "birthday": "1999/2/15"},
{"name": "Carol", "birthday": "1999/1/25"},
{"name": "Dave", "birthday": "2000/4/21"}
]
# ソートを事前に行う
data.sort(key=lambda x: x['birthday'])
print(data)
for key, group in itertools.groupby(data, key=lambda x: datetime.strptime(x['birthday'], "%Y/%m/%d").year):
print(key, list(group))
5.itertools.starmap
聞き馴染みのあるmap関数は、iterable(リスト、タプルなど)に適用するために使用される関数です。
itertools.starmap
は各要素がタプルであるiterableを対象とし、関数にタプルの要素を展開して引数として渡します。
import itertools
def multiply(x, y):
return x * y
pairs = [(1, 2), (3, 4), (5, 6)]
multiplied_numbers = itertools.starmap(multiply, pairs)
# 結果をリストに変換して表示
print(list(multiplied_numbers)) # 出力: [2, 12, 30]
別でmultiprocessのstarmapとかもあるとのこと。
6.lambdaのあれこれ
リスト内包表記の利用
li = [lambda arg=x: arg * 10 for x in range(1, 5)]
if-elseの利用
max_func = lambda a, b : a if(a > b) else b
print(max_func(1, 2))
複数行でlambdaの利用(x, fという形でlambdaに持たせる)
li = [[2,6,4],[1, 4, 8, 64],[3, 34, 9, 17]]
# Sort each sublist
sort_list = lambda x: (sorted(i) for i in x)
# Get the second largest element
fetch_second_largest = lambda x, f : [y[len(y)-2] for y in f(x)]
result = fetch_second_largest(li, sort_list)
print(result)
7.functools.reduce
reduce(function, iterable, initializer)
は要素同士全てに対してを加算したり減算したりできます。
from functools import reduce
from operator import add
from operator import sub
from operator import mul
from operator import and_
from operator import or_
array = [100, 10, 20, 30, 40, 50]
#add,sub,sub
print(reduce(add, array))
print(reduce(sub, array))
print(reduce(mul, array))
# 以下と同等
print(reduce(lambda a, b: a+b, array))
print(reduce(lambda a, b: a-b, array))
print(reduce(lambda a, b: a*b, array))
# or_
print(reduce(or_, ({1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}))) # {1, 2, 3, 4}
# nd_
print(reduce(and_, ({1}, {1, 2}, {1, 2, 3}, {1, 2, 3, 4}))) # {1}
Python Tips: functools.reduce() を活用したいの以下の内容がしっくりきました。
これは受け売りですが、 reduce() については、要は「 sum() ・ all() ・ any() 等の「シーケンスからひとつの値を生成する」タイプの処理を抽象> 化したものが reduce() 」という捉え方ができます。もし OOP のクラスと同じような親子関係(継承関係)が関数にもあるなら、 reduce() は sum() > や all() の親関数、と捉えてもよさそうです。
以下ではreduceのパフォーマンスについて言及されています
併せて読みたい記事
reduceの前に内包表記などを検討するなどがいいかもですね
reduce() は、Python3 からは from > functools import reduce をしないと使えなくなりました。これは、reduce() より内包表記やループを使えという意図でしょう。
8.Sorting HOW TO
sorted(iterable, /, *, key=None, reverse=False)
などを用いてソートしたい時にこちらを毎回読むと良いと思います。
例えば以下のようなコードが掲載されています
#----------------------
from operator import itemgetter, attrgetter
student_objects = [
Student('john', 'A', 15),
Student('jane', 'B', 12),
Student('dave', 'B', 10),
]
print(sorted(student_objects, key=attrgetter('age')))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
#----------------------
from operator import itemgetter, attrgetter
student_objects = [
Student('john', 'A', 15),
Student('jane', 'B', 12),
Student('dave', 'B', 10),
]
print(sorted(student_objects, key=attrgetter('age')))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
#----------------------
Student.__lt__ = lambda self, other: self.age < other.age
print(sorted(student_objects))
# [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
9.zipのあれこれ
みんな大好きzip(*iterables, strict=False)
です。
zipの挙動としては短い方のイテラブルが尽きた場合は処理をやめます。
>>> list(zip(range(3), ['fee', 'fi', 'fo', 'fum']))
[(0, 'fee'), (1, 'fi'), (2, 'fo')]
strict=True とすることで、同じ長さのイテラブルであることを保証できます
#-------------------
>>> list(zip(('a', 'b', 'c'), (1, 2, 3), strict=True))
[('a', 1), ('b', 2), ('c', 3)]
#-------------------
>>> for item in zip(range(3), ['fee', 'fi', 'fo', 'fum'], strict=True):
print(item)
(0, 'fee')
(1, 'fi')
(2, 'fo')
Traceback (most recent call last):
...
ValueError: zip() argument 2 is longer than argument 1]
itertools.zip_longest
itertools.zip_longest(*args, fillvalue=None)
こちらを使うことで、短い方のイテラブルが尽きた時に値を埋めることができます。
from itertools import zip_longest
# zip_longestを使ったサンプルコード
for i in zip_longest('ABCD', 'xy', fillvalue='-'):
print(i)
# ('A', 'x')
# ('B', 'y')
# ('C', '-')
# ('D', '-')
10. collections.Counter
リストや文字列の中で要素がどれだけ出現するかを計算する場合、Counterを使用すると簡単かつ効率的に集計ができます。
from collections import Counter
myword = "helloworld!"
mycounter = Counter(myword)
print(mycounter)
# Counter({'l': 3, 'o': 2, '!': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
most_common([n])
最も多い n 要素を、カウントが多いものから少ないものまで順に並べたリストを返します。
n が省略されるか None であれば、 most_common() はカウンタの すべての 要素を返します。
等しいカウントの要素は挿入順に並べられます:
Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
Discussion