文に立ち返ってPython再入門

Event:

PyCon Kyushu 2022 Kumamoto

Presented:

2022/01/22 nikkie

変更履歴

  • 2022/04/17 BNF記法 -> BNF に修正(F(form)自体が「記法」の意のため) #26

参加者の皆さん、こーんにーちはーー❗️

最初に質問:Pythonで書けますか❔

  • if

  • for

  • 関数

本トーク「文に立ち返ってPython再入門」について

対象者:

if 文・ for 文・関数定義が書ける(=入門書は終えた)Python使い(Intermediate)

話すこと:

構文 解析

聞いて持ち帰れるもの( 公開プロポーザル

  • 文の構成要素:式とキーワード

  • 節、ヘッダ、スイートによる文の説明

  • BNFの読み方

免責事項🙏

  • 「構文」がめちゃくちゃ面白い✨と思っているので、話します

  • 万人向けではありません(この話を知らなくてもPythonは使っていけます)

  • Pythonに未入門の方、方向性合わないかもと直感した方、裏のトークに行くなら今!

お前、誰よ

  • Python大好き にっきー

  • Python歴4年。株式会社ユーザベースのデータサイエンティスト(NLPer)

心を燃やした❤️🔥PyCon JP 2021座長

News: PyCon JP 2022 座長決定👏

もう少し:このコミットグラフは?

../_images/202201_ainouta_watch_log.png

たたーん! 映画『アイの歌声を聴かせて』🤖🎤🎼 鑑賞回数!

  • ㊗️ 日本アカデミー賞 優秀アニメーション作品賞 👏

  • ほんとによくて、2021年10月の公開から 20回以上 観てます

  • ブログ「好きな映画の鑑賞ログをGitHubのContributionのような形式で表示する」 前編後編

One more thing: Everyday Contribution🙌

../_images/202201_nikkie_contribution.png

2020/08の 100DaysOfContribution 達成しました! が続いています

質問:Pythonを書くとき、「文」って意識しますか❔

本題へ: に立ち返って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
名前を入力してください: ローランド
俺か

IMO:文は意識しない?

  • Pythonを書いているとき「私、いま文書いてる」とはあまり思わない

  • 書いているプログラムで意識するのは (例:レビューで 「N行目の...」)

  • (文は、プログラムの1つ以上の行からできます)

IMO:意識しているのは、文が作り出す構造

構造を捉える例(roland.py

name = input("名前を入力してください: ")
if name == "ローランド":
    print("俺か")  # nameが"ローランド"のときの分岐
else:
    print("俺以外か")  # nameが"ローランド"でないときの分岐

では、文は誰のためにあるのか?

どうやら私たちプログラマは文を意識していなさそう

IMO:文は 機械 🤖のためにある

高水準(高級)/低水準(低級)

High level / Low level

高水準言語/機械語

High level

Low level

高水準言語(例:Python)

機械語

人間👩‍💻👨‍💻が読み書き

機械🤖が読み書き

人間が高水準言語でプログラムを書けるためには

  • 機械が高水準言語を機械語に 変換 している

  • つまり、機械は(機械語だけでなく) 高水準言語も読んで いる

機械 がプログラムの構造を理解する

そのための

別の視点:計算機科学における「コンパイル」

参考:『 コンピュータシステムの理論と実装

コンパイルの例

  • ソースコード(Pythonで書かれた)

  • バイトコード(インタプリタの内部表現。pycファイルにキャッシュ。 ref: 用語集

if 文の例でコンパイルを見る

name = input("名前を入力してください: ")
if name == "ローランド":
    print("俺か")
else:
    print("俺以外か")

コンパイルから2点ピックアップ

  1. 字句解析

  2. 抽象構文木

字句解析(tokenize)

  • ソースコードは文字列

  • 字句(トークン) =意味を持つ最小単位

字句解析の例(python -m tokenize -e roland.py

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)

  • 機械は、プログラムの構造を で表す

  • 一連のトークンから抽象構文木を出力

抽象構文木の例(python -m ast -m exec roland.py

    If(
      test=Compare(
        left=Name(id='name', ctx=Load()),
        ops=[
          Eq()],
        comparators=[
          Constant(value='ローランド')]),
      body=[
        Expr(

構文にも2種類ある

抽象構文

具象構文

インタプリタが解釈

プログラミング言語の見た目(例:複合文はこう書く)

このトークの主題は 具象構文

  • Pythonの複合文の現在の 見た目 について深堀ります(Python 3.10.2)

  • 機械は文法に沿って具象構文を読んでます(鍵🗝は「再帰」)

さらに別視点:Grow with Covid-19

PyCon Kyushu 2022 Kumamotoのテーマ

IMO:2方向のGrow

  • 横方向:文法やライブラリを知ってできること広がる

  • 縦方向:文法やライブラリを掘り下げて深く理解する

(某書レビューでの学びをもとにしています)

このトークは 縦方向

  • 横方向:文法やライブラリを知ってできること広がる

  • 縦方向:文法やライブラリを 掘り下げ て深く理解する 👈本トーク

本トーク「文に立ち返ってPython再入門」では

  • Pythonのコロンやインデントの意味を共有

  • BNFを読んで一緒に味わう

本トーク「文に立ち返ってPython再入門」では

  • コロンやインデントの意味: 文の構成要素を機械に伝えている

  • BNFを読んで一緒に味わう

本トーク「文に立ち返ってPython再入門」では

  • コロンやインデントの意味:文の構成要素を機械に伝えている

  • BNFを読んで一緒に味わう: 簡潔かつ抜け漏れのない表現

お品書き:文に立ち返ってPython再入門

  1. Pythonにおける文

  2. 文を定義する(拡張BNF)

  3. 文の定義を味わう

Part I. Pythonにおける文

用語集「文」

A statement is part of a suite (a “block” of code).

Python用語集 文 (en)

文はスイート (コードの"ブロック") の一部です。(nikkie訳)

今回は「複合文」にフォーカス

複合文には、他の文 (のグループ) が入ります

言語リファレンス 8. 複合文

複合文の例( if 文)

name = input("名前を入力してください: ")
if name == "ローランド":
    print("俺か")
else:
    print("俺以外か")

複合文のイメージは制御フロー

複合文は、中に入っている他の文の実行の制御に何らかのやり方で影響を及ぼします。( 8. 複合文

if 文だと分岐(実行する/しない)

複合文の構成要素

  • ヘッダ

  • スイート

複合文は、一つ以上の '節 (clause)' からなります。( 8. 複合文

例の if 文を構成する節 その1

name = input("名前を入力してください: ")
if name == "ローランド":
    print("俺か")
else:
    print("俺以外か")

例の if 文を構成する節 その2

name = 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("俺以外か")

スイートはインデントされた文の集まり(2文以上)

name = input("名前を入力してください: ")
if name == "ローランド":
    print("俺")
    print("か")
else:
    print("俺以外か")

スイート

[先に紹介した形式] のスイートに限り、さらに複合文をネストできます( 8. 複合文

複合文は 再帰 的!(複合文のスイートに別の複合文)

スイートに複合文をネストする例

name = input("名前を入力してください: ")
if name == "ローランド":
    for _ in range(3):
        print("俺か")
else:
    print("俺以外か")

小まとめ🥟:Pythonにおける文

  • 複合文は (ヘッダとスイート)からなる

  • コロンはヘッダを示している!

  • インデントはスイートを示している!

文の補足:用語集には続きが

A statement is either an expression or one of several constructs with a keyword, such as if, while or for.

文は、式または、 if, while, for のようなキーワードから構成されるものです(nikkie訳)

文はどちらか

  1. 式 (an expression)

  2. キーワードから構成されるもの (one of several constructs with a keyword)

キーワードの補足:予約語

>>> if = 1231  # 変数として使えません
  File "<stdin>", line 1
    if = 1231
       ^
SyntaxError: invalid syntax

文の構成要素の1つ:式とは

何かの値と評価される、一まとまりの構文 (A piece of syntax which can be evaluated to some value.)

Python用語集 式

用語集「式」の続き

言い換えると、式とは [中略] 値を返す式の要素の積み重ねです。 (In other words, an expression is an accumulation of expression elements [...] which all return a value.)

Python用語集 式

式の定義にも 再帰

式の要素とは

  • 式の要素の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("俺以外か")

文の補足、終わり!

  1. ✅ Pythonにおける文

  2. 文を定義する(拡張BNF)

  3. 文の定義を味わう

Part II. 文を定義する(拡張BNF)

拡張したBNF

字句解析と構文に関する記述では、BNF 文法記法に手を加えたものを使っています。( 1.2. 本マニュアルにおける表記法

EBNFとも呼ぶドキュメントもありました(Extended Backus–Naur Form)

拡張したBNF(承前)

各規則は name (規則によって定義されているものの名前) と ::= から始まります。( 1.2. 本マニュアルにおける表記法

注意⚠️:Python 3.9からはPEG

拡張BNFの読み方

1.2. 本マニュアルにおける表記法 に記載があります。

拡張BNFにおける記号

  • |

  • *

  • +

  • []

  • ()

  • "

  • 『 』(※空白です)

|

複数の選択項目を分かち書きするときに使います

「または」

"a" | "b""a", "b"

*

直前にくる要素のゼロ個以上の繰り返しを表します

"a"*"", "a", "aaaa", ...

+

プラス (+) は一個以上の繰り返し

* に似て、直前に来る要素の一個以上の繰り返し

"a"+"a", "aaaa", ...

[]

角括弧 ([ ]) に囲われた字句は、字句がゼロ個か一個出現する
(別の言い方をすれば、囲いの中の字句はオプションである)

["a"]"", "a"

()

字句のグループ化には丸括弧を使います。

"

リテラル文字列はクオートで囲われます。

空白

空白はトークンを分割しているときのみ意味を持ちます。

拡張BNFの例

前提「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)

  • 拡張BNFの記号の意味を確認

  • Pythonの 複合文の定義を読む準備 が整った

お品書き:文に立ち返ってPython再入門

  1. ✅ Pythonにおける文

  2. ✅ 文を定義する(拡張BNF)

  3. 文の定義を味わう

Part III. 文の定義を味わう

構文定義を読んでみる

  • 8. 複合文 中の定義を読んでみましょう

  • 拡張BNFに沿って読むと、簡潔な行数で、抜け漏れなく説明される 感動がありました!

構文定義、積ん読!

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | funcdef
                   | match_stmt
                   | (リファレンスにはまだまだあります)

構文の拡張BNFを読む際の前提

  • プログラムの字句解析が終わって、トークンの並びになっています

  • 機械は トークンの並びが規則に当てはまるか をチェックしています

  • =拡張BNFに沿って書けば文法エラーにはなりません

if

if 文は、条件分岐を実行するために使われます:

8.1. if 文

if 文の構文を(EBNFを使わずに)どう伝えますか❔

少し考えてみてください

私の戦略:場合分け

  • elseの有無

    • if ...

    • if ... else ...

場合分け、抜け漏れの確認ツライ😫

  • elifの個数(0個/1個/複数)

    • if ... elif ...

    • if ... elif ... elif ...

    • if ... elif ... else ...

    • if ... elif ... elif ... else ...

EBNFでは、たった 3行

  • 場合分け戦略だと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/3

if_stmt ::=  "if" assignment_expression ":" suite
             ("elif" assignment_expression ":" suite)*
             ["else" ":" suite]
  • キーワード if で始まるヘッダと続くスイートからなる節は 必須

味わう if 文 2/3

if_stmt ::=  "if" assignment_expression ":" suite
             ("elif" assignment_expression ":" suite)*
             ["else" ":" suite]
  • elif の節は 0回以上 の繰り返し(ない場合もあるし、複数ある場合もある)

味わう if 文 3/3

if_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

6.12. 代入式

代入式はPython3.8 (2019/10 release) から

大きな構文の一部として、変数に値を割り当てる新しい構文 := が追加されました。 この構文は セイウチの目と牙 に似ているため、「セイウチ演算子」の愛称で知られています。

https://docs.python.org/ja/3/whatsnew/3.8.html#assignment-expressions

代入式の例(Python 3.8 What's Newより)

代入式により len() 関数を二重に呼びだすことを回避しています:

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

構文を読んでの気づき 1/2

assignment_expression ::=  [identifier ":="] expression
  • :=オプション として if 文の定義に登場

構文を読んでの気づき 2/2

  • := が使えるようになるんだくらいの理解だったが、制御フローの構文が変わるほどの 大きな変更だった ことを実感

  • while 文の定義にも登場します

while

while 文は、式の値が真である間、実行を繰り返すために使われます:

8.2. 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) 内の要素に渡って反復処理を行うために使われます:

8.3. for 文

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 節のスイートを実行することなくループを終了します。

関数定義

関数定義は、ユーザ定義関数オブジェクトを定義します

8.7. 関数定義

関数定義の構文(def

funcdef ::=  [decorators] "def" funcname "(" [parameter_list] ")"
             ["->" expression] ":" suite

味わう関数定義 1/2

funcdef ::=  [decorators] "def" funcname "(" [parameter_list] ")"
             ["->" expression] ":" suite
  • キーワード def で始まり関数名 funcname() を含むヘッダと、続くスイートからなる節が必須

  • () の中の仮引数 parameter_list はオプショナル

味わう関数定義 2/2

funcdef ::=  [decorators] "def" funcname "(" [parameter_list] ")"
             ["->" expression] ":" suite
  • 他にもオプショナルなもの

    • 1つ以上のデコレータ decorators

    • 返り値の型を表す型ヒントに使う -> expression の部分

match

The match statement is used for pattern matching.

match 文はパターンマッチングに使われます(nikkie訳)

8.6. The match statement

キャッチアップのリソース📚

パターンマッチの構文

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/3

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 文 2/3

match_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/3

match_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)※オプショナル

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再入門

  1. ✅ Pythonにおける文

  2. ✅ 文を定義する(拡張BNF)

  3. ✅ 文の定義を味わう

まとめ🌯:文に立ち返ってPython再入門

  • Pythonの複合文の現在の見た目(具象構文)にフォーカス

  • 拡張BNFを紹介し、複合文の構文を味わった

まとめ🌯:文に立ち返ってPython再入門

  • 複合文はヘッダとスイートから構成される

  • コロンやインデントは、文の構成要素を 機械に伝える ためにある

まとめ🌯:文に立ち返ってPython再入門

  • BNFを読み、 簡潔かつ抜け漏れのない表現 を味わった

  • 縦方向のGrowへの取り組みも面白いですよ〜

「だんだんな〜」 ご清聴ありがとうございました

Enjoy development with Python!

References

本トークの元:2021/11 OSC Fukuoka

Appendix: blockとは

  • match 文の case ブロックの定義に出てくる block とは?

  • suite と何が違うのか?

case_block ::=  'case' patterns [guard] ":" block

発表への質問としていただいた

回答: block はPEG

  • この発表で扱った BNFではない

  • 10. 完全な文法仕様 に見つけた(複合文のドキュメントに見つけられなかった)

  • match 文はPython3.10からなので、Python3.9からのPEGの採用は納得

block のPEG

この発表で紹介した「スイート」(EBNFの suite)を表していると思われる(宿題:PEG版にアップデート)

block:
    | NEWLINE INDENT statements DEDENT
    | simple_stmts

Appendix: FAQから見るコロンやインデントの理由

if/while/def/class 文にコロンが必要なのはなぜですか?

https://docs.python.org/ja/3/faq/design.html#why-are-colons-required-for-the-if-while-def-class-statements

コロンの理由(FAQより)

  • 主に 可読性を高める ため

  • 英語の標準的な用法

  • コロンによってエディタがシンタックスハイライトをしやすくなる

Python はなぜ文のグループ化にインデントを使うのですか?

https://docs.python.org/ja/3/faq/design.html#why-does-python-use-indentation-for-grouping-of-statements

インデントの理由(FAQより)

  • インデントでグループ化することで、Pythonプログラムを 読みやすく するため

  • 開始/終了の括弧がないことによる3つの理由(次スライド)

開始/終了の括弧がないことで

  • 構文解析器と人間の読者の間にグループ化の解釈の違いが起こりえない

  • コーディングスタイルの争いに余り影響されない

  • プログラムが冗長にならない 「20 行の Python は 20 行の C よりもはるかに多くの作業ができます」

EOF