Python普及しろ協会に入会したい
この記事はタナイ氏によるPython滅ぼす協会に入会したいを読んでから執筆したものです。
この記事の趣旨はPython滅ぼす協会に入会したいに対する反論という形をとりながら、タナイ氏により「バカの言語」と揶揄され、「使ってエンジニアを名乗るというのは」「滑稽」とまで言われたPythonの立場を再考することです。
追記
本記事は「Pythonはこれだけ優れた言語だからみんな使おう!」というものではなく「言うほど酷くないと思うよ」程度のものです。
型アノテーションがあるからと言って静的型付けを軽視しているわけでもなければ、map関数をもってmapメソッドを不要だと言っているわけでもありません。
この記法は嫌い〜この記法が好き〜と表明することは個人の自由ですが、同様に「この記法は実はこういう意味があって〜」という意見があればそれを聞いた上で、物事を判断して欲しいです。もちろん、聞いても意見が変わらなければそれで結構です。僕と価値観や重視する点がちょこっと異なるだけです。
追記2
言語には向いている用途とそうじゃない用途があります。
Pythonが好きな筆者でさえ、大人数のエンジニアが関係するような開発や、リテラシーが低めのエンジニアが居るような複数人開発ではPythonを選定することは多少チャレンジングだと考えています。
逆に、リテラシーの高いエンジニアが少数でスピード感を持って開発したい時やAIに関する開発、書きなぐりのコードを作りたい時や初学者に教える時、Pythonは良いチョイスだと思います。
そういった向き不向きを踏まえてどの言語を使うべきか考えるのも我々の仕事であり、言語を滅ぼすことは少なくともその範疇には含まれていないはずです。
追記3
筆者はPythonが好きですが、何でもかんでもPythonでやればいいじゃんとかは全く思っていません。言語仕様の全てが好きなわけでもありません。
List.map
が欲しいし、定数はないよりある方が便利だし、アロー演算子も好きです。だからと言って、List.map
を追加しない選択をしたのはバカだ!とか、定数がないからゴミだ!とかlambdaはダメだ!という話にはならないんじゃないの?と思います。
そういう意味で「彼らにも一定主張があって、それは一理あると僕は思うよ」というのをPythonの肩を持ちながら述べています。
動的型付け言語も進化している
普段からDartやTypeScriptなども書いている筆者にとって「型というガードレールもシートベルトもなしで糞を撒き散らしながらする開発にはうんざり」という意見はわからないでもない。
しかし、本当にPythonは「型というガードレールもシートベルト」もなく、開発は「糞を撒き散ら」すものなのか。
name_by_id: dict[int, str] = {} # warningが出る場合があるが(リテラルで記述できるよ!)という内容なので無視できる
name_by_id[1] = 'python'
name_by_id['ruby'] = 2 # まともなIDEならwarning
def greet(name: str) -> None:
if not isinstance(name, str):
raise TypeError(f'name must be str: got {type(name)!r}')
print(f'hello {name}')
greet('python')
greet(100) # まともなIDEならwarning
ここ数年でPythonの型アノテーションは劇的な進化を遂げてきた。TypeAlias, Union, Genericsさえもある。
「貧弱な型ヒント、しかも書いたところで大した効用もない」という部分に関しては、まったく同意しかねる。
Pythonのdict
は型を指定するものではないのでエラーにはならない。しかし静的解析ができる範囲で、早期に問題を発見することができる。
ツールで言うと、mypyやpyrightという非常に優秀な型チェッカーも存在する。
└> mypy sample.py
sample.py:3: error: Invalid index type "str" for "Dict[int, str]"; expected type "int"
sample.py:3: error: Incompatible types in assignment (expression has type "int", target has type "str")
sample.py:13: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 3 errors in 1 file (checked 1 source file)
「十分に型を記述して型チェックも入れたPythonコードをmypyでチェックして実行する」ことと「十分に型を記述したTypeScriptコードをJavaScriptにトランスパイルして実行する」ことの間には、「発想自体がどうかしている」という程の差が果たしてあるのだろうか。
個人的には、ある言語(そもそも直接のRuntimeがないのだからそこにあるのはプログラミング言語というより、言語仕様ではないか。この言い方だとllvmやアセンブリを無視しているように聞こえるかもしれないが、言いたいことは伝わると思う)から別の言語にトランスパイルする過程での問題を過小評価しているといわざるを得ないと感じる。
確かに一部の古いライブラリには型アノテーションが不十分、あるいはまったくないものも存在する。
それらを使う場合には、ドキュメントを読み解いて自前でアノテーションを付けて型チェックを挟むことなども必要になるだろう。おかしな型アノテーションがされていればそれを上書きすることさえある。
ただし、ほとんどの場合においてそういうことはあまりない。
開発で普通に使われるようなライブラリはたいていしっかりとアノテーションがつけられている(コミュニティが大きいPythonだからこそ、こういった対応も早い)し、つけられていなくても優秀なIDEが自分で型を解析してくれたりする場合が多い。
Pythonが型安全だ!とか静的型付けと変わらない!と言っているのではなく「それほど混沌としたものでは無いのだから、厳格さの重要性と照らして問題ない場合にPythonを選ぶのは理にかなっているだろう」という主張だ。
結局のところどんな言語を使おうと、ほとんどの開発はいかに any
やunknown
型と戦うかという話になる。
WebAPIからJSONを取ってくればresponse bodyのbytes[]
(意味のあるデータ列だということを信じてはいるが、この時点では実質any
。如何なるデータもPC内部ではただのバイナリ)をstring
にし、パースしてMap<any, any>
(あるいはMap<[str, number], [str, number, Array<self>, self]>
とか?細かい仕様を見て書いてはいない)を得ることになるし、SQLで select age, name from users;
を叩けば any
をTuple<integer, string>
に型変換することになる。もっと複雑な処理をSQL文でやれば、プログラミング言語が提供できる型安全性からさらに離れていく。(少なくとも僕の知っている全ての)ORMには限界がある。どっかの段階で、生SQLという選択肢を選ぶ必要が出てくる。
完璧な型安全性などはないのだから、みんなどこかでany
と戦っている。
実務的な範囲で言えば、IOに型が付かないからと言って困る(型の安全性を疑う)場面はそうそうないだろう。しかし、そういう裏側をラップしてくれている存在がいて、そのうえで型安全な場所が提供されているということは意識した方が良いだろう。
Pythonでも、一部のアノテーションがついていないものに自分でしっかりアノテーションを付ければ、自分だけの型安全(と思える快適)な聖域が作れる。
どこから安全かという違いはあるにしろ、シートベルトをいつからどれくらいきつく締めるかは、プログラマ次第ではないだろうか。
ちなみに筆者はmypyなどのツールは普段使っていない。PyCharmくんは優秀なので。
シンタックスがキモいらしい
Python滅ぼす協会に入会したいによると、筆者のタナイ氏は少なくとも以下のsyntaxをキモいと感じているそうだ。
map(lambda x: y, some_list)
- 通常のif文は
if then else
だが、式は1 if x else 0
-
some_dict.some_key
ではなくsome_dict["some_key"]
、{foo: number, bar: string}
を定義するためだけにTypedDict
をimportする必要がある -
a = 10
が文であり式ではない。その影響でa := 10
という構文がある - 無名関数が
lambda 引数: 返り値
-
&&
や||
ではなくand
とor
- 定数が定義できない
結構多い
map(lambda x: y, some_list)
1. 「配列に対して操作を加えていきたいのに、記述位置を戻る必要がある。
戻らずに書き下せるように、適用順を変える演算子だとかモナドだとかHaskellの爪の垢を煎じて飲ませたい。」
Pythonは全体的な設計思想として、便利そうだからといってポンポンと機能を追加したりはしない。
このmap
関数(やfilter
など)は純粋な「高階関数」という概念を表しているに過ぎず、こういうsyntaxでmap
を提供している言語は多く存在する。高階関数(処理, 対象)
というsyntaxは珍しくもなんともないし、そもそもはそういうものだ。
タナイ氏が「爪の垢を煎じて飲ませたい」と言っているhaskellでさえ
map func list
というsyntax(というか関数だが)を提供している。
Pythonはその思想の体現である『The Zen of Python』で"There should be one-- and preferably only one --obvious way to do it."(明白なやり方が ~できればたった1つだけ~ あるべきだ)と言っている。
高階関数が既にあるなら別にlist
にmap
メソッドをはやす必要はない。やりたいなら、自分で拡張すればよい。という考え方だ。
例えばGolangではよりシンプルにfilter
やmap
すらない。Pythonの作者のGuidoとしては、そこまでシンプルにしすぎるのはやりすぎで "practicality beats purity."(実用性は純粋さに勝る) から、関数は作ったのかもしれない。
さらに言うなら、map
やfilter
をチェイン(Pythonではネストになるが)したいならそれぞれの処理ごとに変数に入れたり、for文を使えばよい。
無理に頑張る必要はないのだから。
この記事の筆者は、
Array<T>
のメソッドとして定義されているmapメソッドも好きだ。TypeScriptなどを書く際には楽しんで使っている。
しかし、それはmap
関数を否定する材料にはならないと考えている。
if then else
だが、式は 1 if x else 0
2. 通常のif文は「冗長で、醜く、プログラマのメンタルモデルに一致しない。」とタナイ氏は述べているが、これは"日本語話者エンジニアである自身のメンタルモデルに一致していない"と言い換えていただいた方が良いかもしれない。
PythonはそのSyntaxを英語に寄せており、可読性を重視している。英語においては"A if condition. otherwise B"という言い方を普通にするので、読みやすさという観点でこれにあっているのだ。
condition ? A : B
が読みやすい(分かりやすいと感じる)のはこの記法に慣れているからであって、この記法に慣れている人間よりも英語に慣れている人間の方がはるかに多いのだから、英語に寄せるという設計思想である以上メンタルモデル云々はPythonの文法を否定する材料にはならない。
文がthen if else
じゃないのは、そりゃそうだという感じだ。conditionと値の距離が近い式と違って、文は遠い。
ifを先に持ってくるのが実利に勝るし、何よりブロックの開始を意味するsyntaxを増やさなくてはならない。Pythonは、むやみやたらにそういうものを増やさない。
some_dict.some_key
ではなく some_dict["some_key"]
、{foo: number, bar: string}
を定義するためだけに TypedDict
をimportする必要がある
3. これは根本からタナイ氏が間違っている。
some_dict.some_key
をTypeScriptでできるから~という流れで記述されているようだが、そもそも.some_key
は辞書(TypeScript/JavaScriptでいうMap)のゲッターではない。
TypeScriptでの構文は
const map = new Map<string, string>();
map.set('key', 'value');
map.get('key');
である。
const map: CustomMap = {
key: 'value'
}
interface CustomMap {
key: string;
}
map.key
として定義できるものを辞書と言っているのかもしれないが、これはただのObject
だ。
つまり、Pythonでこれに等価なものはdict
ではなく、class
のインスタンスである
#~#~#~#~#~#~#~# class #~#~#~#~#~#~#~#
class CustomMap:
def __init__(self, key: str):
self.key = key
def __repr__(self) -> str:
return f'CustomMap(key={self.key!r})'
>>> CustomMap(key='value')
CustomMap(key='value')
>>> CustomMap(key=1)
CustomMap(key=1)
#~#~#~#~#~#~#~# dataclass #~#~#~#~#~#~#~#
# 公式のdataclassを使うとより簡単に表現できる
from dataclasses import dataclass
@dataclass
class CustomMapWithDataclass:
key: str
>>> CustomMapWithDataclass('value')
CustomMapWithDataclass(key='value')
# IDEではwarningを出してくれるし、mypyだとエラーが出る
>>> CustomMapWithDataclass(key=1)
CustomMapWithDataclass(key=1)
#~#~#~#~#~#~#~# pydantic #~#~#~#~#~#~#~#
# pydanticを使うと、castさせたり、エラーを吐かせたりが簡単にできる
from pydantic import BaseModel
class CustomMapWithPydantic(BaseModel):
key: str
>>> CustomMapWithPydantic(key='value')
CustomMapWithPydantic(key='value')
>>> CustomMapWithPydantic(key=1)
CustomMapWithPydantic(key='1')
#~#~#~#~#~#~#~# pydantic(strict) #~#~#~#~#~#~#~#
from pydantic import Field
class CustomMapWithStrictPydantic(BaseModel):
key: str = Field(strict=True)
>>> CustomMapWithStrictPydantic(key='value')
CustomMapWithStrictPydantic(key='value')
>>> CustomMapWithStrictPydantic(key=1)
pydantic_core._pydantic_core.ValidationError: 1 validation error for CustomMapWithPydantic
key
classのインスタンスであれば
class CustomMap:
def __init__(self, key: str):
self.key = key
def __repr__(self) -> str:
return f'CustomMap(key={self.key!r})'
>>> custom_map = CustomMap(key='value')
CustomMap(key='value')
>>> custom_map.key
'value'
>>> custom_map.key = 'next_value'
とできる。
TypedDict
をimportする"必要"は一切ない。class
を使えばいいだけだ。
VSCodeではMap
にマウスオーバーすると要素が表示されるのか?と思ったがそんなことはなかった。そりゃそうだ。辞書はランタイムでkeyも変わるのだから。
この項目に関しては、まったくの的外れである。
a = 10
が文であり式ではない。その影響でa := 10
という構文がある
4. この言い分には2つの視点がある。
a = 10
が文であり式ではない
これは、個人的に良い言語仕様であると思う。 a = 10
が 10
となるような式だった場合
const SECRET = 'secret_key';
const validateKey = (key: string) => {
return key = SECRET; // == にすべきところをtypoしてしまった!
}
if (validateKey('wrongKey')) {
console.log('correct key!')
} else {
console.log('wrong key...')
}
# output:
# correct key!
となってしまう。多くのプログラマーが =
と==
を間違えて時間を溶かしてしまった経験があると思う。
これを防ぐために、Pythonでは
>>> a = 1
>>> if a = 1:
File "<stdin>", line 1
if a = 1:
^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
となっているのだ。
C言語などにおける
int a, b;
a = (b = 10);
は
a = b = 10
とできるので、そもそも式にする必要はない
a := 10
という構文がある
これは
value = heavy_duty()
if value:
# valueはここでのみ使う
# 正規表現
regex_match = pattern.search(data)
if regex_match is not None:
# regex_matchはここでのみ使う
# read
chunk = file.read(8192)
while chunk:
process(chunk)
chunk = file.read(8192)
といった処理を
if value := heavy_duty():
# valueはここでのみ使う
if (regex_match := pattern.search(data)) is not None:
# regex_matchはここでのみ使う
while chunk := file.read(8192):
process(chunk)
と簡略化するためだけのsyntaxである。
トップレベルで使えないのは、=
が使えるからだ。
Pythonは新規機能を追加するのに至極消極的である。この構文も、Guido van Rossumがこの構文を気に入らなければ、十中八九却下されていただろう。その中で、トップレベルで(=と同じように)使えないようにしたのは、
"There should be one-- and preferably only one --obvious way to do it."(明白なやり方が ~できればたった1つだけ~ あるべきだ)からだろう。
ただし
a = (b := 10)
という構文はトップレベルでも有効だ
lambda 引数: 返り値
5. 無名関数が これが「まだるっこしい」かどうかは、個々人の価値観次第だ。タナイ氏がオランダ人じゃないからそう感じるのかもしれない
個人的には、嫌いじゃない
&&
や||
ではなくand
とor
6. この言語は、英語話者が直感的に理解できるようにという思想で作られている。
&&
や||
より and
とor
の方が、この視点で言えば良い。少なくとも、&
と&&
のどっちがbit演算子でどっちが論理演算子かを迷う必要はない。
この"迷いがない"というメリットに対するデメリットが「記述量が増えている」なら、正直無視できる
7. 定数が定義できない
Pythonには一部「真の定数」が存在するが、ユーザはそれを作成することはできない。
ユーザができることはせいぜい定数として命名しlinterを通して再代入を防ぐように呼び掛けたり、classのgetterやsetterを頑張って使って定数っぽいものを作ったり、アノテーションを通してFinalと示してIDEやツールで代入を防ぐことくらいだ。
しかし、別にそれで特に問題はないだろう。
定数を上書きできたとして何に困るのか、いまいちわからない。
上書きできるのだから定数に依存するテストが書きやすい。
プログラマが上書きしないようにと考えていれば、本来定数なんて上書きするものではないのだから。
もししてしまうなら、それは定数がないことより、そんなコードを書くことの方が問題だ。
Guidoは"We're all consenting adults here"(Effective Pythonには「みんないい大人なんだから」と書いていたような気がする。完全にうろ覚え)と言ったらしい。
各々が各々の責任において「この定数を上書きするぞ!」と決めたのなら、それをやめさせる理由はない。
競プロ以外に正しい使い道がないのか
筆者は大昔に競プロをPythonでやっていた。
水色になった直後に業プロ(業務プログラミング)に目覚めて辞めてしまったが、競プロをPythonでやっていたのは結構良い選択だったと思っている。
30行くらいのコードを書くシーンでは使いやすいが、「そんなノリで通常の開発をしたら早晩詰む」という意見がある。
果たしてそうなのか。
答えは、Instagramとか見ていただければよいと思う。
いい例だけを取り上げて「Pythonは大規模開発にも向いている!」というつもりはない。大規模開発にもっと向いている言語はたくさんある。
ただし、「一般の開発に使うと早晩詰むか」と聞かれれば「あなた次第だ」というほかない。
Jupyter Notebook(や Google Colaboratory)についても、向き不向きがある。
機械学習をやっている時、Jupyter Notebookの開発体験は非常に良かった。同じ用途においては、他の人にも同様のものを勧めるだろう。
しかし、それはほぼ書き捨てで様々な検証を行うためのものだ。
あくまで"ノートブック"だし、"コラボ"で"ラボラトリー"(予想である)を運用するための物であって、これを保守対象とするのは、渡す側と受け取る側の問題だ。
まだ言い続けるなら、TypeScriptのプレイグラウンドを保守対象として渡すことも辞さない。
ちなみに筆者も、保守対象として Jupyter Notebook を受け取りたいといえば嘘になる
教育現場でPythonが選択されているのは不幸なのか
不幸ではないと個人的に思う。
「国語の教科書の代わりに素人が書いたケータイ小説を渡しているようなもの」という意見もあるがでは逆に、小学1年生に『人間失格』を読ませるのか。
いや、そもそもタナイ氏の例もそれに対する上記の私の反論も正しいものではないだろう。
さしずめ厳密な型付けをした型安全で記号が多い言語が「文語体の論文」で、動的型付けで英語のように「記述量が増えている」言語が「文語体の小説」とか言った方が近いかもしれない。「素人が書いた」かどうかは全くもって言語うんぬんとは異なる話である。
研究者を育成したいなら小説だけを読ませるのは問題だ。しかし、日本語のとっかかりに美しい口語体の小説を読ませ、慣れてきたうえで望むならば論文でも読ませてあげるのが流れというものではないか。
Python滅ぼす協会に入会したいの筆者と同じような勘違いをされている方も一部いらっしゃるかもしれないので一応述べておくと、実はプログラミング教育の目的はプログラミング言語の習得それ自体ではなく、IT時代を生き抜くためにロジカルな思考を身につけ、活用する術・態度を育むことである(筆者は以前文部科学省のPython教材に関する記事を書いたことがあり、教材・資料を読み漁った)。
口語体でも文語体でも、国語の教科書だろうとケータイ小説だろうと、学んでほしいのはその裏側にある思考なのだ。
プログラミング教育の目的はエンジニアの育成ではない。
プログラミング教育現場で使用する言語に求められる要素は
- 実際に活用可能
- さまざまな環境(win, mac)で動作する
- アルゴリズムをもとに、簡単に記述ができる
などだ
型が~とか、&&
だand
だ~とか言っているうちは、何もわかっていない・視野が狭いということだ。
逆にどの言語なら良いのだろうか?
TypeScript(JavaScript)は型の暗黙の変換による罠が多すぎて初学者には不向き
Javaはいわゆる「おまじない」が多すぎて不向き
Cの型システムは子供には難解すぎるかもしれないので不向き
どの言語も(Pythonももちろん含めて)否定しようと思えばいくらでも出来ると思う。
lambda arg: return
より(arg)=>resp
が、A if cond else B
よりcond ? A : B
が完全に優れていて、それぞれ前者を「まだるっこしいシンタックスなのも癇に障る」とか「冗長で、醜く、プログラマのメンタルモデルに一致しない」と思う程記号でプログラミングがやりたいなら、Brainfuckでもやっておけばよい。
Discussion
unknown型については、pyrightではTypeScriptのようにunknown型をany型と分けて推論してくれます。pyrightでは暗黙のany型をunknown型、明示的なany型をany型として扱い、strictモードではそれに対するいくつかの型チェックが提供されています。mypyではこれらは区別されず、mypyではなくpyrightを選ぶメリットの一つのように感じています。
TypedDictについては、個人的に、ネストされたdictに対してTypeScriptのように単一の式で型を定義することができないのがかなり致命的なように感じています。TypedDictでネストしたdictの型を定義したい場合、個別に子要素のTypedDictを定義しする必要があります。
これについては、inlined TypedDictという形で、pyrightにexperimental featureが追加されています。
これが正式に実装されるだけでかなり開発が楽になると個人的には感じています。
@npkyokyo さん
コメントありがとうございます。
本記事は型チェッカーに何を使うのがベストかという類のものではないのですが、勉強になりました。
ネストしたいなら(というか、オブジェクトのメンバの値の型を厳密に定義したいなら)TypedDictではなく
class
を使うべきだと思います。記述が冗長になるという意見もあるかと思いますが、TypeScriptのそれはインライン型定義と呼べるもので、インラインで型を定義できないPythonでは現状では明示的に宣言するしかないです。
inlined TypedDictを使うような場面では、とりあえずclassに変えたらもっと開発が楽になると思います。
ありがとうございます。こんなに早く返信が返ってくるとは思わず…
参考にさせていただきます。
これの出典があれば教えてください。
PEP 308 – Conditional Expressionsにおいては可読性を重視して選択したとは書かれていないほか、Guido van Rossumは[Python-Dev] Conditional Expression Resolutionでも “
Note that all these are intentionally ugly. :)” (意図的に醜くした) と述べているようです。
コメントありがとうございます。
"Note that all these are intentionally ugly. :)"については
という文脈です。
「例として挙げたコードは意図的に変なのを選んでるよ :) 」という意図であり、構文自体の話ではありません。
Guidoの総括的な立場はここがわかりやすいと思います
彼自身はこの機能自体の追加に対して中立的な立場をとっていますが
cond ? expr1 : expr2
はC系シンタックスに馴染みのない人が分かりづらいif cond then expr1 else expr2
は始まりが分かりづらいし、新たなキーワードを追加する必要がある。とくに
if cond then expr1 else expr2
に対して "this form has theadvantage of evaluating strictly from left to right"(左から右に読むという厳格なルールがあるというアドバンテージがある)としつつも "not that that is a requirement for being Pythonic -- list comprehensions don't"(でもPythonicであるための必須条件ではない。リスト内包表記とかもそうだし)としています。
Pythonicという言葉は方々で使われてあいまいだと思うので、"The Hitchhiker's Guide to Python" の言葉を借ります。
"When a veteran Python developer (a Pythonista) calls portions of code not “Pythonic”, they usually mean that these lines of code do not follow the common guidelines and fail to express its intent in what is considered the best (hear: most readable) way."(ベテランのPython開発者(Pythonista)がコードを"Pythonic"じゃないというとき、多くの場合「コードが一般的なガイドラインに従っておらず、最適(最も可読性が高い)とされている書き方で意図を表現できていない」という意味です)
GuidoはPEP308(if三項演算子)についても "This PEP contains a concrete proposal of a fairly Pythonic syntax" (このPEPは、かなりPythonicな構文の具体的な提案だ)としており、
expr1 if cond else expr2
をPythonicだといっています。Pythonic ≒ 可読性が高い(Pythonではほとんどの場合、英語の構文に近かったりすることを可読性が高いと言ったりしますが) 構文として、ifの三項演算子が追加されています。
とはいえ、言い切るのは少し誤解を生むかもしれませんので
と修正させていただきました。
Pythonは多くのケースで可読性の高い構文を選択していてPythonicなスタイルから外れずに書かれたPythonコードが読みやすいことに異論はないのですが、if条件式に関しては既存のPythonらしさから大きく外れない構文に決着したものの、それは可読性や英語話者にとっての自然さのために積極的に選択されたものではなかったように思います。
PEP 308に付録されている議論の一部として、以下の記述があります。
この箇所の原文を書いたのはGuidoではなくTerry Reedyさんですが、(Python-Devの投稿の多くは既存の三項演算子に慣れていて不快に感じたというバイアスはあるでしょうが) 英語として自然で読みやすいというわけではなく、ドラフトの議論の一部としてPEPに(元の提案ではない)この投稿をあえて抜粋しているということはGuidoとしてもこの意見を否定していないということではないでしょうか。
そもそもこの機能についてGuidoは積極的な推進という立場ではなかったので、それはまぁ仕方ないと思います。
cond ? expr1 : expr2
if cond then expr1 else expr2
(if cond: expr1 else: expr2)
など様々な選択肢がありますが、全体的な可読性などを考慮して
expr1 if cond else expr2
になり、これが一番Pythonicであるという風にGudioが判断したのは事実です。例えばこれが日本語プログラミング言語(なでしこのようなもの)だったとして
expr1 もし cond でなければ expr2
であったとするとこれは非常に悪いsyntaxだということが、Pythonistaであっても日本語話者なら理解できると思います。この違和感の正体が、Pythonが英語オリエンテッドな言語であるということに基づいているのだと思います。
uncomfortable
であってunreadable
ではないというのもミソではないでしょうか。私もif/else式が自然言語に即することを意図した設計であるとは正直信じがたいです。
というのも
true-case
-condition
-false-case
の式順からなる三項条件分岐式をもつ言語は極めて珍しく、少なくともTIOBE INDEX上位50言語の中ではPythonだけです。多くの言語設計者は英語かそれに近い文法を持つゲルマン語派言語を第一言語としているわけで、それでも
condition
-true-case
-false-case
が支配的であることを考えればPythonの式順が彼らにとって自然であると言うことは困難でしょう。PEP308のレビュー中では"but starts with 'if', which makes the parser believe it is the start of an 'if' statement."とも言っているので実際には構文解析上の課題も強く影響していたのではないかと思います。
意図したかどうかは現在の記事では触れていませんの悪しからずです。
初期のプログラミング言語には三項条件演算子を持つものが少なく、多くの言語がCスタイルかif文に近い形を取っています。
Cの三項条件演算子について詳しい訳では無いですが、Cは数学者が設計したのでコンパイラ都合な構文が多く、そういった関係でコンピュータ的に自然な 条件 -> 値 という構文になっているのだと思います。
if文に近いタイプは、文を式として評価できるようにしたものが多いので、こちらも条件 -> 式になっています。
「ほとんどの言語が先人にならえでやってきたというだけ」であるとも言える状況だと思いますし、自然言語的に then-if-else が自然な以上 「Pythonの式順が彼らにとって自然であると言うことは困難でしょう」とは言えないと思います。
英語のPythonチュートリアルにおいて、Pythonの条件三項演算子を「読みやすい」と評しているものは多数あります。実際に調べて見て頂きたいのですが、そこら辺の温度感は伝わるかなと思います。
Pythonが自然言語に合わせて多くの仕様を決めているという事実と、Pythonの条件三項演算子が英語文法的に自然であるという事実は揺るぎません。
頂いた引用の直後に"To resolve this, the syntax would have to require parentheses, which makes it uglier." とも述べられており、対策はあるが醜い(可読性が悪い)ということは、最終的な判断基準は可読性なのだと思います。
さらに言うと、頂いた引用部は「thenキーワードを追加するという問題以外を無視したとしても」という前置きがあり、構文解析上の問題は寧ろ小さなものとして扱われていたのだと思います。
Python の仕様や背景にある思想にも触れていて興味深かったです。
細かいですが以下の点は 式 と 文 が逆ではないでしょうか。
あ、失礼しました...
修正しました。
ありがとうございます
これはただの補足情報です。mypyなどを使った結果でも良いのでしたら定数の定義はtyping.Finalの利用が対応すると思います。
確かにそうですね、Finalの使用もありでした。補足しておきます。