RにはないPythonの文法・テクニック (演算子/for文/条件分岐 編)

学生時代からデータ解析にはRをメインで使ってきました。Pythonについてはデータ構造クラス/インスタンスなど最低限知っておくべきPythonの知識は学んだものの,細かい文法・テクニックなどについて詳しくなく,困ったときにはRのノリ + ググった付け焼き刃で乗り越えてきました。最近仕事でPythonを使う機会が段々多くなり体系的に学ぶ必要があると感じ,『Python 実践入門』と『Python言語によるプログラミングイントロダクション*1など読み進めています。

本を読み進める中で「こんなのRにはないぞ」「これは知らん」というポイントがいくつもありました。 そこで今回はPython documentationを参照しながらまとめていきます。今回は条件分岐,for文,演算子についてです。 私のようにRをメインで使っていてたまにPythonを使う,という人には役に立つかもしれません。(Pythonのヘビーユーザーの方はそっと閉じてもらって大丈夫です)

withステートメント (with文)

Python documentのPEP 343: "with" ステートメントに以下の記述があります。

'with' ステートメントは、以前なら後片付けが実行されるのを確実にするために try...finally ブロックを使ったであろうようなコードを、より単純明快にします。(途中省略)'with' ステートメントは基本構造が以下となる制御フロー構造です:

with expression [as variable]:
    with-block

これでは少し分かりづらいので,with文が最も頻繁に用いられるファイルの読み書きの文脈で説明します。

Pythonでファイルを読み書きするときに用いられる組み込み関数open(filename, mode)はファイルオブジェクトと呼ばれる返り値を出力します。このファイルオブジェクトはopen()で一度開かれると,closeメソッドで明示的に閉じるまで開きっぱなしになります。この開かれたファイルオブジェクトを閉め忘れないために用いるのがwith文です。

# with文なしでの書き方
f = open('example.txt', 'r') # 読み込みモードでtxtを開く (ファイルオブジェクトが開かれる)
example = f.read() # txtの中身をexampleに保管
f.close() # ファイルオブジェクトを閉じる

# with文ありでの書き方
with open('example.txt', 'r') as f:
    example = f.read() # with文の終了時に自動でファイルオブジェクトが閉じられる。

Rではread.csvなどでファイルを読み込むと,同時に閉じる動作も行われるので,with文に対応するものが必要ありません。

(訂正: 2020年5月26日) Rガチ勢の@yutannihilationさんがwith文に対応するものRにもあるよとツイートされています。

セイウチ演算子 := (walrus operator)

Python documentのWhat’s New In Python 3.8というページにセイウチ演算子の記述があります。

There is new syntax := that assigns values to variables as part of a larger expression. It is affectionately known as “the walrus operator” due to its resemblance to the eyes and tusks of a walrus.

つまり,変数の作成と変数の使用を同時に行える演算子です。「:=」がセイウチの目と牙に似ているからこんな名前だそうです(全然わからん)。簡単な使い方はこんな感じ。

# 従来の書き方
>>> a = 10 # 変数の作成
>>> b = a/2 # 変数の使用
>>> a, b
...
(10, 5.0)

# セイウチ演算子
>>> b = (a:=10) / 2
>>> a, b
...
(10, 5.0)

Python3.8から導入された新しい機能なので,まだ慣れてる人は少なそう。 Rでは変数を作る際に代入演算子<-が用いられますが,こちらはセイウチ演算子と同じように使えます。

> a <- (b<-10)/2
> a
[1] 5
> b
[1] 10

空のシーケンスやコレクションが真理値の偽になる

Python documentationの組み込み型・真理値判定のページに以下の記載があります。

オブジェクトは、デフォルトでは真と判定されます。ただしそのクラスが __bool__() メソッドを定義していて、それが False を返す場合、または __len__() メソッドを定義していて、それが 0 を返す場合は偽と判定されます。 主な組み込みオブジェクトで偽と判定されるものを次に示します:

・偽であると定義されている定数: None と False

・数値型におけるゼロ: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)

・空のシーケンスまたはコレクション: '', (), [], {}, set(), range(0)

「空のシーケンス」が条件式に与えられた場合には偽,「空ではないシーケンス」が与えられた場合は真となるので,この特徴を利用してコーディングするのがPythonicな書き方のようです。

>>> def print_char(char):
...    if char:
...        return char
...    else:
...        print('No character')
...        return None
>>> print_char('apple') 
'apple'
>>> print_char('') 
No character

条件分岐での比較演算子の連結

Python document 6.10. 比較に以下の記述があります。

比較はいくらでも連鎖することができます。例えば x < y <= z は x < y and y <= z と等価になります。

>>> x,y,z = 1,2,3
>>> x < z > y
True

Rの場合,andを使った記法のみ許されており,連結の記法ではエラーが出てしまいます。

iterableなオブジェクト (反復可能なオブジェクト)

Python documentの用語集によると,

(反復可能オブジェクト) 要素を一度に 1 つずつ返せるオブジェクトです。 反復可能オブジェクトの例には、(list, str, tuple といった) 全てのシーケンス型や、 dict や ファイルオブジェクト といった幾つかの非シーケンス型、 あるいは Sequence 意味論を実装した iter() メソッドか getitem() メソッドを持つ任意のクラスのインスタンスが含まれます。

簡単に言うとiterableなオブジェクトとは「for文で回せる対象となるオブジェクト」を指します。基本的に押さえておくべきiterableなオブジェクトはリスト,タプル,文字列,辞書,です。文字列が反復可能で1文字ずつiterationできる点が,Rと大きく異なる点だと思います。 ちなみにRのfor文で回せるのは,c("apple", "orange", "lemon")のようなベクトルやリストだけです。

for文のenumerate

PEP 279:enumerateの記述がこちらです。

新たな組み込み関数 enumerate() はある種のループ処理を少し簡潔にするものです。 thing がイテレータかシーケンスだとして、 enumerate(thing) は (0, thing[0]), (1, thing[1]), (2, thing[2]), … を生成するイテレータを返します。

つまりfor文の中でenumerate()を使うことでiterableなオブジェクトの要素のiterationに加え,iterationの回数も回すことができます。

>>> fruit = ['apple', 'orange', 'lemon']
>>> for count, item in enumerate(fruit):
...    print(f'{count}th fruit is {item}')
...
0th fruit is apple
1th fruit is orange
2th fruit is lemon

私の知る限り,このenumerateに対応する関数はRにはありません。便利ですね。

変数を利用しないfor文

for文で回している対象(変数)を利用せずに繰り返しを行う場合,変数をiやkなどでなくアンダースコアで表現する慣習があるようです。Rにこの慣習はありません。

>>> for _ in range(3):
...    print('I love you')
...
I love you
I love you
I love you

終わりに

以上です。次回は同様の手順で関数定義やデータ構造について「RにはないPythonの文法」をまとめたいと思います。

訂正: 2020年5月26日

Rガチ勢の@igjitさんから本記事に対して「実はこれはRでもできるよ」というツッコミ記事を頂きました。 enumerateはpurrr::imapで書けるそうです。

igjit.github.io

*1:こちらの本はPython3でなくPython2に対応しているので,読む際には注意が必要