Pythonでメソッドチェーンを改行して書く

Modified: | Tags: Python, pandas, NumPy, Pillow

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行のみ抽出する。

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)
)

同じように括弧を利用して長い文字列をコード上で改行して書く方法もある。以下の記事を参照。

コードフォーマッターBlackの推奨は丸括弧スタイル

冒頭に書いたように、Pythonのスタイルガイド(コーディング規約)であるPEP8にはメソッドチェーンについての推奨はない。

プロジェクトや所属組織のコーディング規約があればそれに従えばよいが、例えば、より厳格なコードフォーマッターであるBlackでは、丸括弧を使ってドットの前で改行するスタイルが推奨されている。

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を返すものがある。

メソッドチェーンを使わない例。

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)

関連カテゴリー

関連記事