Pythonでメソッドチェーンを改行して書く
Pythonのサードパーティライブラリには、pandasやNumPy, Pillow(PIL)のように、メソッドをつなげて順に処理(=メソッドチェーン)できるように設計されているものがある。
メソッドチェーンといっても、メソッドの戻り値からさらにメソッドを呼ぶという処理を繰り返しているだけで、特殊な文法というわけではない。
メソッドチェーンを使うと1行の文字数が長くなりがちだが、括弧などを利用すると適宜改行できる。この記事では、まずpandasを例にメソッドチェーンを改行して書く方法を説明し、その後でNumPyおよびPillow(PIL)の例を紹介する。
なお、Pythonのスタイルガイド(コーディング規約)であるPEP8にはインデントについての項目があるが、メソッドチェーンについて特にこうすべきといった内容はない。
後述のように、より厳格なコードフォーマッターであるBlackでは丸括弧を使って改行するスタイルが推奨されている。
pandasにおけるメソッドチェーン
pandas.DataFrame
, pandas.Series
の多くのメソッドはpandas.DataFrame
, pandas.Series
を返すようになっており、メソッドをつなげて処理していくことが可能。
メソッドチェーンを使わない場合、例えば以下のように書ける。
read_csv()
でファイルを読み込み。
import pandas as pd
df = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
print(df)
# age state point
# name
# Alice 24 NY 64
# Bob 42 CA 92
# Charlie 18 CA 70
# Dave 68 TX 70
# Ellen 24 CA 88
# Frank 30 NY 57
このpandas.DataFrame
に新たな列を追加し、不要な列を削除し、ソートし、先頭3行のみ抽出する。
- 関連記事: pandas.DataFrameに列や行を追加(assign, appendなど)
- 関連記事: pandas.DataFrameの行・列を指定して削除するdrop
- 関連記事: pandas.DataFrame, Seriesをソートするsort_values, sort_index
- 関連記事: pandas.DataFrame, Seriesの先頭・末尾の行を返すheadとtail
df = df.assign(point_ratio=df['point'] / 100)
df = df.drop(columns='state')
df = df.sort_values('age')
df = df.head(3)
print(df)
# age point point_ratio
# name
# Charlie 18 70 0.70
# Alice 24 64 0.64
# Ellen 24 88 0.88
同じ処理をメソッドをつなげて書くと以下のようになる。ここではあえて改行をしていない。
df_mc = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0).assign(point_ratio=df['point'] / 100).drop(columns='state').sort_values('age').head(3)
print(df_mc)
# age point point_ratio
# name
# Charlie 18 70 0.70
# Alice 24 64 0.64
# Ellen 24 88 0.88
メソッドチェーンはシンプルに書けて便利ではあるが、よく理解していないメソッドをいきなり大量につなげると思わぬ結果になってしまうこともある。慣れていない場合は無理せず一つずつメソッドを適用して結果を確認したほうが無難かもしれない。
また、エディタによっては2つ目以降のメソッドで補完が効かないなどのデメリットもある。
括弧内で改行
Pythonでは括弧の中では自由に改行してよいので以下のように書ける。以降、結果はすべて同じなので省略する。
df_mc_break = pd.read_csv(
'data/src/sample_pandas_normal.csv',
index_col=0
).assign(
point_ratio=df['point'] / 100
).drop(
columns='state'
).sort_values(
'age'
).head(
3
)
なお、自由に改行してよいといっても文字列リテラルの中などで改行するとエラーになるので注意。
# df_mc_break = pd.read_csv(
# 'data/src/sample_
# pandas_normal.csv',
# index_col=0
# ).assign(
# point_ratio=df['point'] / 100
# ).drop(
# columns='state'
# ).sort_values(
# 'age'
# ).head(
# 3
# )
# SyntaxError: unterminated string literal (detected at line 2)
もちろん改行しなくてもいいので、文字数が多いところのみ適当に改行するという形でも構わない。
df_mc_break = pd.read_csv(
'data/src/sample_pandas_normal.csv', index_col=0
).assign(
point_ratio=df['point'] / 100
).drop(columns='state').sort_values('age').head(3)
バックスラッシュを使う
Pythonにおいて、バックスラッシュ(\
)は継続文字であり、行末に置くとその後の改行が無視されて行が継続しているとみなされる。
これを利用すると以下のように書ける。
df_mc_backslash = pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0) \
.assign(point_ratio=df['point'] / 100) \
.drop(columns='state') \
.sort_values('age') \
.head(3)
全体を丸括弧で囲み改行
括弧内では自由に改行できるというルールを利用して、全体を丸括弧()
で囲むという方法もある。
df_mc_parens = (
pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
.assign(point_ratio=df['point'] / 100)
.drop(columns='state')
.sort_values('age')
.head(3)
)
好みの問題ではあるが、余分な改行がないのでスッキリと見やすい。バックスラッシュを入力する手間もない。
この場合も改行するしないは自由なので、例えば以下のようにも書ける。
df_mc_parens = (pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
.assign(point_ratio=df['point'] / 100)
.drop(columns='state')
.sort_values('age')
.head(3))
ドット(.
)を行末に置いてもエラーにはならないが、メソッドチェーンであることが分かりにくくなるので避けたほうがよいだろう。
df_mc_parens = (
pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0).
assign(point_ratio=df['point'] / 100).
drop(columns='state').
sort_values('age').
head(3)
)
同じように括弧を利用して長い文字列をコード上で改行して書く方法もある。以下の記事を参照。
- 関連記事: Pythonで長い文字列を複数行に分けて書く
コードフォーマッターBlackの推奨は丸括弧スタイル
冒頭に書いたように、Pythonのスタイルガイド(コーディング規約)であるPEP8にはメソッドチェーンについての推奨はない。
プロジェクトや所属組織のコーディング規約があればそれに従えばよいが、例えば、より厳格なコードフォーマッターであるBlackでは、丸括弧を使ってドットの前で改行するスタイルが推奨されている。
- psf/black: The uncompromising Python code formatter
- The Black code style - Call chains - Black 23.9.1 documentation
df_mc_parens = (
pd.read_csv('data/src/sample_pandas_normal.csv', index_col=0)
.assign(point_ratio=df['point'] / 100)
.drop(columns='state')
.sort_values('age')
.head(3)
)
NumPyにおけるメソッドチェーン
NumPy配列ndarray
のメソッドの中にもndarray
を返すものがある。
メソッドチェーンを使わない例。
- 関連記事: NumPyのarange, linspaceの使い方(連番や等差数列を生成)
- 関連記事: NumPy配列ndarrayの形状を変換するreshapeの使い方と-1の意味
- 関連記事: NumPy配列ndarrayを任意の最小値・最大値に収めるclip
import numpy as np
a = np.arange(12)
a = a.reshape(3, 4)
a = a.clip(2, 9)
print(a)
# [[2 2 2 3]
# [4 5 6 7]
# [8 9 9 9]]
メソッドチェーンを使う例。
a_mc = np.arange(12).reshape(3, 4).clip(2, 9)
print(a_mc)
# [[2 2 2 3]
# [4 5 6 7]
# [8 9 9 9]]
丸括弧で囲んで改行する場合。
a_mc_parens = (
np.arange(12)
.reshape(3, 4)
.clip(2, 9)
)
print(a_mc_parens)
# [[2 2 2 3]
# [4 5 6 7]
# [8 9 9 9]]
なお、NumPyではndarray
のメソッドではなくndarray
を引数とする関数として定義された処理も多い。pandasほどメソッドチェーンで何でもできるというわけでないので注意。
Pillow(PIL)におけるメソッドチェーン
画像処理ライブラリPillow(PIL)では画像をImage
型で表す。Image
のメソッドには処理結果をImage
で返すものがあり、さらにメソッドをつなげていくことが可能。
メソッドチェーンを使わない例。画像ファイルを読み込んで様々な処理を行い、別名で保存している。
from PIL import Image, ImageFilter
im = Image.open('data/src/lena_square.png')
im = im.convert('L')
im = im.rotate(90)
im = im.filter(ImageFilter.GaussianBlur())
im.save('data/temp/lena_square_pillow.jpg', quality=95)
メソッドチェーンを使う例。
Image.open('data/src/lena_square.png').convert('L').rotate(90).filter(ImageFilter.GaussianBlur()).save('data/temp/lena_square_pillow.jpg', quality=95)
丸括弧で囲んで改行する場合。
(
Image.open('data/src/lena_square.png')
.convert('L')
.rotate(90)
.filter(ImageFilter.GaussianBlur())
.save('data/temp/lena_square_pillow.jpg', quality=95)
)
この例のように、読み込みから保存までまとめて行うと戻り値を変数に代入することなく完結するので、全体を丸括弧で囲むとちょっと奇妙に見えるかもしれない。
例えば、コードフォーマッターBlackでは丸括弧を使わない以下のような書き方が提案される。
Image.open('data/src/lena_square.png').convert('L').rotate(90).filter(
ImageFilter.GaussianBlur()
).save('data/temp/lena_square_pillow.jpg', quality=95)