hack のためのネタ帳, etc,,,

×

やりたいこと

Python の if 文等で、条件式の結果を変数に代入をしたい

解決方法

"=" ではなく ":=" を使う。
ただし代入できるのは条件式に限られ、変数は一つのみであり、代入できるのも条件式全体に限られる。
通常の式の途中では代入出来ない。

あと、Python2 だと無理っぽい。

状況

他の言語のノリで以下のようなコードを書いたところ
#!/usr/bin/env python3
import urllib.request
import re

url = 'https://pypi.org/simple/'
with urllib.request.urlopen(url) as response:
    html = response.read().decode('utf-8')
if m = re.search(r'(?s)<body>(.*)</body>', html):
    print(m.group(1))
以下のようなエラーになった
$ ./hoge.py 
  File "./hoge.py", line 8
    if m = re.search(r'(?s)<body>(.*)</body>', html):
         ^
SyntaxError: invalid syntax
Visual Studio Code でも = と r の間のスペースに下線が表示されて、
Invalid Syntax (<unknown>, line 8) pylint(syntax-error) Peek Problem (Alt+F8) Noquick fixes available
みたいに言われてて、しばらく「は?」ってなった。

状況的に見て Python では if の条件式に代入を書けないって事のようで、
m = re.search(r'(?s)<body>(.*)</body>', html)
if m:
    print(m.group(1))
みたいに分けて書けってことのようである。

C 畑の言語にどっぷり漬かり切った頭には、ある意味カルチャーショックと言うか、
#include<stdio.h>
#include<stdlib.h>

int main() {
  for (int i = 0;; i++) {
    int fizz = i % 3;
    int buzz = i % 5;
    if (fizz = 0) printf("fizz");
    if (buzz = 0) printf("buzz");
    if (fizz && buzz) printf("%d", i);
    printf("\n");
  }
  return EXIT_SUCCESS;
}
みたいに、未だについやってしまいがちな間抜けなバグを防げるのでフールプループ的な意味では悪くない仕様ですよねと、それはそれで納得はしたので、Python ってそういう仕様なんだーと、そこで終わりかかったのだが、
いやいや、待ってください。if, else if, ... で条件に正規表現使いたい場合、条件式で代入書けないのは地味に辛くないですか?的な疑問が。
Python ってそんな不便な言語なの?

gawk の match(s, r [, a]) みたいに、マッチ結果を配列 a に返すみたいな仕組みがあれば良さそうな気がするが、 なので、そういう仕組みはなさそうだし、どうすんの?これ?

公式ドキュメントにおける言及について

これ、割と他の言語では出来る書き方なので、公式文書のどこかに注記がありそうに思うのだが、それらしいところが見つからない。
とりあえず、 の例示では、特に言及せず
match = re.search(pattern, string)
if match:
    process(match)
みたいに分けて書いてあるし、
  • Python チュートリアル / 3. 形式ばらない Python の紹介 # 3.1. Python を電卓として使う # 3.1.1. 数
には、
等号 (=) は変数に値を代入するときに使います。代入を行っても、結果は出力されず、次の入力プロンプトが表示されます。:
みたいな記述があり、対話モードで演算は結果が表示され、代入は結果が表示されない例が示されているものの、
こちらにも特に注記等はないし、
  • Python チュートリアル / 4. その他の制御フローツール # 4.1. if 文
でも、条件式に代入の構文が使えない点については特に触れられてないる様子がない。

で、 を見ると、ようやく求めていた答えが。

まず、 には "=" は含まれておらず、これは であった。

次に、"=" による代入は
assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
みたいな BNF で定義されていて、例えば
a = b = 1
みたいな一括代入は、前半の (target_list "=")+ の1回以上の繰り返しで実現されているので、右辺の starred_expression や yield_expression には含まれていないのであった。

ただ、先程の演算子に := とか言う見慣れない演算子があって、これって Pascal の代入っぽくね?みたいな気がしたので試しに REPL に
a := 1
とか入れてみたら、
>>> a := 1
  File "<stdin>", line 1
    a := 1
      ^
SyntaxError: invalid syntax
のようなエラーになった。

何だこれ?って感じだけど、これは なんだそうで、
assignment_expression ::=  [identifier ":="] expression
と定義されてるらしい。

で、ここに、
if matching := pattern.search(data):
    do_something(matching)
while chunk := file.read(9000):
    process(chunk)
みたいな例文が紹介されていた。

つまり、通常の代入文は "=" で代入するけど、
条件式の中で代入する場合は ":=" を使えということらしい。

残る問題は、代入式は if 文に与えられる式なのに、なんで先程書いた
a := 1
みたいな式はエラーになんの?って話なんだが、
ここに書けるのは、
expression_stmt ::=  starred_expression
であり、これは によれば
starred_expression ::=  expression | (starred_item ",")* [starred_item]
である。
一方、if 文に与えられる条件式は
  • Python 言語リファレンス / 8. 複合文 (compound statement) # 8.1. if 文
より、
if_stmt ::=  "if" assignment_expression ":" suite
             ("elif" assignment_expression ":" suite)*
             ["else" ":" suite]
なのである。
つまり代入式は、まさに最初の疑問として挙げた「代入できないと if, elseif, ... で正規表現使いたい時に困るんじゃね?」に対する、答えのような機能と言っても良さそうだ。
あくまでも条件分岐の処理内で条件式の結果を使いたい場合の解決策的な機能であって、Python としては通常の式の中では代入なんてする必要はないだろって見解なんだろう。

複数条件を課したい場合

条件 x と正規表現両方みたいな条件を課したい場合は、以下のように and 条件の末尾に正規表現を持ってくると良いだろう。
if matching := x and pattern.search(data):
    do_something(matching)
and を取っている場合、最後の式の値が結果になるので、順序を逆にすると結果として x が代入されてしまう。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

フリーエリア

管理人/副管理人のみ編集できます