PyCon Kyushu 2022 Kumamoto
2022/01/22 nikkie
2022/04/17 BNF記法 -> BNF に修正(F(form)自体が「記法」の意のため) #26
if
文
for
文
関数
if
文・ for
文・関数定義が書ける(=入門書は終えた)Python使い(Intermediate)
構文 解析
文の構成要素:式とキーワード
節、ヘッダ、スイートによる文の説明
BNFの読み方
「構文」がめちゃくちゃ面白い✨と思っているので、話します
万人向けではありません(この話を知らなくてもPythonは使っていけます)
Pythonに未入門の方、方向性合わないかもと直感した方、裏のトークに行くなら今!
座長(= 開催に責任 を持つ人)
スタッフは2019年から
PyCon JP 2022 座長決定
— PyCon JP (@pyconjapan) January 14, 2022
来年のPyCon JP 2022の座長が決まりました。
座長: 片寄 里菜(かたよせ りな)
PyCon JP 2022をどのような形にするか 決めにくい状況です。座長のもとで、スタッフや参加者が楽しいイベントが作れることを期待しています。https://t.co/XpaXfOqe8m #pyconjp
2020/08の 100DaysOfContribution 達成しました! が続いています
本題へ: 文 に立ち返ってPython再入門(LT1本くらいの導入です)
if
文の例(roland.py
)name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
ref: https://zenn.dev/kuramapommel/books/introduce_polymorphism/viewer/chapter2
roland.py
実行例$ python roland.py
名前を入力してください: nikkie
俺以外か
$ python roland.py
名前を入力してください: ローランド
俺か
Pythonを書いているとき「私、いま文書いてる」とはあまり思わない
書いているプログラムで意識するのは 行 (例:レビューで 「N行目の...」)
(文は、プログラムの1つ以上の行からできます)
入門時に if
文・ for
文と教わった記憶(例 Pythonチュートリアル "制御フロー")
制御フローは「分岐」や「反復」という 構造 として捉えている
roland.py
)name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か") # nameが"ローランド"のときの分岐
else:
print("俺以外か") # nameが"ローランド"でないときの分岐
どうやら私たちプログラマは文を意識していなさそう
High level / Low level
High level |
Low level |
高水準言語(例:Python) |
機械語 |
人間👩💻👨💻が読み書き |
機械🤖が読み書き |
機械が高水準言語を機械語に 変換 している
つまり、機械は(機械語だけでなく) 高水準言語も読んで いる
そのための 文
参考:『 コンピュータシステムの理論と実装 』
ソースコード(Pythonで書かれた)
バイトコード(インタプリタの内部表現。pycファイルにキャッシュ。 ref: 用語集)
if
文の例でコンパイルを見るname = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
字句解析
抽象構文木
ソースコードは文字列
字句(トークン) =意味を持つ最小単位
2,0-2,2: NAME 'if'
2,3-2,7: NAME 'name'
2,8-2,10: EQEQUAL '=='
2,11-2,18: STRING '"ローランド"'
2,18-2,19: COLON ':'
2,19-2,20: NEWLINE '\n'
3,0-3,4: INDENT ' '
3,4-3,9: NAME 'print'
AST (Abstract Syntax Trees)
機械は、プログラムの構造を 木 で表す
一連のトークンから抽象構文木を出力
If(
test=Compare(
left=Name(id='name', ctx=Load()),
ops=[
Eq()],
comparators=[
Constant(value='ローランド')]),
body=[
Expr(
抽象構文 |
具象構文 |
インタプリタが解釈 |
プログラミング言語の見た目(例:複合文はこう書く) |
Pythonの複合文の現在の 見た目 について深堀ります(Python 3.10.2)
機械は文法に沿って具象構文を読んでます(鍵🗝は「再帰」)
PyCon Kyushu 2022 Kumamotoのテーマ
横方向:文法やライブラリを知ってできること広がる
縦方向:文法やライブラリを掘り下げて深く理解する
(某書レビューでの学びをもとにしています)
横方向:文法やライブラリを知ってできること広がる
縦方向:文法やライブラリを 掘り下げ て深く理解する 👈本トーク
Pythonのコロンやインデントの意味を共有
BNFを読んで一緒に味わう
コロンやインデントの意味: 文の構成要素を機械に伝えている
BNFを読んで一緒に味わう
コロンやインデントの意味:文の構成要素を機械に伝えている
BNFを読んで一緒に味わう: 簡潔かつ抜け漏れのない表現
Pythonにおける文
文を定義する(拡張BNF)
文の定義を味わう
A statement is part of a suite (a “block” of code).
文はスイート (コードの"ブロック") の一部です。(nikkie訳)
複合文には、他の文 (のグループ) が入ります
言語リファレンス 8. 複合文
if
文)name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
複合文は、中に入っている他の文の実行の制御に何らかのやり方で影響を及ぼします。( 8. 複合文)
if
文だと分岐(実行する/しない)
節
ヘッダ
スイート
複合文は、一つ以上の '節 (clause)' からなります。( 8. 複合文)
if
文を構成する節 その1name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
if
文を構成する節 その2name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
節は、ヘッダと 'スイート (suite)' からなります。( 8. 複合文)
各節のヘッダは一意に識別するキーワードで始まり、コロンで終わります。( 8. 複合文)
いくつかの トークン (字句)
例
if
else
for
def
name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
スイートは、[略]、または、ヘッダに続く行で一つ多くインデントされた文の集まりです。( 8. 複合文)
name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
name = input("名前を入力してください: ")
if name == "ローランド":
print("俺")
print("か")
else:
print("俺以外か")
[先に紹介した形式] のスイートに限り、さらに複合文をネストできます( 8. 複合文)
複合文は 再帰 的!(複合文のスイートに別の複合文)
name = input("名前を入力してください: ")
if name == "ローランド":
for _ in range(3):
print("俺か")
else:
print("俺以外か")
複合文は 節 (ヘッダとスイート)からなる
コロンはヘッダを示している!
インデントはスイートを示している!
A statement is either an expression or one of several constructs with a keyword, such as if, while or for.
文は、式または、 if, while, for のようなキーワードから構成されるものです(nikkie訳)
式 (an expression)
キーワードから構成されるもの (one of several constructs with a keyword)
>>> if = 1231 # 変数として使えません
File "<stdin>", line 1
if = 1231
^
SyntaxError: invalid syntax
何かの値と評価される、一まとまりの構文 (A piece of syntax which can be evaluated to some value.)
言い換えると、式とは [中略] 値を返す式の要素の積み重ねです。 (In other words, an expression is an accumulation of expression elements [...] which all return a value.)
式の定義にも 再帰 !
式の要素の1つ、リテラル( 6. 式 6.2.2)
例:整数 108
はリテラル 👉 108
は式
式の要素の1つ、演算子( 6. 式 6.7)
例: 33 - 4
は式(リテラル、演算子、リテラル)
式の要素を組合せて、別の式ができる(再帰)
関数呼び出しも、式の要素の1つ( 6. 式 6.3.4)
例: print("だんだんな〜")
以下の例の関数呼び出しは文でもある(スイートとして書けている)
name = input("名前を入力してください: ")
if name == "ローランド":
print("俺か")
else:
print("俺以外か")
✅ Pythonにおける文
文を定義する(拡張BNF)
文の定義を味わう
字句解析と構文に関する記述では、BNF 文法記法に手を加えたものを使っています。( 1.2. 本マニュアルにおける表記法)
EBNFとも呼ぶドキュメントもありました(Extended Backus–Naur Form)
各規則は name (規則によって定義されているものの名前) と ::= から始まります。( 1.2. 本マニュアルにおける表記法)
python.jp Python 3.9 の with文
Python 3.9 ではPython言語のパーザが置き換えられ
PEP 617 -- New PEG parser for CPython
Parsing Expression Grammar(解析表現文法)
1.2. 本マニュアルにおける表記法 に記載があります。
|
*
+
[]
()
"
『 』(※空白です)
|
複数の選択項目を分かち書きするときに使います
「または」
"a" | "b"
➡ "a"
, "b"
*
直前にくる要素のゼロ個以上の繰り返しを表します
"a"*
➡ ""
, "a"
, "aaaa"
, ...
+
プラス (+) は一個以上の繰り返し
*
に似て、直前に来る要素の一個以上の繰り返し
"a"+
➡ "a"
, "aaaa"
, ...
[]
角括弧 ([ ]) に囲われた字句は、字句がゼロ個か一個出現する(別の言い方をすれば、囲いの中の字句はオプションである)
["a"]
➡ ""
, "a"
()
字句のグループ化には丸括弧を使います。
"
リテラル文字列はクオートで囲われます。
空白はトークンを分割しているときのみ意味を持ちます。
前提「lc_letter は 'a' から 'z' までの何らかの文字一字( "a"..."z"
)」
name ::= lc_letter (lc_letter | "_")*
規則 name
はどんな文字列を表す?
lc_letter | "_"
lc_letter
または "_"
= 'a' から 'z' までの文字一字、または (リテラルの)'_'
例: z
, _
(lc_letter | "_")
丸括弧によるグループ化
'a' から 'z' までの文字一字、または '_' をグループに
(lc_letter | "_")*
*
は0個以上の繰り返し
= グループ (lc_letter | "_")
の0個以上の繰り返し
例: ''
(空文字列), z
, w_
, _prq
name ::= lc_letter (lc_letter | "_")*
lc_letter
の後に、 (lc_letter | "_")*
が続く
= lc_letter
の後に「 lc_letter
またはリテラルの _
」を0個以上の繰り返す
例: a
, b_
, cde
1文字目に _
は来られない
拡張BNFの記号の意味を確認
Pythonの 複合文の定義を読む準備 が整った
✅ Pythonにおける文
✅ 文を定義する(拡張BNF)
文の定義を味わう
8. 複合文 中の定義を読んでみましょう
拡張BNFに沿って読むと、簡潔な行数で、抜け漏れなく説明される 感動がありました!
compound_stmt ::= if_stmt | while_stmt | for_stmt | funcdef | match_stmt | (リファレンスにはまだまだあります)
プログラムの字句解析が終わって、トークンの並びになっています
機械は トークンの並びが規則に当てはまるか をチェックしています
=拡張BNFに沿って書けば文法エラーにはなりません
if
文if 文は、条件分岐を実行するために使われます:
if
文の構文を(EBNFを使わずに)どう伝えますか❔少し考えてみてください
elseの有無
if ...
if ... else ...
elifの個数(0個/1個/複数)
if ... elif ...
if ... elif ... elif ...
if ... elif ... else ...
if ... elif ... elif ... else ...
場合分け戦略だと6つのケースが出てきていた。「これは簡潔!」
3行なのに、抜け漏れがない!
if
文の構文if_stmt ::= "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]
suite
とはsuite ::= stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
「または」で定義
suite
stmt_list NEWLINE
:セミコロンでつなげた文
NEWLINE INDENT statement+ DEDENT
:改行し、インデントして並べた文
if
文 1/3if_stmt ::= "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]
キーワード if
で始まるヘッダと続くスイートからなる節は 必須
if
文 2/3if_stmt ::= "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]
elif
の節は 0回以上 の繰り返し(ない場合もあるし、複数ある場合もある)
if
文 3/3if_stmt ::= "if" assignment_expression ":" suite ("elif" assignment_expression ":" suite)* ["else" ":" suite]
else
の節は オプション (0回または1回繰り返し)
assignment_expression
if
節や elif
節のヘッダに登場する assignment_expression
について
assignment_expression
は代入式assignment_expression ::= [identifier ":="] expression
大きな構文の一部として、変数に値を割り当てる新しい構文 := が追加されました。 この構文は セイウチの目と牙 に似ているため、「セイウチ演算子」の愛称で知られています。
https://docs.python.org/ja/3/whatsnew/3.8.html#assignment-expressions
代入式により len() 関数を二重に呼びだすことを回避しています:
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")
assignment_expression ::= [identifier ":="] expression
:=
は オプション として if
文の定義に登場
:=
が使えるようになるんだくらいの理解だったが、制御フローの構文が変わるほどの 大きな変更だった ことを実感
while
文の定義にも登場します
while
文while 文は、式の値が真である間、実行を繰り返すために使われます:
while
文の構文while_stmt ::= "while" assignment_expression ":" suite ["else" ":" suite]
while
文while_stmt ::= "while" assignment_expression ":" suite ["else" ":" suite]
キーワード while
で始まるヘッダと続くスイートからなる節が 必須
オプションの else
の節
while
else
式が偽であれば (最初から偽になっていることもありえます)、 else 節がある場合にはそれを実行し、ループを終了します。
最初のスイート内で break 文が実行されると、 else 節のスイートを実行することなくループを終了します。
else
節を使わないのがオススメ項目9 forループとwhileループの後のelseブロックは使わない
Effective Python 第2版 (誤解を生みやすいためと説明されます)
for
文for 文は、シーケンス (文字列、タプルまたはリスト) や、その他の反復可能なオブジェクト (iterable object) 内の要素に渡って反復処理を行うために使われます:
for
文の構文for_stmt ::= "for" target_list "in" expression_list ":" suite ["else" ":" suite]
for
文for_stmt ::= "for" target_list "in" expression_list ":" suite ["else" ":" suite]
キーワード for
で始まり in
を含むヘッダと、続くスイートからなる節が必須
オプションの else
の節
for
else
全ての要素を使い切ったとき [略] else 節があればそれが実行され、ループは終了します。
最初のスイートの中で break 文が実行されると、 else 節のスイートを実行することなくループを終了します。
関数定義は、ユーザ定義関数オブジェクトを定義します
def
)funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
キーワード def
で始まり関数名 funcname
と ()
を含むヘッダと、続くスイートからなる節が必須
()
の中の仮引数 parameter_list
はオプショナル
funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
他にもオプショナルなもの
1つ以上のデコレータ decorators
返り値の型を表す型ヒントに使う -> expression
の部分
match
文The match statement is used for pattern matching.
match
文はパターンマッチングに使われます(nikkie訳)
PyCon Kyushu 2022 Kumamoto: Introduction to Structural Pattern Matching (by takanory-san)
PEP 636 -- Structural Pattern Matching: Tutorial > Appendix A -- Quick Intro
PyCon JP 2021 レポート (YouTubeにアーカイブあります)
match_stmt ::= 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
match
文の例def fizzbuzz(number):
match number % 3, number % 5:
case 0, 0: return "FizzBuzz"
case 0, _: return "Fizz"
case _, 0: return "Buzz"
case _, _: return str(number)
match
文 1/3match_stmt ::= 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
ソフトキーワード match
で始まるヘッダが必須
def fizzbuzz(number):
match number % 3, number % 5:
case 0, 0: return "FizzBuzz"
case 0, _: return "Fizz"
case _, 0: return "Buzz"
case _, _: return str(number)
match
文 2/3match_stmt ::= 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
case_block
(スイート)が1つ以上
def fizzbuzz(number):
match number % 3, number % 5:
case 0, 0: return "FizzBuzz"
case 0, _: return "Fizz"
case _, 0: return "Buzz"
case _, _: return str(number)
match
文 3/3match_stmt ::= 'match' subject_expr ":" NEWLINE INDENT case_block+ DEDENT
NEWLINE
(改行)や INDENT
(インデント)の明示
case
のブロックはヘッダの後につなげない(例えば if
はヘッダの後に続けられる)
case_block
case_block ::= 'case' patterns [guard] ":" block
case_block
case_block ::= 'case' patterns [guard] ":" block
ソフトキーワード case
で始まるヘッダが必須
スイートが block
(セミコロンで区切っても書けるし、次の行にインデントしても書ける)
質疑より: block
とは?( suite
とどう違う?)👉 Appendixに追加
guard ::= "if" named_expression
>>> flag = False
>>> match (100, 200):
... case (100, 200) if flag: # マッチするが, ガードが成り立たない
... print("Case 2")
... case (100, y): # ここにマッチ!(yに200が代入される)
... print(f"Case 3, y: {y}")
...
Case 3, y: 200
複合文の定義を味わった
拡張BNFで、 簡潔 かつ 抜け漏れなく 表現されている
✅ Pythonにおける文
✅ 文を定義する(拡張BNF)
✅ 文の定義を味わう
Pythonの複合文の現在の見た目(具象構文)にフォーカス
拡張BNFを紹介し、複合文の構文を味わった
複合文はヘッダとスイートから構成される
コロンやインデントは、文の構成要素を 機械に伝える ためにある
BNFを読み、 簡潔かつ抜け漏れのない表現 を味わった
縦方向のGrowへの取り組みも面白いですよ〜
Enjoy development with Python!
WEB+DB PRESS Vol.125 「特集1 作って学ぶ プログラミング言語のしくみ」
抽象文法、具象文法
match
文の case
ブロックの定義に出てくる block
とは?
suite
と何が違うのか?
case_block ::= 'case' patterns [guard] ":" block
#pycon9kuB @takanory さんからいただいた質問
— nikkie にっきー (@ftnext) January 22, 2022
match文のBNFのblockはsuiteとはどう違う?
A: 調査不足です。説明できるほど理解してなかったと認識しました。後ほどアップデートします!
質問ありがとうございました
block
はPEGこの発表で扱った BNFではない
10. 完全な文法仕様 に見つけた(複合文のドキュメントに見つけられなかった)
match
文はPython3.10からなので、Python3.9からのPEGの採用は納得
block
のPEGこの発表で紹介した「スイート」(EBNFの suite
)を表していると思われる(宿題:PEG版にアップデート)
block:
| NEWLINE INDENT statements DEDENT
| simple_stmts
Guido-sanの 設計意図 を知られる
主に 可読性を高める ため
英語の標準的な用法
コロンによってエディタがシンタックスハイライトをしやすくなる
インデントでグループ化することで、Pythonプログラムを 読みやすく するため
開始/終了の括弧がないことによる3つの理由(次スライド)
構文解析器と人間の読者の間にグループ化の解釈の違いが起こりえない
コーディングスタイルの争いに余り影響されない
プログラムが冗長にならない 「20 行の Python は 20 行の C よりもはるかに多くの作業ができます」