Python の式とステートメント: 違いは何ですか?
Pythonをしばらく作業した後、最終的には、式と statement の2つの類似の用語に出くわします。公式のドキュメントを閲覧したり、オンラインフォーラムでPython関連のスレッドを掘り下げたりすると、人々がこれらの用語を同じ意味で使用しているという印象が得られる場合があります。それはしばしば真実ですが、紛らわしいほど、表現とステートメントの区別が重要になる場合があります。
では、Python の式とステートメントの違いは何でしょうか?
要約: 式には値があり、ステートメントは副作用を引き起こす
Python 用語集を開くと、次の 2 つの定義が見つかります。
式: 何らかの値に評価できる構文の一部。 (…) (ソース)
ステートメント: ステートメントはスイート (コードの「ブロック」) の一部です。ステートメントは、式、またはキーワード (…) (出典) を含む複数の構成要素の 1 つです。
まあ、それは特に役に立ちませんか?幸いなことに、表現と声明に関する最も重要な事実を要約することができます。
- Pythonのすべての指示は、声明の広範なカテゴリに該当します。
- この定義により、すべての式も声明であり、場合によっては式と呼ばれることがあります。
- すべてのステートメントが式であるわけではありません。
技術的な意味では、コードのすべての行またはブロックは Python のステートメントです。これには、 特別な種類のステートメントを表す式が含まれます。式を特別なものにするものは何ですか?今すぐわかります。
式: 値を含むステートメント
基本的に、プログラム全体の動作を変更することなく、コード内のすべての式を実行時に生成される計算値に置き換えることができます。一方、ステートメントは式でない限り、同等の値に置き換えることはできません。
次のコードスニペットを検討してください。
>>> x = 42
>>> y = x + 8
>>> print(y)
50
この例では、コードの 3 行すべてにステートメントが含まれています。最初の 2 つは代入ステートメントで、3 つ目は print()
関数の呼び出しです。
各行を詳しく見ると、対応するステートメントをサブコンポーネントに分解し始めることができます。たとえば、代入演算子 (=
) は、左側と右側の部分で構成されます。等号の左側の部分は、x
や y
などの変数名を示し、右側の部分はその変数に割り当てられる値です。
値はここで重要です。変数 x
には、リテラル値 42
が割り当てられていることに注意してください。これはコードに焼き付けられています。対照的に、次の行は、変数 y
に算術式 x + 8
を割り当てます。 Pythonは、最初にこのような式を計算または評価して、プログラムが実行されているときに変数の最終値を決定する必要があります。
算術式は Python 式の一例にすぎません。その他には、論理式、条件式などが含まれます。それらに共通しているのは、 評価の対象となる値です。ただし、それぞれの値は通常異なります。その結果、任意の式を対応する値に安全に置き換えることができます。
>>> x = 42
>>> y = 50
>>> print(y)
50
この短いプログラムは以前と同じ結果をもたらし、前のプログラムと機能的に同一です。手作業で算術式を計算し、その場所に結果の値を挿入しました。
x + 8
は評価できますが、代入 y=x + 8
は式が組み込まれていても同じことはできないことに注意してください。コード行全体は、 固有の値を持たない純粋なステートメントを表します。では、そのような発言には何の意味があるのでしょうか? Python ステートメントを詳しく調べてみましょう。
ステートメント:副作用のある指示
式ではないステートメントは副作用を引き起こし、プログラムの状態を変更したり、ディスク上のファイルなどの外部リソースに影響を与えたりします。たとえば、変数に値を割り当てるときは、Python のメモリのどこかでその変数を定義または再定義します。同様に、print()
を呼び出すと、標準出力ストリーム (stdout) に効果的に書き込み、デフォルトで画面にテキストが表示されます。
注: ステートメントには式が含まれますが、ほとんどの人は、値のない純粋なステートメントや指示を指す場合に、ステートメントという単語を非公式に使用します。
わかった。表現ではない表現と声明である声明を取り上げました。これからは、それぞれ純粋な表現と純粋な声明と呼ぶことができます。しかし、ここには中央の根拠があることがわかりました。
一部の命令は値を持ち、同時に副作用を引き起こす可能性があります。言い換えれば、 これらは副作用のある式、つまり値を伴うステートメントです。その代表的な例は、言語に組み込まれた Python の next()
関数です。
>>> fruit = iter(["apple", "banana", "orange"])
>>> next(fruit)
'apple'
>>> next(fruit)
'banana'
>>> next(fruit)
'orange'
ここでは、 Fruit
という名前のイテレーターオブジェクトを定義します。これにより、フルーツ名のリストを繰り返します。このオブジェクトで next()
を呼び出すたびに、Iteratorをリストの次のアイテムに移動することにより、内部状態を変更します。それがあなたの副作用です。同時に、 next()
対応するフルーツ名を返します。これは方程式の値部分です。
一般に、単一の式の中で値と副作用を混在させないことがベスト プラクティスと考えられています。関数型プログラミングでは純粋関数の使用が推奨されますが、手続き型言語では値を返す関数と値を返さないプロシージャが区別されます。
注: Pythonでは、すべての関数が値を返します。関数の1つが return
ステートメントが含まれていない場合でも、pythonは関数を暗黙的に返す none
にします。
最後に、これは最初は直感に反しているように聞こえるかもしれませんが、Pythonには何も評価したり、副作用を引き起こしたりしない声明があります。あなたはそれが何であるか、そしてあなたがそれを使用したいときに推測できますか?少しヒントを与えるために、それは一般にコンピューターサイエンスのノーオプとして知られています。
何だと思う? Python pass
ステートメントです!声明が構文的に必要な場所でプレースホルダーとして使用することがよくありますが、行動を起こしたくありません。これらのプレースホルダーは、開発の初期段階で空の関数定義またはループで使用できます。 Pythonの ellipsis
( ...
)が同様の目的を果たす可能性があるにもかかわらず、 ellipsis
を式にすることです。
式とステートメントの概要
要点を理解するには、次の図を簡単に見てください。これは、Python のさまざまな種類の式やステートメントをより深く理解するのに役立ちます。
要約すると、ステートメントは、単一行であろうとコードのブロックであろうと、Python命令になります。上記の図の4つの4つの象限はすべて、ステートメントを表しています。ただし、この用語は、値を持たずに副作用のみを引き起こす純粋なステートメントを参照するためによく使用されます。
対照的に、a 純粋な表現は、副作用を引き起こすことなく何らかの価値を評価する特別な種類の声明です。さらに、副作用を備えた式に触れることができます。これは、値のあるステートメントです。これらは、副作用を引き起こしながら値を生成する式です。最後に、a no-op はどちらもしないステートメントです。価値を生み出したり、副作用を引き起こしたりしません。
次に、野生の動物を識別する方法を学びます。
Python 命令が式であるかステートメントであるかを確認する方法
この時点で、すべての Python 命令は技術的には常にステートメントであることはすでにわかっています。したがって、より具体的に自問したいのは、式を扱っているのか、 それとも純粋なステートメントを扱っているのかということです。この質問には 2 つの方法で答えます。手動で、次に Python を使用してプログラムで答えます。
Python Replで手動で確認します
このセクションで提起された質問に対する答えをすばやく決定するには、Python Replの力を活用できます。PythonReplは、式を入力するときに評価を評価します。命令がたまたま式である場合、出力の値のデフォルト文字列表現をすぐに表示します。
>>> 42 + 8
50
これは算術式であり、50
と評価されます。たとえば、式を変数に割り当てたり、引数として関数に渡したりすることによってその値をインターセプトしていないため、Python は計算結果を表示します。同じコード行が Python スクリプトに存在していれば、インタープリターは評価値を無視し、評価値は失われていたでしょう。
対照的に、REPL で純粋なステートメントを実行すると、出力には何も表示されません。
>>> import math
>>>
これは import
ステートメントであり、対応する値はありません。代わりに、指定されたPythonパッケージを副作用として現在の名前空間にロードします。
ただし、このアプローチには注意が必要です。場合によっては、計算値のデフォルトの文字列表現が誤解を招く可能性があります。次の例を考えてみましょう。
>>> fruit = {"name": "apple", "color": "red"}
>>> fruit.get("taste")
>>> fruit.get("color")
'red'
果物を表す Python 辞書を定義します。初めて .get()
を呼び出したときは、表示される出力はありません。しかし、その後、別のキーを使用して .get()
を再度呼び出すと、そのキーに関連付けられた値 ('red'
) が返されます。
最初のケースでは、辞書にキー "taste"が含まれていないため、
などの存在するキーを使用すると、メソッドは対応する値を返します。.get()
はキーと値のペアが欠落していることを示す None
を返します。code>。ただし、"color"
Python REPL では、明示的に出力しない限り None
が表示されることはありません。この動作を知らないと混乱を招く可能性があります。疑わしい場合は、いつでも結果に対して print()
を呼び出して、その真の値を明らかにすることができます。
>>> fruit.get("taste")
>>> print(fruit.get("taste"))
None
これは機能しますが、命令が Python の式であるかどうかを確認する、より信頼性の高い方法があります。
命令を変数に割り当てることができ、R値のかどうかを確認できます。それ以外の場合、それが純粋なステートメントである場合、変数の値として使用することはできません。このエラーが発生します。
>>> x = import math
File "<python-input-0>", line 1
x = import math
^^^^^^
SyntaxError: invalid syntax
Python は SyntaxError
を発生させて、import
ステートメントには具体的な値がないため変数に代入できないことを示します。
やや特別なケースはチェーンの割り当てです。これは、 y=42
を変数 x
に割り当てようとしているように見えるかもしれません。
>>> x = y = 42
ただし、これにより、複数の変数 (この場合は x
と y
) が同じ値 42
を参照するようになります。 これは、x=42
と y=42
という 2 つの別々の代入を行うための簡略表記です。
Python コードの一部が式であるか純粋なステートメントであるかを判断するもう 1 つの方法は、 コードを括弧で囲むことです。通常、括弧は、用語をグループ化し、演算子の優先順位によって決定されるデフォルトの演算順序を変更するのに役立ちます。ただし、ラップできるのは式のみであり、ステートメントはラップできません。
>>> (2 + 2)
4
>>> (x = 42)
File "<python-input-7>", line 1
(x = 42)
^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
行1 には式が含まれています。これは4つに評価されますが、行4 は構文エラーにつながります。Pythonは割り当てステートメントを評価しようとしないためです。表示されたエラーメッセージは、割り当て演算子(=
)を値の等まで( ==
)またはセイウチのオペレーター(:=
に変更するように求められます。 )。
これまで、Python REPL で式とステートメントを手動で比較してきました。次のセクションでは、これをプログラムで行う方法を学習します。これは、自動化したい反復的なタスクの場合に役立ちます。
Python 式とステートメント検出器の構築
命令が式であるかステートメントであるかをプログラム的に判断するには、Python の eval()
および exec()
組み込み関数を呼び出すことができます。最初の関数では式を動的に評価でき、2 番目の関数では文字列から任意のコードを実行できます。
>>> eval("2 + 2")
4
>>> exec("x = 42")
>>> print(x)
42
string "2 + 2"
には、 eval()
がinteger 4
を評価するpython式が含まれています。必要に応じて、 eval()
によって返された値を変数に割り当てることができます。一方、 exec()
は何も返さず、副作用を引き起こします。同じ範囲内でこの関数を呼び出すと、 exec()
に渡された文字列で定義された変数にアクセスする方法に注意してください。
入力文字列が有効な式またはステートメントではない場合、どちらの関数も構文エラーを報告します。
>>> eval("2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
さらに、 eval()
は、付属の文字列に値のない純粋なステートメントが含まれている場合に構文エラーを発生させます。同時に、同じステートメントは exec()
で正常に機能します。
>>> eval("x = 2 + 2")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 + 2")
この矛盾を利用して、表現と純粋な声明を区別できます。ただし、 exec()
の前に eval()
を呼び出すことを忘れないでください。すべての式はステートメントであるため、 exec()
は、式または声明を引数として受信するかどうかにかかわらず、ほとんど常に成功します。
>>> eval("print('Hello, World!')")
Hello, World!
>>> exec("print('Hello, World!')")
Hello, World!
どちらの関数でも、Hello World!
という同じ結果が得られます。 exec()
を呼び出してそこで停止した場合、コードは有効な式であるにもかかわらず、誤ってステートメントであると結論付ける可能性があります。一方、Python のすべての関数呼び出しは技術的には式であり、そのように分類される必要があります。これに対処する方法は次のとおりです。
コードの重複を避けるために、eval()
と exec()
の両方を 1 つの関数に結合し、ast.parse()
に委譲することができます。適切なモードで:
>>> import ast
>>> def valid(code, mode):
... try:
... ast.parse(code, mode=mode)
... return True
... except SyntaxError:
... return False
...
>>> valid("x = 2 + 2", mode="eval")
False
>>> valid("x = 2 + 2", mode="exec")
True
この関数は、python code
snippetとモード
パラメーターを受け入れます。 「。 関数は、基礎となる式またはステートメントが構文的に正しくない場合、 false
を返します。それ以外の場合、指定されたモードでコードが正常に実行されると、 true
を返します。
さらに、指定されたコード スニペットのテキスト表現を提供する別のヘルパー関数を作成できます。
>>> def describe(code):
... if valid(code, mode="eval"):
... return "expression"
... elif valid(code, mode="exec"):
... return "statement"
... else:
... return "invalid"
...
>>> describe("x = 2 + 2")
'statement'
>>> describe("2 + 2")
'expression'
>>> describe("2 +")
'invalid'
最初に、指定されたコードが式であるかどうかを確認します。そうであれば、文字列 "expression"
を返します。そうでない場合は、コードがステートメントであるかどうかを確認できます。ステートメントとしての資格がある場合は、文字列 "ステートメント"
を返します。最後に、どちらの条件も満たされない場合、コードは「無効」
であると結論付けます。
このアプローチは、何らかの理由でプログラムでそのようなテストを実行する必要がある場合に役立ちます。たぶん、Pythonで構造パターンマッチングを使用して独自のPython Replを構築し、評価された式をいつ表示するかを決定する必要があります。
注:構文の一部が式であるか、ステートメントが石に設定されていないか。これの注目すべき例は、 print()
関数です。これは、レガシーPython 2の print
ステートメントでした。
ここまでで、Python の式とステートメントの違いをより直観的に理解できるはずです。どれがどれであるかもわかるはずです。次に重要な疑問は、それが純粋主義者にとっての意味上の区別にすぎないのか、それとも日常のコーディング実践において何らかの重要性を持っているのかということです。今すぐわかるでしょう!
この違いは日常のプログラミングに影響しますか?
ほとんどの場合、Python で式を扱うかステートメントを扱うかについて、あまり考える必要はありません。そうは言っても、言及する価値のある 2 つの注目すべき例外があります。
ラムダ
式assert
ステートメント
最初のものから始めます。これは lambda
式です。
ラムダ式
Python の lambda
キーワードを使用すると、匿名関数を定義できます。これは、並べ替えキーやフィルター条件の指定などのワンショット操作に便利です。
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(fruits, key=lambda item: item[1])
[('banana', 0), ('apple', 2), ('orange', 3)]
>>> list(filter(lambda item: item[1] > 0, fruits))
[('apple', 2), ('orange', 3)]
ここでは、果物の名前とそれぞれの量を含む2つの要素のタプルのリストを定義します。次に、数量に基づいてこのリストを昇順で並べ替えます。最後に、リストをフィルタリングし、少なくとも1つのユニットを持つ果物のみを残します。
これは、そのようなインライン関数を即時使用しないことを参照する必要がない限り、便利なアプローチです。 lambda式を常に使用するために変数に割り当てることはできますが、 def
で定義された通常の関数と構文的に同等ではありません。
a 関数定義関数の本体を表す新しいコードブロックを開始します。 Pythonでは、ほぼすべてのコードブロックが複合ステートメントに属しているため、評価することはできません。
注:次のセクションでは、簡単な声明と複合声明について詳しく知ります。
ステートメントには値がないため、Lambda式でできるように、機能定義を変数に割り当てることはできません。
>>> inline = lambda: 42
>>> regular = (
... def function():
... return 42
... )
...
File "<python-input-1>", line 2
def function():
^^^
SyntaxError: invalid syntax
変数 inline
は、 lambda
として定義された匿名関数への参照を保持しますが、 regular
は、名前の関数定義をAに割り当てることに失敗した試みです。変数。
ただし、 すでに別の場所で定義されている関数への参照を割り当てることは可能です。
>>> def function():
... return 42
...
>>> alias = function
def function():
ステートメントとその下に表示される function
参照の意味が異なることに注意してください。 1 つ目は関数の動作の青写真で、2 つ目は関数のアドレスで、実際に関数を呼び出さずに渡すことができます。
注:関数を値として扱う能力は、機能プログラミングパラダイムの重要な側面です。関数の構成と高次関数の作成を可能にします。
lambda
キーワードで定義された関数には、その体内に常に 1つの式を含める必要があります。 return
ステートメントを含めることなく、評価および暗黙的に返されます。実際、 lambda
関数でのステートメントの使用は完全に禁止されています。それらを使用しようとすると、構文エラーが発生します。
>>> lambda: pass
File "<python-input-0>", line 1
lambda: pass
^^^^
SyntaxError: invalid syntax
pass
はステートメントであることがわかったため、 lambda
式には場所がありません。ただし、そのためのワークアラウンドがあります。あなたはいつでも関数に1つ以上のステートメントをラップし、あなたのラムダ式でその関数を呼び出すことができます:そうです:
>>> import tkinter as tk
>>> def on_click(age):
... if age > 18:
... print("You're an adult.")
... else:
... print("You're a minor.")
...
>>> window = tk.Tk()
>>> button = tk.Button(window, text="Click", command=lambda: on_click(42))
>>> button.pack(padx=10, pady=10)
>>> window.mainloop()
これは、Tkinter で構築されたグラフィカル ユーザー インターフェイス (GUI) を特徴とする最小限の Python アプリケーションです。強調表示された行は、ボタンのクリック イベントのリスナーを登録します。イベント ハンドラーはインライン ラムダ式として定義され、条件文をカプセル化するラッパー関数を呼び出します。このような複雑なロジックをラムダ式だけで表現することはできません。
アサートステートメント
assert
ステートメントに関しては、それを取り巻く共通の混乱のポイントがあります。これは、やや誤解を招く構文に由来しています。 assert
はステートメントであり、通常の関数呼び出しのように振る舞わないことを忘れがちです。これは、注意しないと意図しない動作を引き起こすことがあります。
最も基本的な形式では、 assert
キーワードの後に論理述語またはブール値に評価される式を続ける必要があります。
>>> assert 18 < int(input("What's your age? "))
What's your age? 42
>>> assert 18 < int(input("What's your age? "))
What's your age? 15
Traceback (most recent call last):
...
AssertionError
表現が真実になってしまえば何も起こりません。式が false の場合、コマンドライン オプションまたは環境変数でアサーションを完全に無効にしていない限り、Python は AssertionError
を発生させます。
オプションで、表示されたアサーションエラーとともに表示されるカスタムエラーメッセージを追加できます。それを行うには、述語の後にコンマを配置し、文字通りの文字列、または1つに評価する式を含めます。
>>> assert 18 < int(input("What's your age? ")), "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
ここまでは順調ですね。ただし、長いエラー メッセージを組み込むとコードが読みにくくなり、何らかの方法で改行したくなる可能性があります。
Pythonは、暗黙の線の継続と呼ばれるきちんとした機能を提供します。これにより、明示的なバックスラッシュ(\
)を使用せずに、複数の行に長い命令を分割できます。これは、括弧、 brackets 、または curly braces で式を囲むことで、これを達成できます。
>>> assert (
... 18 < int(input("What's your age? ")),
... "You're a minor"
... )
<python-input-0>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
assert (
What's your age? 15
>>>
これは通常は機能しますが、この場合は機能しません。ご覧のとおり、最新の Python バージョンでは、おそらく何かが間違っていることを知らせる警告も発行されます。
assert
ステートメントは、キーワードの直後に式を期待することを忘れないでください。括弧で述語とカスタムメッセージを囲むと、単一の式として扱われるタプルを本質的に定義します。 Pythonは、このような空でないシーケンスを true
に評価します。したがって、 assert
ステートメントは、確認しようとしている実際の条件に関係なく、常に渡されます。
この問題を回避するには、長い行を破りたいときに明示的なラインの継続を使用する方が良いでしょう。
>>> assert 18 < int(input("What's your age? ")), \
... "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
これで、述語が正しく評価されるため、予想される動作を再度観察します。
以前は、Pythonのコードブロックが複合ステートメントの一部であることを学びました。次に、Pythonの単純なステートメントと複合ステートメントの違いを調べます。
Pythonのシンプルで複合声明は何ですか?
ステートメントは、Pythonプログラムのコアビルディングブロックです。実行の流れを制御し、変数に値を割り当てるなどのアクションを実行できます。ステートメントを2つの主要なタイプに分類できます。
- 単純なステートメント
- 複合文
単純なステートメントは 1 行に収まりますが、複合ステートメントは通常、改行文字が後に続くインデントされたコードの複数行にわたる他のステートメントで構成されます。
以下の表は、両方のタイプのステートメントの一般的な例をいくつか示しています。
assert age > 18
18歳の場合:...
import math
true:...
return 42
_ in range(3):...
pass
try:...
x = 42 + 8
def add(x、y):...
ご覧のとおり、単純なステートメントは特定の副作用を引き起こしますが、pass
ステートメントを除いて、副作用は発生しません。一方、複合ステートメントには、ループ、条件文、関数定義などの従来の制御フロー構成要素が含まれます。
複合ステートメントを詳しく見ると、ヘッダーと suite を含む1つ以上の条項 で構成されていることがわかります。次の条件ステートメントを例として考えてみましょう。
if age > 18:
print("Welcome!")
elif age < 18:
raise ValueError("You're too young to be here.")
else:
import sys
sys.exit(1)
強調表示された行は、複合ステートメントの条項ヘッダーを示し、残りの行は対応するスイートを表しています。たとえば、句の場合、年齢が18を超えるかどうかをチェックし、 print()
機能をスイート内の
関数を条件付きで呼び出します。
複合ステートメント内のすべての句ヘッダーは、同じインデンテーションレベルで並べられています。 、 elif 、または else
などのpythonキーワードから始め、コロン(:
)それは、スイートのコードブロックの開始をマークします。スイートは、それぞれの条項に準拠した声明のコレクションです。この場合、3つの条項は、年齢の状態に基づいて実行するスイートのどれを決定します。
ステートメントリストとして知られる特別な種類の複合ステートメントがあります。これは、一連の簡単なステートメントで構成されています。それらのそれぞれは単一の行に配置する必要がありますが、複数の簡単なステートメントを単一の行に絞ることができます。どのように知っていますか?
単一の行に複数のステートメントを配置する方法は?
ほとんどの場合、読みやすくするために、1 行に 1 つのステートメントまたは式のみを配置することをお勧めします。ただし、複数の文字を同じ行に収める必要がある場合は、ステートメントの区切り文字としてセミコロン (;
) を使用できます。
これが役立つ可能性のある人気のあるユースケースでは、
$ python -c 'import sys; print(sys.version)'
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
これにより、短いコードスニペットの形でアイデアをすばやくテストできます。しかし、最近では、ほとんどの端末により、問題なく複数の行にコードを広めることができます。
$ python -c '
> import sys
> print(sys.version)
> '
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
Pythonインタープリターが期待するコマンドは、複数の行で構成されます。
セミコロンのもう1つの一般的なユースケースは、ブレークポイントをコードに挿入することです。組み込み breakppoint()
関数を導入するPython 3.7の前に、次のイドオマチックなコードラインでデバッガーにジャンプします。
import pdb; pdb.set_trace()
インタープリターが pdb.set_trace()
に到達すると、実行が一時停止され、Python デバッガー (pdb) を使用した対話型デバッグ セッションに入ります。
さらに、このトリックを使用して、Pythonコードを意図的に難読化または削除してみてください。ただし、構文的な制限を回避することはできないため、Pyarmorのような外部ツールでより良い結果が得られます。
このチュートリアルを閉じる前に、Python の式とステートメントに関する最後の質問に答える必要があります。より高度なプログラミングの課題に取り組むための基礎を提供します。
Python ではステートメントに二重の性質があるのでしょうか?
Pythonのすべての指示はステートメントであるため、計算された値を考慮せずに副作用を持つ式を実行できます。これは、関数またはメソッドを呼び出すが、その返品値を無視する場合によくあることです。
>>> with open("file.txt", mode="w", encoding="utf-8") as file:
... file.write("Hello, World!")
...
13
上の例では、文字列 "Hello, World!"
を引数として使用して、ファイル オブジェクトの .write()
メソッドを呼び出します。このメソッドは書き込まれた文字数 (13 文字) を返しますが、戻り値を変数にインターセプトしたり、何らかの方法でさらに処理したりしないことにより、文字数を無視します。ただし、この状況では、Python REPL は最後に評価された式の結果を自動的に表示することに注意してください。
わかった。したがって、式に副作用がある場合は、その副作用のみを目的として式を利用できます。しかし、その逆はどうでしょうか?ステートメントを評価できますか?もうすぐわかります!
課題
一部のプログラミング言語は、声明と式の間の線を曖昧にします。たとえば、cまたはc ++で割り当てステートメントを評価できます。
#include <stdio.h>
int main() {
int x;
while (x = fgetc(stdin)) {
if (x == EOF)
break;
putchar(x);
}
return 0;
}
この短いプログラムは、ユーザーがキーボードで入力するものをすべてエコーします。標準入力から次の文字を取得し、ローカル変数 x
に割り当てる強調表示された行に注目してください。同時に、この割り当てはブール式として評価され、ループのの継続条件として使用されます。言い換えれば、入力文字の順序数がゼロ以外である限り、ループは続きます。
これは、年齢のCおよびC ++プログラムを悩ませてきた悪名高いバグのソースでした。プログラマーは、視覚的に類似しているため、意図した平等テストオペレーター( ==
)ではなく、そのような条件で誤って割り当て演算子(=
)を誤って使用することがよくあります。上記の例は意図的にこの機能を使用していますが、通常、予期しない行動につながる可能性があるのは通常、ヒューマンエラーの結果でした。
長い間、コア開発者は、この潜在的な混乱に関する懸念のために、Pythonで同様の機能を実装することを避けました。 Python 3.8まで、Walrusオペレーター(:=
)を導入しての割り当て式を導入しました。
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while chunk := file.read(MAX_BYTES):
buffer.extend(chunk)
このコードスニペットでは、空のチャンクで示されるファイルの終わりのコントロールバイトにつまずくまで、バイナリチャンクのWAVファイルを段階的に読み取ります。ただし、によって返されるチャンクを明示的にチェックする代わりに、または
true
を評価する場合は、割り当て式を活用して実行および評価します。 1つのステップでの割り当て。
これにより、数行のコードが節約されます。そうでなければ次のように見えます。
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while True:
chunk = file.read(MAX_BYTES)
if not chunk:
break
buffer.extend(chunk)
決定論的ループを無限ループに変更し、さらに 3 つのステートメント (代入ステートメント、条件ステートメント、および break
ステートメント) を追加しました。
Cの例とは異なり、割り当て式をその声明の対応物と混同する方法はありません。 Pythonは、ブールのコンテキストで割り当てステートメントを禁止し続けています。このような場合、Pythonの禅からの格言の1つとうまく整合するセイウチの演算子を明示的に使用する必要があります。
暗黙的よりも明示的な方が優れています。 (ソース)
Python に類似した表現を持つステートメントの例は他にもあります。次に、条件文と式について見ていきます。
条件文
多くのプログラミング言語は、論理条件、条件が True
と評価された場合の値、条件が次のように評価された場合の代替値の 3 つの要素を組み合わせる三項条件演算子を提供します。 偽
。
C-Family言語では、Ternary Operator(?:
)は、エルビスプレスリーの絵文字に似ています。これらの言語はそれをElvisオペレーターと呼んでいますが、Pythonはより保守的な用語条件付き表現に固執します。これが次のようになります:
>>> def describe(users):
... if not users:
... print("No people")
... else:
... print(len(users), "people" if len(users) > 1 else "person")
...
>>> describe([])
No people
>>> describe(["Alice"])
1 person
>>> describe(["Alice", "Bob"])
2 people
一見すると、Python の条件式は 1 行に凝縮された標準の条件式に似ています。これは、真の値に関連付けられた式で始まり、次にチェックする条件、次に偽の値に対応する式が続きます。これにより、通常は条件ステートメントの使用が必要となる条件ロジックを評価できるようになります。
内包表記
灰色の領域を占めるステートメントの別の例は、リストの理解やジェネレーター式など、あらゆる種類の理解式です。
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(name.title() for name, quantity in fruits if quantity > 0)
['Apple', 'Orange']
繰り返しますが、構文は for
ループと if
ステートメントに似ていますが、これらを 1 行に折りたたんで、個々の部分を再配置します。これは、評価する条件と式が 1 ~ 2 行に収まる程度に小さい場合にのみ機能します。そうしないと、コードが乱雑になって読みにくくなってしまいます。
最後に、Pythonジェネレーターはここで言及するに値します。なぜなら、彼らは式と声明の両方と同時に声明となる可能性のある構文を使用しているからです。
発電機
yield
および yield from
キーワードはジェネレーター関数内に表示され、これを使用すると、大規模なデータ ストリームを効率的に処理したり、コルーチンを定義したりできます。
ジェネレーター関数から値を生成する場合、Python でステートメントとしてyield
を実行できるようにします。
>>> import random
>>> def generate_noise(size):
... for _ in range(size):
... yield 2 * random.random() - 1
...
>>> list(generate_noise(3))
[0.7580438973021972, 0.5273057193944659, -0.3041263216813208]
このジェネレーターは、-1から1の間のランダム値のプロデューサーです。 rigve
を使用して、 return
ステートメントを使用することと多少似ています。副作用として一連の値を発信者に中継しますが、関数を完全に終了する代わりに、 rigves
ステートメントは関数の実行を一時停止し、再開して後で継続することができます。
これで、オプションで* rigve> code を式として評価して、発電機を潜在的に多くのエントリポイントと出口ポイントを持つProsumer(プロデューサーおよび消費者)に変えることができます。各 rigve
式は、両方の方法で値を提供および受信できます。生成された値はジェネレーターの出力を表し、ジェネレーターに返信される値は入力です。
>>> def lowpass_filter():
... a = yield
... b = yield a
... while True:
... a, b = b, (yield (a + b) / 2)
...
最初の rigve
式は暗黙的に何も生成しません( none
)が、式として評価されると外の世界から値を受け取ります。この値は、 a
という名前のローカル変数に割り当てられます。
2番目の rigve
式は a
を生成しますが、同時に b
に別の値を保存します。この時点で、フィルターは飽和状態になり、着信データを処理する準備が整います。各サスペンションポイント( ried
)で値を消費して生成する機能により、回路を通る電流が流れるようにフィルターを通して信号を押すことができます。
最後の yield
式は、両方の数値の平均を計算して求め、a
を b
で上書きし、新しい値を b に割り当てます。
。
注: ループ内の yield
式を囲むかっこは、評価順序のあいまいさを避けるために必要です。これらは、 yield
を式として扱うことが目的であり、代入の前に評価する必要があることを明示的に伝えます。
ローパスフィルターに .send()
を呼び出すことで、両方のジェネレーターを互いに接続できます。
>>> lf = lowpass_filter()
>>> lf.send(None)
>>> for value in generate_noise(10):
... print(f"{value:>5.2f}: {lf.send(value):>5.2f}")
...
0.66: 0.66
-0.01: 0.32
0.30: 0.15
-0.10: 0.10
-0.98: -0.54
0.79: -0.10
0.31: 0.55
-0.10: 0.11
-0.25: -0.18
0.57: 0.16
左の列の数字はノイズジェネレーターから直接送られ、ローパスフィルターに直接供給されます。右の列には、そのフィルターの出力が含まれています。最初に none
を送信するか、 next>
を呼び出して、最初の evelow> expressionに進むように next
を呼び出す必要があることに注意してください。
結論
このチュートリアルでは、Python の式とステートメントの基本的な違いについて説明しました。式は値として評価される構文の一部であり、 ステートメントは副作用を引き起こす可能性のある命令であることを学びました。同時に、それらの間にはグレーゾーンもあります。
この区別を理解することは、Python開発者にとって重要です。これは、コードの作成方法と構造に影響を与えるためです。これらのスキルが整ったので、より正確で表現力のあるPythonコードを書くことができ、表現とステートメントを最大限に活用して、堅牢で効率的なプログラムを作成できます。