Python印刷機能のガイド

Python印刷機能のガイド

私を含め、ほとんどのPythonユーザーと同じようであれば、おそらく `+ print()`について学ぶことでPythonの旅を始めたでしょう。 独自の「 hello world 」ワンライナーを書くのに役立ちました。 これを使用して、フォーマットされたメッセージを画面に表示し、おそらくいくつかのバグを見つけることができます。 しかし、Pythonの ` print()+`関数について知っているだけであると思うなら、あなたは多くを見逃しています!

読み続けて、この一見退屈で評価されていない小さな機能を最大限に活用してください。 このチュートリアルでは、Pythonの `+ print()`を効果的に使用する方法について説明します。 ただし、セクションを進めながら、深く掘り下げる準備をしてください。 あなたは、 ` print()+`がどれだけ提供しているかに驚くかもしれません!

このチュートリアルの終わりまでに、次の方法を理解できます。

  • Pythonの `+ print()+`でよくある間違いを避ける

  • 改行、文字エンコード、およびバッファリングに対処する

  • テキストをファイルに書き込む

  • ユニットテストで `+ print()+`をモックする

  • ターミナルで高度なユーザーインターフェイスを構築する

あなたが完全な初心者であれば、このチュートリアルの最初の部分を読むことから最も恩恵を受けるでしょう。これは、Pythonでの印刷の本質を説明しています。 それ以外の場合は、その部分をスキップして、必要に応じて自由にジャンプしてください。

注意: `+ print()`はPython 3の主要な追加機能で、Python 2で使用可能な古い ` print +`ステートメントを置き換えました。

すぐにわかるように、それには多くの正当な理由がありました。 このチュートリアルはPython 3に焦点を当てていますが、参考のためにPythonでの古い印刷方法を示しています。

*無料ボーナス:*リンク:[ここをクリックして無料のPythonチートシートを入手してください]データ型、辞書、リスト、Python関数の操作など、Python 3の基本を示します。

簡単に印刷する

Pythonでの印刷の実際の例をいくつか見てみましょう。 このセクションの終わりまでに、 `+ print()+`を呼び出すすべての可能な方法がわかります。 または、プログラマの専門用語では、*関数のシグネチャ*に精通していると言えます。

印刷を呼び出す

Pythonの `+ print()+`を使用する最も簡単な例では、ほんの数回のキーストロークが必要です。

>>>

>>> print()

引数を渡しませんが、最後に空の括弧を付ける必要があります。これは、Pythonに参照するのではなく、実際にhttps://realpython.com/lessons/example-function/[関数の実行]を指示します。名前で。

これにより、目に見えない改行文字が生成され、画面に空白行が表示されます。 このように `+ print()+`を複数回呼び出して、垂直方向のスペースを追加できます。 ワードプロセッサでキーボードの[.keys]#Enter#を押しているようです。

今見たように、引数なしで `+ print()+`を呼び出すと*空白行*になります。これは改行文字だけで構成される行です。 これを「空行」と混同しないでください。空行には、文字もまったく含まれておらず、改行も含まれていません。

Pythonのhttps://realpython.com/python-strings/[string]リテラルを使用して、これら2つを視覚化できます。

'\n'  # Blank line
''    # Empty line

最初の文字は1文字の長さですが、2番目の文字にはコンテンツがありません。

注意: Pythonの文字列から改行文字を削除するには、次のように `+ .rstrip()+`メソッドを使用します。

>>>

>>> 'A line of text.\n'.rstrip()
'A line of text.'

これにより、文字列の右端から後続の空白が削除されます。

より一般的なシナリオでは、何らかのメッセージをエンドユーザーに伝えたいでしょう。 これを実現する方法はいくつかあります。

最初に、文字列リテラルを直接 `+ print()+`に渡すことができます:

>>>

>>> print('Please wait while the program is loading...')

これにより、メッセージがそのまま画面に表示されます。

次に、そのメッセージを意味のある名前で独自の変数に抽出して、読みやすくし、コードの再利用を促進できます。

>>>

>>> message = 'Please wait while the program is loading...'
>>> print(message)

最後に、https://realpython.com/lessons/concatenating-joining-strings-python/[string concatenation]のような式を渡して、結果を出力する前に評価することができます。

>>>

>>> import os
>>> print('Hello, ' + os.getlogin() + '! How are you?')
Hello, jdoe! How are you?

実際、Pythonでメッセージをフォーマットする方法はたくさんあります。 Python 3.6で導入されたhttps://realpython.com/python-f-strings/[f-strings]をご覧になることを強くお勧めします。これらはすべて最も簡潔な構文を提供するためです。

>>>

>>> import os
>>> print(f'Hello, {os.getlogin()}! How are you?')

さらに、f-stringsは、連結されたオペランドをキャストすることを忘れるというよくある間違いを防ぐことができます。 Pythonは強く型付けされた言語です。つまり、これを行うことはできません。

>>>

>>> 'My age is ' + 42
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    'My age is ' + 42
TypeError: can only concatenate str (not "int") to str

文字列に数字を追加しても意味がないので、それは間違っています。 それらを結合するには、まず番号を明示的に文字列に変換する必要があります。

>>>

>>> 'My age is ' + str(42)
'My age is 42'

自分でhttps://realpython.com/courses/python-exceptions-101/[そのようなエラーを処理]しない限り、Pythonインタープリターはhttps://realpython.com/python-traceback/を表示することで問題を通知します[トレースバック]。

注意: `+ str()+`は、オブジェクトを文字列表現に変換するグローバルな組み込み関数です。

数値など、任意のオブジェクトで直接呼び出すことができます。

>>>

>>> str(3.14)
'3.14'

組み込みのデータ型には、事前定義された文字列表現がすぐに使用できますが、この記事の後半で、カスタムクラスに文字列表現を提供する方法について説明します。

他の関数と同様に、リテラル、変数、または式を渡すかどうかは関係ありません。 ただし、他の多くの関数とは異なり、 `+ print()+`はタイプに関係なくすべてを受け入れます。

これまでは文字列だけを見てきましたが、他のデータ型はどうですか? さまざまな組み込み型のリテラルを試して、何が出るか見てみましょう。

>>>

>>> print(42)                            # <class 'int'>
42
>>> print(3.14)                          # <class 'float'>
3.14
>>> print(1 + 2j)                        # <class 'complex'>
(1+2j)
>>> print(True)                          # <class 'bool'>
True
>>> print([1, 2, 3])                     # <class 'list'>
[1, 2, 3]
>>> print((1, 2, 3))                     # <class 'tuple'>
(1, 2, 3)
>>> print({'red', 'green', 'blue'})      # <class 'set'>
{'red', 'green', 'blue'}
>>> print({'name': 'Alice', 'age': 42})  # <class 'dict'>
{'name': 'Alice', 'age': 42}
>>> print('hello')                       # <class 'str'>
hello

ただし、定数「+ None 」に注意してください。 値がないことを示すために使用されていますが、空の文字列ではなく「 'None' +」として表示されます。

>>>

>>> print(None)
None

`+ print()`は、これらすべての異なる型を扱う方法をどのように知っていますか? まあ、短い答えはそうではないということです。 暗黙的に ` str()+`を呼び出して、オブジェクトを文字列に型キャストします。 その後、文字列を均一に処理します。

このチュートリアルの後半では、このメカニズムを使用して、クラスなどのカスタムデータ型を印刷する方法を学習します。

さて、1つの引数または引数なしで `+ print()+`を呼び出すことができるようになりました。 固定またはフォーマットされたメッセージを画面に印刷する方法を知っています。 次のサブセクションでは、メッセージのフォーマットについて少し説明します。

複数の引数の分離

`+ print()+`が引数なしで呼び出されて空白行が生成され、単一の引数で呼び出されて固定メッセージまたはフォーマットされたメッセージが表示されるのを見ました。

ただし、この関数は、0個、1個、またはそれ以上の引数を含む、 positional arguments をいくつでも受け入れることができます。 これは、いくつかの要素を結合したい一般的なメッセージフォーマットの場合に非常に便利です。

この例を見てみましょう。

>>>

>>> import os
>>> print('My name is', os.getlogin(), 'and I am', 42)
My name is jdoe and I am 42

`+ print()`は渡された4つの引数すべてを連結し、それらの間に1つのスペースを挿入したため、 ` 'My name isjdoeand I am42' +`のようなつぶれたメッセージが表示されなくなりました。

また、各引数を結合する前に、暗黙的に各引数で `+ str()+`を呼び出すことにより、適切な型キャストを処理していることに注意してください。 前のサブセクションから思い出すと、単純な連結は、互換性のないタイプが原因で簡単にエラーになる可能性があります。

>>>

>>> print('My age is: ' + 42)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    print('My age is: ' + 42)
TypeError: can only concatenate str (not "int") to str

可変数の位置引数を受け入れることとは別に、 `+ print()`は4つの名前付き引数または*キーワード引数*を定義します。これらはすべてデフォルト値を持っているためオプションです。 インタラクティブなインタープリターから ` help(print)+`を呼び出すと、簡単なドキュメントを表示できます。

とりあえず、 + sep +`に注目しましょう。 これは separator の略で、デフォルトで単一のスペース( `+ '' +)が割り当てられます。 要素を結合する値を決定します。

文字列か `+ None +`のどちらかでなければなりませんが、後者はデフォルトのスペースと同じ効果があります:

>>>

>>> print('hello', 'world', sep=None)
hello world
>>> print('hello', 'world', sep=' ')
hello world
>>> print('hello', 'world')
hello world

セパレータを完全に抑制したい場合は、代わりに空の文字列( + '' +)を渡す必要があります。

>>>

>>> print('hello', 'world', sep='')
helloworld

`+ print()+`で引数を個別の行として結合したい場合があります。 その場合は、前述のエスケープされた改行文字を渡すだけです。

>>>

>>> print('hello', 'world', sep='\n')
hello
world

`+ sep +`パラメータのより便利な例は、ファイルパスのようなものを印刷することです。

>>>

>>> print('home', 'user', 'documents', sep='/')
home/user/documents

セパレーターは要素の周りではなく要素の間にあるため、何らかの方法でそれを考慮する必要があることに注意してください。

>>>

>>> print('/home', 'user', 'documents', sep='/')
/home/user/documents
>>> print('', 'home', 'user', 'documents', sep='/')
/home/user/documents

具体的には、最初の位置引数にスラッシュ文字( /)を挿入するか、最初の引数として空の文字列を使用して先頭のスラッシュを強制できます。

*注意:*リストまたはタプルの要素の結合には注意してください。

少なくとも1つの要素が文字列でない場合、手動で実行すると、よく知られている `+ TypeError +`が発生します。

>>>

>>> print(' '.join(['jdoe is', 42, 'years old']))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    print(','.join(['jdoe is', 42, 'years old']))
TypeError: sequence item 1: expected str instance, int found

スター演算子( + * +)でシーケンスをアンパックし、 `+ print()+`で型キャストを処理する方が安全です。

>>>

>>> print(*['jdoe is', 42, 'years old'])
jdoe is 42 years old

展開は、リストの個々の要素で `+ print()+`を呼び出すのと事実上同じです。

もう1つの興味深い例は、データをhttps://realpython.com/courses/reading-and-writing-csv-files/[comma-separated values](CSV)形式にエクスポートすることです。

>>>

>>> print(1, 'Python Tricks', 'Dan Bader', sep=',')
1,Python Tricks,Dan Bader

これは、コンマを正しくエスケープするなどのエッジケースを正しく処理しませんが、単純なユースケースでは処理する必要があります。 上記の行は、ターミナルウィンドウに表示されます。 ファイルに保存するには、出力をリダイレクトする必要があります。 このセクションの後半では、 `+ print()+`を使用して、Pythonから直接ファイルにテキストを書き込む方法について説明します。

最後に、「+ sep +」パラメーターは単一の文字のみに制限されません。 任意の長さの文字列で要素を結合できます。

>>>

>>> print('node', 'child', 'child', sep=' -> ')
node -> child -> child

次のサブセクションでは、 `+ print()+`関数の残りのキーワード引数について説明します。

改行の防止

メッセージを末尾の改行で終わらせたくない場合があります。これにより、以降の `+ print()+`の呼び出しが同じ行で継続されます。 古典的な例には、長時間実行される操作の進行状況の更新や、ユーザーに入力を求めるプロンプトが含​​まれます。 後者の場合、ユーザーは同じ行に回答を入力する必要があります。

Are you sure you want to do this? [y/n] y

多くのプログラミング言語は、標準ライブラリを通じて `+ print()`に似た関数を公開していますが、改行を追加するかどうかを決定できます。 たとえば、JavaとC#には2つの異なる関数がありますが、他の言語では文字列リテラルの末尾に明示的に「 \ n +」を追加する必要があります。

そのような言語の構文の例をいくつか示します。

Language Example

Perl

print "hello world\n"

C

printf("hello world\n");

C++

std::cout << "hello world" << std::endl;

対照的に、Pythonの `+ print()`関数は、尋ねることなく常に ` \ n `を追加します。これはほとんどの場合に必要なことだからです。 それを無効にするには、行の終わりを指示する別のキーワード引数 ` end +`を利用できます。

セマンティクスの点では、 `+ end `パラメーターは以前見た ` sep +`パラメーターとほとんど同じです。

  • 文字列または `+ None +`でなければなりません。

  • 任意に長くすることができます。

  • デフォルト値は `+ '\ n' +`です。

  • 「+ None +」に等しい場合、デフォルト値と同じ効果があります。

  • 空の文字列( + '' +)と等しい場合、改行を抑制します。

これで、引数なしで `+ print()`を呼び出すと、内部で何が起こっているのかがわかります。 関数に位置引数を指定しないので、結合するものは何もないので、デフォルトのセパレータはまったく使用されません。 ただし、デフォルト値の「 end +」が引き続き適用され、空白行が表示されます。

*注意: *`+ end +`パラメータがオペレーティングシステムで意味のあるものではなく、固定のデフォルト値を持っている理由を疑問に思うかもしれません。

まあ、 `+ print()`は自動的に変換を処理するので、異なるオペレーティングシステムでの改行表現について心配する必要はありません。 文字列リテラルでは常に「 \ n +」エスケープシーケンスを使用することを忘れないでください。

これは現在、Pythonで改行文字を印刷する最も移植性の高い方法です。

>>>

>>> print('line1\nline2\nline3')
line1
line2
line3

たとえば、LinuxマシンでWindows固有の改行文字を強制的に印刷しようとすると、出力が壊れてしまいます。

>>>

>>> print('line1\r\nline2\r\nline3')


line3

反対に、 `+ open()`で読み込むためにファイルを開くとき、改行表現も気にする必要はありません。 この関数は、システム固有の改行をすべての「」\ n '+ `に変換します。 同時に、本当に必要な場合は、入力と出力の両方で改行を処理する方法を制御できます。

改行を無効にするには、 `+ end +`キーワード引数で空の文字列を指定する必要があります:

print('Checking file integrity...', end='')
# (...)
print('ok')

これらは2つの別個の `+ print()+`呼び出しであり、長時間実行される可能性がありますが、最終的には1行しか表示されません。 まず、次のようになります。

Checking file integrity...

ただし、 `+ print()+`の2回目の呼び出しの後、同じ行が画面に表示されます。

Checking file integrity...ok

`+ sep `と同様に、 ` end +`を使用して個々のピースをカスタムセパレーターを使用して大きなテキストの塊に結合できます。 ただし、複数の引数を結合する代わりに、各関数呼び出しからのテキストを同じ行に追加します。

print('The first sentence', end='. ')
print('The second sentence', end='. ')
print('The last sentence.')

これらの3つの命令は、1行のテキストを出力します。

The first sentence. The second sentence. The last sentence.

2つのキーワード引数を混在させることができます。

print('Mercury', 'Venus', 'Earth', sep=', ', end=', ')
print('Mars', 'Jupiter', 'Saturn', sep=', ', end=', ')
print('Uranus', 'Neptune', 'Pluto', sep=', ')

テキストが1行だけでなく、すべてのアイテムがコンマで区切られています。

Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto

改行文字の周りに余分なパディングを追加して使用することを止めるものは何もありません。

print('Printing in a Nutshell', end='\n* ')
print('Calling Print', end='\n *')
print('Separating Multiple Arguments', end='\n* ')
print('Preventing Line Breaks')

次のテキストを印刷します。

Printing in a Nutshell
 *Calling Print
* Separating Multiple Arguments
 * Preventing Line Breaks

ご覧のとおり、キーワード引数「+ end +」は任意の文字列を受け入れます。

*注意:*テキストファイル内の行をループすると、独自の改行文字が保持され、 `+ print()+`関数のデフォルトの動作と組み合わされて、冗長な改行文字が生成されます。

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line)
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod

tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,

quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

テキストの各行の後に2つの改行があります。 この記事で前述したように、行を印刷する前に、そのうちの1つを削除する必要があります。

print(line.rstrip())

または、コンテンツに改行を保持し、 `+ print()`によって自動的に追加される改行を抑制することもできます。 そのためには、キーワード引数「 end +」を使用します。

>>>

>>> with open('file.txt') as file_object:
...     for line in file_object:
...         print(line, end='')
...
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo

空の文字列で行を終了することにより、いずれかの改行を事実上無効にします。

Pythonでの印刷に慣れてきましたが、まだ役に立つ情報がたくさんあります。 次のサブセクションでは、 `+ print()+`関数の出力をインターセプトしてリダイレクトする方法を学習します。

ファイルへの印刷

信じられないかもしれませんが、 `+ print()+`は、メッセージを画面上のテキストに変換する方法を知りません。率直に言って、それをする必要はありません。 これは、バイトを理解し、それらをプッシュする方法を知っている、コードの下位層の仕事です。

`+ print()+`はこれらのレイヤーの抽象化であり、実際の印刷をストリームまたは*ファイルのようなオブジェクト*に委任するだけの便利なインターフェースを提供します。 ストリームは、ディスク上の任意のファイル、ネットワークソケット、またはおそらくメモリ内バッファです。

これに加えて、オペレーティングシステムによって提供される3つの標準ストリームがあります。

  1. * + stdin +:*標準入力

  2. * + stdout +:*標準出力

  3. * + stderr +:*標準エラー

Pythonでは、組み込みの `+ sys +`モジュールを介してすべての標準ストリームにアクセスできます。

>>>

>>> import sys
>>> sys.stdin
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>
>>> sys.stdin.fileno()
0
>>> sys.stdout
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
>>> sys.stdout.fileno()
1
>>> sys.stderr
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
>>> sys.stderr.fileno()
2

ご覧のとおり、これらの事前定義された値は、 `+ mode `および ` encoding `属性だけでなく、 ` .read()`および ` .write()+`メソッドを持つファイルのようなオブジェクトに似ています。

デフォルトでは、 `+ print()`は ` file `引数を介して ` sys.stdout +`にバインドされますが、変更できます。 そのキーワード引数を使用して、書き込みモードまたは追加モードで開いていたファイルを示し、メッセージがそのファイルに直接送られるようにします。

with open('file.txt', mode='w') as file_object:
    print('hello world', file=file_object)

これにより、コードはオペレーティングシステムレベルでのストリームリダイレクトの影響を受けなくなります。これは望ましい場合と望ましくない場合があります。

Pythonでのファイルの操作の詳細については、https://realpython.com/read-write-files-python [ Pythonでのファイルの読み取りと書き込み(ガイド)]。

*注意:*テキストにのみ適しているため、バイナリデータの書き込みに `+ print()+`を使用しないでください。

バイナリファイルの `+ .write()+`を直接呼び出すだけです:

with open('file.dat', 'wb') as file_object:
    file_object.write(bytes(4))
    file_object.write(b'\xff')

標準出力に生のバイトを書きたい場合、 `+ sys.stdout +`は文字ストリームであるため、これも失敗します。

>>>

>>> import sys
>>> sys.stdout.write(bytes(4))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: write() argument must be str, not bytes

代わりに、基礎となるバイトストリームのハンドルを取得するには、さらに掘り下げる必要があります。

>>>

>>> import sys
>>> num_bytes_written = sys.stdout.buffer.write(b'\x41\x0a')
A

これは、大文字の `+ A +`と改行文字を出力します。これらは、ASCIIの65と10の10進値に対応します。 ただし、バイトリテラルの16進表記を使用してエンコードされます。

`+ print()`はhttps://realpython.com/python-encodings-guide/[character encoding]を制御できないことに注意してください。 受信したUnicode文字列をバイトに正しくエンコードするのはストリームの責任です。 ほとんどの場合、デフォルトのUTF-8が必要なため、自分でエンコードを設定することはありません。 おそらくレガシーシステムで本当に必要な場合は、 ` open()`の ` encoding +`引数を使用できます。

with open('file.txt', mode='w', encoding='iso-8859-1') as file_object:
    print('über naïve café', file=file_object)

ファイルシステムのどこかに実際のファイルが存在する代わりに、コンピューターのメモリに存在する偽のファイルを提供できます。 ユニットテストで `+ print()+`をモックするために、後でこのテクニックを使用します:

>>>

>>> import io
>>> fake_file = io.StringIO()
>>> print('hello world', file=fake_file)
>>> fake_file.getvalue()
'hello world\n'

ここまで来たら、次のサブセクションで見るように、 `+ print()+`にキーワード引数が1つだけ残っています。 おそらく、それらの中で最も使用頻度が低いでしょう。 それにもかかわらず、絶対に必要な場合があります。

印刷呼び出しのバッファリング

前のサブセクションで、 `+ print()`が ` sys.stdout +`などのファイルのようなオブジェクトに印刷を委任することを学びました。 ただし、一部のストリームは、パフォーマンスを向上させるために特定のI/O操作をバッファリングします。 例を見てみましょう。

カウントダウンタイマーを作成していて、毎秒同じ行に残り時間を追加していると想像してください。

3...2...1...Go!

最初の試行は次のようになります。

import time

num_seconds = 3
for countdown in reversed(range(num_seconds + 1)):
    if countdown > 0:
        print(countdown, end='...')
        time.sleep(1)
    else:
        print('Go!')

`+ countdown `変数が0より大きい限り、コードは末尾の改行なしでテキストを追加し続け、その後1秒間スリープします。 最後に、カウントダウンが終了すると、「 Go!+」が出力され、行が終了します。

予想外に、プログラムは毎秒カウントダウンする代わりに、3秒間無駄にアイドル状態になり、その後突然行全体を一度に印刷します。

これは、この場合、オペレーティングシステムが標準出力への後続の書き込みをバッファリングするためです。 バッファリングに関して3種類のストリームがあることを知っておく必要があります。

  1. バッファなし

  2. ラインバッファリング

  3. ブロックバッファ

    *Unbuffered* は一目瞭然です。つまり、バッファリングは行われず、すべての書き込みが即座に有効になります。 *line-buffered* ストリームは、バッファーのどこかに改行が現れるまでI/O呼び出しを実行する前に待機しますが、 *block-buffered* ストリームは、コンテンツに関係なくバッファーが特定のサイズまで満たされるようにします。 標準出力は、どちらのイベントが最初に来るかに応じて、 *line-buffered* と *block-buffered* の両方です。

バッファリングは、高価なI/O呼び出しの数を減らすのに役立ちます。 たとえば、待ち時間の長いネットワークを介してメッセージを送信することを検討してください。 リモートサーバーに接続してSSHプロトコルを介してコマンドを実行すると、各キーストロークが実際に個々のデータパケットを生成する場合があります。これは、ペイロードよりも桁違いに大きくなります。 なんてオーバーヘッド! 少なくともいくつかの文字が入力されるまで待ってから、それらを一緒に送信することは理にかなっています。 そこでバッファリングが開始されます。

一方、カウントダウンの例で見たように、バッファリングは望ましくない効果をもたらすことがあります。 これを修正するには、 `+ flush `フラグを使用して、バッファ内の改行文字を待たずにストリームを強制的にフラッシュするように ` print()+`に指示するだけです:

print(countdown, end='...', flush=True)

それで全部です。 カウントダウンは期待どおりに動作するはずですが、私の言葉を受け入れないでください。 先に進み、テストして違いを確認してください。

おめでとうございます。 この時点で、すべてのパラメーターをカバーする `+ print()+`を呼び出す例を見てきました。 それらの目的と使用するタイミングを知っています。 ただし、署名を理解することは始まりにすぎません。 今後のセクションでは、その理由がわかります。

カスタムデータタイプの印刷

これまでは、文字列や数字などの組み込みデータ型のみを扱っていましたが、多くの場合、独自の抽象データ型を印刷する必要があります。 それらを定義するさまざまな方法を見てみましょう。

データを運ぶことを目的とするロジックのない単純なオブジェクトの場合、通常はhttps://docs.python.org/3/library/collections.html#collections.namedtuple [+ namedtuple +]を利用します。標準ライブラリで利用可能です。 名前付きタプルには、すぐに使えるテキスト表現があります:

>>>

>>> from collections import namedtuple
>>> Person = namedtuple('Person', 'name age')
>>> jdoe = Person('John Doe', 42)
>>> print(jdoe)
Person(name='John Doe', age=42)

データを保持すれば十分ですが、「+ Person +」タイプに動作を追加するには、最終的にクラスを定義する必要があります。 この例を見てください。

class Person:
    def __init__(self, name, age):
        self.name, self.age = name, age

`+ Person `クラスのインスタンスを作成して印刷しようとすると、この奇妙な出力が得られますが、これは同等の ` namedtuple +`とはまったく異なります。

>>>

>>> jdoe = Person('John Doe', 42)
>>> print(jdoe)
<__main__.Person object at 0x7fcac3fed1d0>

これはオブジェクトのデフォルト表現であり、メモリ内のアドレス、対応するクラス名、およびそれらが定義されたモジュールで構成されます。 それを少し修正しますが、記録のためだけに、https://realpython.com/inheritance-composition-python/[inheritance]を介して `+ namedtuple +`とカスタムクラスを組み合わせることができます。

from collections import namedtuple

class Person(namedtuple('Person', 'name age')):
    pass

`+ Person `クラスは、カスタマイズ可能な2つの属性を持つ特殊な種類の ` namedtuple +`になりました。

注意: Python 3では、 + pass +`ステートメントはhttps://docs.python.org/dev/library/constants.html#Ellipsis[ellipsis]( `+ …​ +)に置き換えることができますプレースホルダーを示すリテラル:

def delta(a, b, c):
    ...

これにより、インデントされたコードブロックが欠落しているために、インタープリターが `+ IndentationError +`を発生させなくなります。

これは、プレーンテキストの「+ namedtuple +」よりも優れています。無料で印刷できるだけでなく、クラスにカスタムメソッドやプロパティを追加できるからです。 ただし、1つの問題を解決しながら別の問題を導入します。 名前付きタプルを含むタプルはPythonでは不変であるため、一度作成された値を変更することはできません。

不変のデータ型を設計することが望ましいのは事実ですが、多くの場合、それらを変更できるようにしたいので、通常のクラスに戻ります。

*注意:*他の言語とフレームワークに続いて、Python 3.7はhttps://realpython.com/python-data-classes/[data classes]を導入しました。これは可変タプルと考えることができます。 これにより、両方の長所を最大限に活用できます。

>>>

>>> from dataclasses import dataclass
>>> @dataclass
... class Person:
...     name: str
...     age: int
...
...     def celebrate_birthday(self):
...         self.age += 1
...
>>> jdoe = Person('John Doe', 42)
>>> jdoe.celebrate_birthday()
>>> print(jdoe)
Person(name='John Doe', age=43)

variable annotationsの構文は、対応するタイプのクラスフィールドを指定するために必要であり、Python 3.6で定義されました。

前のサブセクションから、 `+ print()`が組み込みの ` str()`関数を暗黙的に呼び出して、その位置引数を文字列に変換することを既に知っています。 実際、通常の ` Person `クラスのインスタンスに対して手動で ` str()+`を呼び出すと、印刷と同じ結果が得られます。

>>>

>>> jdoe = Person('John Doe', 42)
>>> str(jdoe)
'<__main__.Person object at 0x7fcac3fed1d0>'

次に、 `+ str()+`は、通常実装するクラス本体内の2つの*マジックメソッド*のいずれかを探します。 見つからない場合は、見苦しいデフォルト表示に戻ります。 これらの魔法のメソッドは、検索の順番です:

  1. + def str (self)+

  2. + def repr (self)+

最初のものは、最も関連性の高い属性からの情報を含む、人間が読める短いテキストを返すことをお勧めします。 結局のところ、オブジェクトを印刷するときに、ユーザーパスワードなどの機密データを公開したくないのです。

ただし、もう一方は、オブジェクトの状態を文字列から復元できるように、オブジェクトに関する完全な情報を提供する必要があります。 理想的には、有効なPythonコードを返し、 `+ eval()+`に直接渡すことができるようにする必要があります。

>>>

>>> repr(jdoe)
"Person(name='John Doe', age=42)"
>>> type(eval(repr(jdoe)))
<class '__main__.Person'>

別の組み込み関数 `+ repr()`の使用に注意してください。これは常にオブジェクトで ` . repr ()+`の呼び出しを試みますが、そのメソッドが見つからない場合はデフォルトの表現にフォールバックします。 。

注意: `+ print()`自体は型キャストに ` str()`を使用しますが、一部の複合データ型は、メンバーで ` repr()+`を呼び出すデリゲートを行います。 これは、リストやタプルなどで起こります。

同じクラスの代替文字列表現を返す両方の魔法のメソッドを持つこのクラスを検討してください。

class User:
    def __init__(self, login, password):
        self.login = login
        self.password = password

    def __str__(self):
        return self.login

    def __repr__(self):
        return f"User('{self.login}', '{self.password}')"

`+ User `クラスの単一のオブジェクトを印刷する場合、 ` print(user)`は ` str(user)`を呼び出し、最終的には ` userを呼び出すため、パスワードは表示されません。 . str ()+ `:

>>>

>>> user = User('jdoe', 's3cret')
>>> print(user)
jdoe

ただし、角カッコで囲むことでリスト内に同じ `+ user +`変数を配置すると、パスワードがはっきりと見えるようになります。

>>>

>>> print([user])
[User('jdoe', 's3cret')]

これは、リストやタプルなどのシーケンスが、すべての要素が最初に `+ repr()`で変換されるように、 ` . str ()+`メソッドを実装するためです。

Pythonは、組み込みのデータ型がどれもニーズに合わない場合、独自のデータ型を定義することに関して、多くの自由を与えます。 名前付きタプルやデータクラスなど、それらの一部は、ユーザーの作業を必要とせずに見栄えの良い文字列表現を提供します。 それでも、最も柔軟性を高めるには、クラスを定義し、上記のマジックメソッドをオーバーライドする必要があります。

Python Printを理解する

この時点で `+ print()+`を使用する方法は非常によくわかっていますが、それが何であるかを知っていると、より効果的かつ意識的に使用できます。 このセクションを読むと、長年にわたってPythonでの印刷がどのように改善されたかを理解できます。

印刷はPython 3の関数です

`+ print()+`はPython 3の関数であることがわかりました。 具体的には、組み込み関数であるため、どこからでもインポートする必要はありません。

>>>

>>> print
<built-in function print>

グローバル名前空間で常に利用できるため、直接呼び出すことができますが、標準ライブラリのモジュールからアクセスすることもできます。

>>>

>>> import builtins
>>> builtins.print
<built-in function print>

これにより、カスタム関数との名前の衝突を回避できます。 末尾の改行を追加しないように、 `+ print()`を*再定義*したいとしましょう。 同時に、元の関数の名前を ` println()+`のようなものに変更したいと思いました。

>>>

>>> import builtins
>>> println = builtins.print
>>> def print(*args, **kwargs):
...     builtins.print(*args, **kwargs, end='')
...
>>> println('hello')
hello
>>> print('hello\n')
hello

これで、Javaプログラミング言語と同じように、2つの個別の印刷機能ができました。 link:#mocking-python-print-in-unit-tests [mocking section]のカスタムの `+ print()`関数も後で定義します。 また、関数ではなかった場合、そもそも ` print()+`を上書きできないことに注意してください。

一方、「+ print()」は数学的な意味での関数ではありません。暗黙の「 None +」以外の意味のある値を返さないためです。

>>>

>>> value = print('hello world')
hello world
>>> print(value)
None

そのような関数は、実際には、何らかの副作用を達成するために呼び出すプロシージャまたはサブルーチンであり、最終的にはグローバルな状態の変化です。 `+ print()+`の場合、その副作用は標準出力にメッセージを表示するか、ファイルに書き込むことです。

`+ print()+`は関数であるため、既知の属性を持つ明確に定義された署名を持っています。 特定のタスクを実行するための奇妙な構文を覚える必要なく、選択したエディターを使用して*ドキュメント*をすばやく見つけることができます。

その上、関数は extend に簡単です。 関数に新しい機能を追加することは、別のキーワード引数を追加するのと同じくらい簡単ですが、その新しい機能をサポートするように言語を変更することははるかに面倒です。 たとえば、ストリームのリダイレクトやバッファのフラッシュを考えてください。

関数である `+ print()`のもう1つの利点は、 *composability* です。 関数はいわゆるhttps://realpython.com/lessons/functions-first-class-objects-python/[first-class objects]またはhttps://realpython.com/lessons/functions-are-first-class-ですPythonのcitizens-python/[first-class citizens]。これは、文字列や数値のような値であると言うのが派手な方法です。 この方法で、関数を変数に割り当てたり、別の関数に渡したり、別の関数から返すことさえできます。 この点で、「 print()+」は変わりません。 たとえば、依存関係の注入に利用できます。

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

def custom_print(*args):
    pass  # Do not print anything

download('/js/app.js', log=custom_print)

ここで、 `+ log `パラメーターを使用すると、デフォルトで ` print()`になりますが、呼び出し可能な任意のコールバック関数を挿入できます。 この例では、 ` print()+`を何もしないダミー関数に置き換えることで、印刷が完全に無効になります。

注: *依存性*は、別のビットのコードが必要とするコードです。

依存性注入*は、コードの設計で使用され、よりテストしやすく、再利用可能で、拡張のためにオープンにする手法です。 抽象インターフェースを介して間接的に依存関係を参照し、 *pull 形式ではなく push 形式で提供することで、これを実現できます。

インターネット上で循環している依存性注入の面白い説明があります。

_ _ 5歳児の依存注射

自分で冷蔵庫から物を取り出すと、問題が発生する可能性があります。 ドアを開けたままにしたり、ママやパパが欲しくないものを手に入れたりするかもしれません。 私たちが持っていないものや期限切れのものを探しているかもしれません。

あなたがしなければならないことは、「昼食時に何か飲む必要がある」というニーズを述べることです。そして、私たちはあなたが座って食事をするときに何かがあることを確認します。

John Munsch、2009年10月28日。(https://stackoverflow.com/a/1638961 [出典]) _ _

合成により、いくつかの機能を同じ種類の新しい機能に結合できます。 標準のエラーストリームに出力し、すべてのメッセージに特定のログレベルをプレフィックスするカスタムの `+ error()+`関数を指定して、実際の動作を見てみましょう。

>>>

>>> from functools import partial
>>> import sys
>>> redirect = lambda function, stream: partial(function, file=stream)
>>> prefix = lambda function, prefix: partial(function, prefix)
>>> error = prefix(redirect(print, sys.stderr), '[ERROR]')
>>> error('Something went wrong')
[ERROR] Something went wrong

このカスタム関数は、*部分関数*を使用して、目的の効果を実現します。 これは、https://realpython.com/courses/functional-programming-python/[機能プログラミング]パラダイムから借用した高度な概念であるため、現時点ではそのトピックを深く掘り下げる必要はありません。 ただし、このトピックに興味がある場合は、https://pymotw.com/3/functools/[+ functools +]モジュールをご覧になることをお勧めします。

ステートメントとは異なり、関数は値です。 つまり、それらを expressions 、特にhttps://realpython.com/python-lambda/[lambda expressions]と混ぜることができます。 `+ print()+`を置き換える本格的な関数を定義する代わりに、それを呼び出す匿名ラムダ式を作成できます。

>>>

>>> download('/js/app.js', lambda msg: print('[INFO]', msg))
[INFO] Downloading/js/app.js

ただし、ラムダ式は適切に定義されているため、コードの他の場所でそれを参照する方法はありません。

注: Pythonでは、割り当て、条件付きステートメント、ループなどのステートメントを*匿名ラムダ関数*に入れることはできません。 単一の式でなければなりません!

別の種類の式は、三項条件式です。

>>>

>>> user = 'jdoe'
>>> print('Hi!') if user is None else print(f'Hi, {user}.')
Hi, jdoe.

Pythonにはhttps://realpython.com/python-conditional-statements/[条件付きステートメント]とhttps://realpython.com/python-conditional-statements/#conditional-expressions-pythons-ternary-operator [条件式]の両方があります。 後者は、変数に割り当てられるか、関数に渡される単一の値に評価されます。 上記の例では、「+ None +」と評価される値ではなく副作用に関心があるため、単純に無視します。

ご覧のとおり、関数を使用すると、他の言語と一貫性のあるエレガントで拡張可能なソリューションが可能になります。 次のサブセクションでは、関数として `+ print()+`がなければ頭痛の種になる原因を見つけます。

印刷はPython 2のステートメントでした

*ステートメント*は、実行されたときに副作用を引き起こす可能性があるが、値を評価しない命令です。 つまり、ステートメントを出力したり、次のような変数に割り当てることはできません。

result = print 'hello world'

これはPython 2の構文エラーです。

Pythonのステートメントのいくつかの例を次に示します。

  • 割り当て: + = +

  • 条件付き: + if +

  • ループ: + while +

  • アサーション+ assert +

注意: Python 3.8には、物議を醸す* walrus演算子*( +:= +)があります。これはhttps://www.python.org/dev/peps/pep-0572/[割り当て式]です。 これにより、式を評価し、別の式内であっても同時に結果を変数に割り当てることができます!

高価な関数を1回呼び出して、さらに計算するために結果を再利用するこの例を見てください。

# Python 3.8+
values = [y := f(x), y**2, y**3]

これは、効率を損なうことなくコードを簡素化するのに役立ちます。 通常、パフォーマンスコードはより冗長になる傾向があります。

y = f(x)
values = [y, y**2, y**3]

この新しい構文の背後にある論争は、多くの議論を引き起こしました。 多数の否定的なコメントと白熱した議論により、最終的にはGuido van Rossumが Benevolent Dictator For Life またはBDFLの地位を辞めました。

ステートメントは通常、言語で固定された意味を持つ + if ++ for +、または `+ print `などの予約キーワードで構成されます。 変数や他のシンボルに名前を付けることはできません。 Python 2では、 ` print +`ステートメントを再定義またはモックすることはできません。 あなたが得るものにこだわっています。

さらに、ステートメントはラムダ式で受け入れられないため、匿名関数から印刷することはできません。

>>>

>>> lambda: print 'hello world'
  File "<stdin>", line 1
    lambda: print 'hello world'
                ^
SyntaxError: invalid syntax

`+ print +`ステートメントの構文はあいまいです。 メッセージの前後に括弧を追加できる場合がありますが、これらは完全にオプションです。

>>>

>>> print 'Please wait...'
Please wait...
>>> print('Please wait...')
Please wait...

それ以外の場合は、メッセージの印刷方法を変更します。

>>>

>>> print 'My name is', 'John'
My name is John
>>> print('My name is', 'John')
('My name is', 'John')

文字列の連結は、互換性のない型のために `+ TypeError +`を発生させる可能性があります。たとえば、次のように手動で処理する必要があります。

>>>

>>> values = ['jdoe', 'is', 42, 'years old']
>>> print ' '.join(map(str, values))
jdoe is 42 years old

これを、シーケンスのアンパックを活用するPython 3の同様のコードと比較してください。

>>>

>>> values = ['jdoe', 'is', 42, 'years old']
>>> print(*values)  # Python 3
jdoe is 42 years old

バッファのフラッシュやストリームのリダイレクトなどの一般的なタスクのキーワード引数はありません。 代わりに、風変わりな構文を覚えておく必要があります。 組み込みの `+ help()`関数でさえ、 ` print +`ステートメントに関しては役に立たない:

>>>

>>> help(print)
  File "<stdin>", line 1
    help(print)
             ^
SyntaxError: invalid syntax

後続の改行の削除は、不要なスペースを追加するため、適切に機能しません。 複数の「+ print +」ステートメントを一緒に作成することはできません。また、その上で、文字エンコーディングについてはさらに熱心にならなければなりません。

問題のリストは延々と続く。 興味がある場合は、#printing-in-a-nutshell [前のセクション]のリンクに戻って、Python 2の構文の詳細な説明を探してください。

ただし、これらの問題のいくつかは、はるかに簡単なアプローチで軽減できます。 Python 3への移行を容易にするために、 `+ print()`関数がバックポートされたことがわかりました。 特別な ` future +`モジュールからインポートすることができます。これは、後のPythonバージョンでリリースされた言語機能の選択を公開します。

*注意:*将来の関数や、 `+ with +`ステートメントなどの焼き付けられた言語構造をインポートできます。

使用可能な機能を正確に調べるには、モジュールを調べます。

>>>

>>> import __future__
>>> __future__.all_feature_names
['nested_scopes',
 'generators',
 'division',
 'absolute_import',
 'with_statement',
 'print_function',
 'unicode_literals']

`+ dir( future )+`を呼び出すこともできますが、それはモジュールの多くの面白くない内部の詳細を示します。

Python 2で `+ print()+`関数を有効にするには、ソースコードの先頭に次のインポートステートメントを追加する必要があります。

from __future__ import print_function

これ以降、 `+ print `ステートメントは使用できなくなりますが、 ` print()`関数を自由に使用できます。 Pythonの関数と同じ関数ではないことに注意してください。これは、キーワード引数「 flush +」が欠落しているためですが、残りの引数は同じです。

それ以外は、文字エンコーディングを適切に管理することからあなたをspareしみません。

Python 2で `+ print()+`関数を呼び出す例は次のとおりです。

>>>

>>> from __future__ import print_function
>>> import sys
>>> print('I am a function in Python', sys.version_info.major)
I am a function in Python 2

これで、Pythonでの印刷がどのように進化したかがわかり、最も重要なこととして、これらの後方互換性のない変更が必要だった理由を理解できます。 これを知ることは、きっとあなたがより良いPythonプログラマーになるのに役立つでしょう。

スタイルを使用した印刷

印刷が画面上のピクセルを照らすことだけであると考えていたら、技術的には正しいでしょう。 ただし、見栄えを良くする方法があります。 このセクションでは、複雑なデータ構造の書式設定、色やその他の装飾の追加、インターフェースの構築、アニメーションの使用、さらにテキストを使用したサウンドの再生方法について説明します!

入れ子になったデータ構造のきれいな印刷

コンピュータ言語を使用すると、データと実行可能コードを構造化された方法で表すことができます。 ただし、Pythonとは異なり、ほとんどの言語では空白や書式設定を自由に使用できます。 これは、たとえば圧縮などで役立ちますが、コードが読みにくくなる場合があります。

プリティプリンティングとは、データやコードを人間の目により魅力的に見せて、より簡単に理解できるようにすることです。 これは、特定の行のインデント、改行の挿入、要素の並べ替えなどによって行われます。

Pythonの標準ライブラリには `+ pprint `モジュールが付属しており、1行に収まらない大きなデータ構造をきれいに印刷するのに役立ちます。 より人間に優しい方法で印刷されるため、https://realpython.com/jupyter-notebook-introduction/[JupyterLabおよびIPython]、通常の ` print()+`関数の代わりにデフォルトで使用します。

注意: IPythonできれいな印刷を切り替えるには、次のコマンドを発行します。

>>>

In [1]: %pprint
Pretty printing has been turned OFF
In [2]: %pprint
Pretty printing has been turned ON

これは、IPythonの Magic の例です。 パーセント記号( )で始まる多くの組み込みコマンドがありますが、https://pypi.org/[PyPI]で詳細を確認したり、独自のコマンドを作成することもできます。

元の `+ print()`関数にアクセスできないことを気にしない場合は、インポートの名前変更を使用して、コード内の関数を ` pprint()+`に置き換えることができます。

>>>

>>> from pprint import pprint as print
>>> print
<function pprint at 0x7f7a775a3510>

個人的には、両方の機能をすぐに使えるようにしたいので、短いエイリアスとして「+ pp +」のようなものを使用します。

from pprint import pprint as pp

一見したところ、2つの機能にほとんど違いはありません。場合によっては、ほとんど何もありません。

>>>

>>> print(42)
42
>>> pp(42)
42
>>> print('hello')
hello
>>> pp('hello')
'hello'  # Did you spot the difference?

これは、 `+ pprint()`が型キャストのために通常の ` str()`の代わりに ` repr()+`を呼び出すため、必要に応じて出力をPythonコードとして評価できるためです。 より複雑なデータ構造の供給を開始すると、違いが明らかになります。

>>>

>>> data = {'powers': [x**10 for x in range(10)]}
>>> pp(data)
{'powers': [0,
            1,
            1024,
            59049,
            1048576,
            9765625,
            60466176,
            282475249,
            1073741824,
            3486784401]}

この関数は読みやすさを向上させるために適切なフォーマットを適用しますが、いくつかのパラメーターを使用してさらにカスタマイズできます。 たとえば、特定のレベルの下に省略記号を表示することにより、深くネストされた階層を制限できます。

>>>

>>> cities = {'USA': {'Texas': {'Dallas': ['Irving']}}}
>>> pp(cities, depth=3)
{'USA': {'Texas': {'Dallas': [...]}}}

通常の `+ print()+`も楕円を使用しますが、スタックオーバーフローエラーを回避するために、循環を形成する再帰的なデータ構造を表示します。

>>>

>>> items = [1, 2, 3]
>>> items.append(items)
>>> print(items)
[1, 2, 3, [...]]

ただし、 `+ pprint()+`は、自己参照オブジェクトの一意のIDを含めることで、より明確になります。

>>>

>>> pp(items)
[1, 2, 3, <Recursion on list with id=140635757287688>]
>>> id(items)
140635757287688

リストの最後の要素は、リスト全体と同じオブジェクトです。

注意: `+ reprlib +`モジュールを使用して、再帰的または非常に大きなデータセットを処理することもできます。

>>>

>>> import reprlib
>>> reprlib.repr([x**10 for x in range(10)])
'[0, 1, 1024, 59049, 1048576, 9765625, ...]'

このモジュールは、ほとんどの組み込み型をサポートし、Pythonデバッガーによって使用されます。

`+ pprint()+`は、印刷の前に辞書キーを自動的にソートし、一貫した比較を可能にします。 文字列を比較するとき、多くの場合、シリアル化された属性の特定の順序は気にしません。 とにかく、シリアル化する前に実際の辞書を比較するのが常に最善です。

辞書は、多くの場合、https://realpython.com/python-json/[JSON data]を表します。これはインターネットで広く使用されています。 辞書を有効なJSON形式の文字列に正しくシリアル化するには、 `+ json +`モジュールを利用できます。 きれいな印刷機能もあります。

>>>

>>> import json
>>> data = {'username': 'jdoe', 'password': 's3cret'}
>>> ugly = json.dumps(data)
>>> pretty = json.dumps(data, indent=4, sort_keys=True)
>>> print(ugly)
{"username": "jdoe", "password": "s3cret"}
>>> print(pretty)
{
    "password": "s3cret",
    "username": "jdoe"
}

ただし、通常はやりたいことではないので、印刷を自分で処理する必要があることに注意してください。 同様に、 `+ pprint `モジュールには、文字列を返す追加の ` pformat()+`関数があります。これは、印刷以外の何かをしなければならない場合に備えてです。

驚いたことに、 `+ pprint()`のシグネチャは、 ` print()+`関数のシグネチャとはまったく異なります。 複数の位置引数を渡すこともできません。これは、データ構造の印刷にどれだけ焦点を合わせているかを示しています。

ANSIエスケープシーケンスを使用した色の追加

パーソナルコンピューターがより高度になると、グラフィックスが向上し、より多くの色を表示できるようになりました。 ただし、さまざまなベンダーが、それを制御するためのAPI設計について独自の考えを持っていました。 それは数十年前にアメリカ国立標準研究所の人々がhttps://en.wikipedia.org/wiki/ANSI_escape_code[ANSIエスケープコード]を定義することで統一することを決定したときに変わりました。

今日の端末エミュレータのほとんどは、ある程度この標準をサポートしています。 最近まで、Windowsオペレーティングシステムは注目に値する例外でした。 したがって、最高の移植性が必要な場合は、Pythonのhttps://pypi.org/project/colorama/[+ colorama +]ライブラリを使用してください。 ANSIコードをWindowsの適切な対応するコードに変換し、他のオペレーティングシステムではそのままにします。

たとえば、色に関連するANSIエスケープシーケンスのサブセットを端末が理解しているかどうかを確認するには、次のコマンドを使用してみてください。

$ tput colors

Linuxのデフォルトのターミナルでは、256の異なる色を表示できますが、xtermでは8色しか表示されません。 色がサポートされていない場合、コマンドは負の数を返します。

ANSIエスケープシーケンスは、端末のマークアップ言語のようなものです。 HTMLでは、ドキュメント内の要素の表示方法を変更するために、「+ <b> 」や「 <i> +」などのタグを使用します。 これらのタグはコンテンツと混在していますが、それら自体は表示されません。 同様に、エスケープコードが認識される限り、エスケープコードは端末に表示されません。 それ以外の場合は、ウェブサイトのソースを表示しているかのように、リテラル形式で表示されます。

その名前が示すように、シーケンスは印刷できない[.keys]#Esc#文字で開始する必要があります。ASCII値は27で、16進数で「+ 0x1b 」または8進数で「+033」と表示されることもあります。 Pythonの数値リテラルを使用して、実際に同じ数値であることをすばやく確認できます。

>>>

>>> 27 == 0x1b == 0o33
True

さらに、シェルの `+ \ e +`エスケープシーケンスを使用して取得できます。

$ echo -e "\e"

最も一般的なANSIエスケープシーケンスの形式は次のとおりです。

Element Description Example

Esc

non-printable escape character

\033

[

opening square bracket

[

numeric code

one or more numbers separated with ;

0

character code

uppercase or lowercase letter

m

*数値コード*はセミコロンで区切られた1つ以上の数字であり、*文字コード*は1文字です。 それらの特定の意味は、ANSI標準によって定義されています。 たとえば、すべての書式設定をリセットするには、コードゼロと文字「+ m +」を使用する次のコマンドのいずれかを入力します。

$ echo -e "\e[0m"
$ echo -e "\x1b[0m"
$ echo -e "\033[0m"

スペクトルのもう一方の端には、複合コード値があります。 端末が24ビット深度をサポートしている場合、RGBチャンネルで前景と背景を設定するには、複数の数字を指定できます。

$ echo -e "\e[38;2;0;0;0m\e[48;2;255;255;255mBlack on white\e[0m"

ANSIエスケープコードで設定できるのはテキストの色だけではありません。 たとえば、ターミナルウィンドウをクリアしてスクロールしたり、背景を変更したり、カーソルを動かしたり、テキストを点滅させたり、下線を付けたりできます。

Pythonでは、おそらく任意のコードをシーケンスにラップできるヘルパー関数を作成します。

>>>

>>> def esc(code):
...     return f'\033[{code}m'
...
>>> print(esc('31;1;4') + 'really' + esc(0) + ' important')

これにより、単語「+ really +」が赤、太字、下線付きフォントで表示されます。

ANSIエスケープコードでフォーマットされたテキスト、幅= 576、高さ= 236

ただし、前述の `+ colorama +`ライブラリや、コンソールでユーザーインターフェイスを構築するためのツールなど、ANSIエスケープコードには高レベルの抽象化があります。

コンソールユーザーインターフェイスの構築

ANSIエスケープコードで遊ぶのは間違いなく楽しいですが、現実の世界では、ユーザーインターフェイスをまとめるためのより抽象的な構成要素が必要です。 端末に対してこのような高レベルの制御を提供するライブラリがいくつかありますが、https://docs.python.org/3/howto/curses.html [+ curses +]が最も一般的な選択肢のようです。

注意: Windowsで `+ curses +`ライブラリを使用するには、サードパーティのパッケージをインストールする必要があります。

C:\> pip install windows-curses

これは、Windows用のPythonディストリビューションの標準ライブラリで「+ curses +」が利用できないためです。

主に、テキストの塊ではなく、独立したグラフィカルウィジェットの観点から考えることができます。 それに、空のキャンバスを描くようなものだから、内なるアーティストを表現するのに多くの自由を得ることができます。 ライブラリは、異なる端末を処理する複雑さを隠します。 それ以外に、キーボードゲームのサポートが優れており、ビデオゲームの作成に役立つ場合があります。

レトロなヘビゲームを作ってみませんか? Pythonスネークシミュレーターを作成しましょう。

cursesライブラリで構築されたレトロなヘビゲーム、width = 576 、height = 392

最初に、 `+ curses +`モジュールをインポートする必要があります。 実行中の端末の状態を変更するため、エラーを処理し、以前の状態を適切に復元することが重要です。 これは手動で行うことができますが、ライブラリにはメイン関数の便利なラッパーが付属しています。

import curses

def main(screen):
    pass

if __name__ == '__main__':
    curses.wrapper(main)

関数は、「+ stdscr +」とも呼ばれる画面オブジェクトへの参照を受け入れる必要があります。この参照は後で追加のセットアップに使用します。

このプログラムをすぐに実行すると、すぐに終了するため、効果は表示されません。 ただし、スニークピークを得るために少し遅延を追加できます。

import time, curses

def main(screen):
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

今回は、画面が一瞬完全に空白になりましたが、カーソルはまだ点滅していました。 非表示にするには、モジュールで定義されている構成関数のいずれかを呼び出すだけです。

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

画面座標の点のリストとしてヘビを定義しましょう:

snake = [(0, i) for i in reversed(range(20))]

蛇の頭は常にリストの最初の要素であり、尾は最後の要素です。 ヘビの初期形状は水平で、画面の左上隅から始まり、右を向いています。 y座標はゼロのままですが、x座標は頭から尾に向かって減少します。

ヘビを描くには、頭から始めて、残りのセグメントを追います。 各セグメントには `(y、x)`座標が含まれているため、それらを展開できます。

# Draw the snake
screen.addstr(*snake[0], '@')
for segment in snake[1:]:
    screen.addstr(*segment, '*')

繰り返しますが、今すぐこのコードを実行しても、画面は後で明示的に更新する必要があるため、何も表示されません。

import time, curses

def main(screen):
    curses.curs_set(0)  # Hide the cursor

    snake = [(0, i) for i in reversed(range(20))]

    # Draw the snake
    screen.addstr(*snake[0], '@')
    for segment in snake[1:]:
        screen.addstr(*segment, '*')

    screen.refresh()
    time.sleep(1)

if __name__ == '__main__':
    curses.wrapper(main)

ベクトルとして定義できる4つの方向のいずれかでヘビを移動します。 最終的に、方向は矢印キーストロークに応じて変化するため、ライブラリのキーコードに接続できます。

directions = {
    curses.KEY_UP: (-1, 0),
    curses.KEY_DOWN: (1, 0),
    curses.KEY_LEFT: (0, -1),
    curses.KEY_RIGHT: (0, 1),
}

direction = directions[curses.KEY_RIGHT]

ヘビはどのように動くのですか? 頭だけが実際に新しい場所に移動し、他のすべてのセグメントはそこに向かって移動します。 各ステップで、頭部と尾部を除き、ほぼすべてのセグメントが同じままです。 ヘビが成長していないと仮定すると、尾を取り外してリストの先頭に新しい頭を挿入できます。

# Move the snake
snake.pop()
snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

頭の新しい座標を取得するには、方向ベクトルを追加する必要があります。 ただし、Pythonでタプルを追加すると、対応するベクトルコンポーネントの代数和ではなく、より大きなタプルが作成されます。 これを修正する1つの方法は、組み込みの + zip()++ sum()+、および `+ map()+`関数を使用することです。

キーストロークで方向が変わるので、押されたキーコードを取得するには、 `+ .getch()+`を呼び出す必要があります。 ただし、押されたキーが、辞書キーとして以前に定義された矢印キーに対応していない場合、方向は変わりません。

# Change direction on arrow keystroke
direction = directions.get(screen.getch(), direction)

ただし、デフォルトでは、 `+ .getch()+`はキーストロークがなければヘビの移動を防ぐブロッキング呼び出しです。 したがって、さらに別の構成を追加して、呼び出しをブロックしないようにする必要があります。

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

ほぼ完了しましたが、最後の1つだけが残っています。 このコードをループすると、ヘビは移動する代わりに成長しているように見えます。 これは、各反復の前に画面を明示的に消去する必要があるためです。

最後に、Pythonでヘビゲームをプレイするために必要なのはこれだけです。

import time, curses

def main(screen):
    curses.curs_set(0)    # Hide the cursor
    screen.nodelay(True)  # Don't block I/O calls

    directions = {
        curses.KEY_UP: (-1, 0),
        curses.KEY_DOWN: (1, 0),
        curses.KEY_LEFT: (0, -1),
        curses.KEY_RIGHT: (0, 1),
    }

    direction = directions[curses.KEY_RIGHT]
    snake = [(0, i) for i in reversed(range(20))]

    while True:
        screen.erase()

        # Draw the snake
        screen.addstr(*snake[0], '@')
        for segment in snake[1:]:
            screen.addstr(*segment, '*')

        # Move the snake
        snake.pop()
        snake.insert(0, tuple(map(sum, zip(snake[0], direction))))

        # Change direction on arrow keystroke
        direction = directions.get(screen.getch(), direction)

        screen.refresh()
        time.sleep(0.1)

if __name__ == '__main__':
    curses.wrapper(main)

これは単に、 `+ curses +`モジュールが開く可能性の表面を引っ掻いているだけです。 このような、またはよりビジネス指向のアプリケーションのようなゲーム開発に使用できます。

クールなアニメーションで生活する

アニメーションは、ユーザーインターフェイスをより魅力的にするだけでなく、全体的なユーザーエクスペリエンスも向上させます。 たとえば、ユーザーに早期のフィードバックを提供すると、ユーザーはプログラムがまだ機能しているかどうか、またはプログラムを強制終了するときかどうかを知ることができます。

ターミナルでテキストをアニメーション化するには、カーソルを自由に動かせる必要があります。 これは、前述のツールの1つ、つまりANSIエスケープコードまたは `+ curses +`ライブラリを使用して実行できます。 ただし、さらに簡単な方法をお見せしたいと思います。

アニメーションを1行のテキストに制限できる場合は、2つの特別なエスケープ文字シーケンスに関心があるかもしれません。

  • キャリッジリターン: + \ r +

  • バックスペース: + \ b +

最初のカーソルはカーソルを行の先頭に移動し、2番目のカーソルはカーソルを1文字だけ左に移動します。 両方とも、既に書かれているテキストを上書きすることなく、非破壊的に機能します。

いくつかの例を見てみましょう。

多くの場合、終了までの残り時間を正確に把握せずに、進行中の作業を示すために、*スピニングホイール*のようなものを表示する必要があります。

多くのコマンドラインツールは、ネットワーク経由でデータをダウンロードするときにこのトリックを使用します。 ラウンドロビン方式で循環する一連のキャラクターから、非常に単純なストップモーションアニメーションを作成できます。

from itertools import cycle
from time import sleep

for frame in cycle(r'-\|/-\|/'):
    print('\r', frame, sep='', end='', flush=True)
    sleep(0.2)

ループは印刷する次の文字を取得し、カーソルを行の先頭に移動し、改行を追加せずに以前の内容を上書きします。 位置引数の間に余分なスペースが必要ないため、セパレーター引数は空白にする必要があります。 また、リテラルにバックスラッシュ文字が存在するため、Pythonの生の文字列の使用に注意してください。

残り時間またはタスクの完了率がわかると、アニメーションの進行状況バーを表示できます。

まず、表示するハッシュタグの数と挿入する空白スペースの数を計算する必要があります。 次に、行を消去し、最初からバーを作成します。

from time import sleep

def progress(percent=0, width=30):
    left = width *percent//100
    right = width - left
    print('\r[', '#'* left, ' ' * right, ']',
          f' {percent:.0f}%',
          sep='', end='', flush=True)

for i in range(101):
    progress(i)
    sleep(0.1)

前と同様に、更新の各リクエストは行全体を再描画します。

*注:*機能が豊富なhttps://pypi.org/project/progressbar2/[+ progressbar2 +]ライブラリと、同様のツールがいくつかあり、より包括的な方法で進行状況を表示できます。

印刷で音を出す

PCスピーカーを搭載したコンピューターを覚えるのに十分な年齢の場合、ハードウェアの問題を示すためによく使用される独特の_ビープ_サウンドも覚えておく必要があります。 彼らはそれ以上かろうじて音を立てることはできませんでしたが、それでもビデオゲームは非常に良く見えました。

今日でもこの小さなスピーカーを利用できますが、ラップトップに付属していない可能性があります。 このような場合、シェルで*端末ベル*エミュレーションを有効にして、代わりにシステム警告音が再生されるようにすることができます。

先に進み、このコマンドを入力して、端末がサウンドを再生できるかどうかを確認します。

$ echo -e "\a"

これは通常テキストを印刷しますが、 `+ -e `フラグはバックスラッシュエスケープの解釈を可能にします。 ご覧のとおり、特別なhttps://en.wikipedia.org/wiki/Bell_character [ベル文字]を出力する「アラート」を表す専用のエスケープシーケンス ` \ a +`があります。 一部の端末では、表示されるたびに音が鳴ります。

同様に、この文字をPythonで印刷できます。 おそらくループで何らかのメロディーを形成します。 音符は1つだけですが、連続するインスタンス間で一時停止の長さを変更できます。 それはモールス信号の再生に最適なおもちゃのようです!

ルールは次のとおりです。

  • 文字は、一連の*ドット*(・)および*ダッシュ*(–)記号でエンコードされます。

  • *ドット*は時間の1単位です。

  • *ダッシュ*は3単位の時間です。

  • 文字内の個々の*記号*は、1単位の時間間隔で配置されます。

  • 2つの隣接する*文字*のシンボルは、3単位の時間間隔で配置されます。

  • 2つの隣接する*単語*のシンボルは、7単位の時間間隔で配置されます。

これらのルールによれば、次の方法でSOS信号を無期限に「印刷」できます。

while True:
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    letter_space()
    dash()
    symbol_space()
    dash()
    symbol_space()
    dash()
    letter_space()
    dot()
    symbol_space()
    dot()
    symbol_space()
    dot()
    word_space()

Pythonでは、わずか10行のコードで実装できます。

from time import sleep

speed = 0.1

def signal(duration, symbol):
    sleep(duration)
    print(symbol, end='', flush=True)

dot = lambda: signal(speed, '·\a')
dash = lambda: signal(3*speed, '−\a')
symbol_space = lambda: signal(speed, '')
letter_space = lambda: signal(3*speed, '')
word_space = lambda: signal(7*speed, ' ')

さらに一歩進んで、テキストをモールス信号に変換するためのコマンドラインツールを作成することもできますか? いずれにせよ、あなたがこれを楽しんでいることを願っています!

単体テストでのPython Printのモック

現在、高品質の基準を満たすコードを出荷することが期待されています。 プロフェッショナルになりたい場合は、https://realpython.com/python-testing/[テスト方法]コードを学習する必要があります。

ソフトウェアテストは、明らかな間違いについて警告するコンパイラを持たないPythonなどの動的型付け言語では特に重要です。 欠陥は、本番環境に到達し、コードのブランチが最終的に実行されるその日まで、長い間休止状態になります。

確かに、https://realpython.com/python-code-quality/#linters [linters]、https://realpython.com/python-type-checking/[type checkers]、および静的コード分析のための他のツールがありますあなたを支援します。 しかし、あなたのプログラムがビジネスレベルで行うべきことを実行しているかどうかはわかりません。

それで、 `+ print()`をテストする必要がありますか? No. 結局のところ、これは組み込みの機能であり、包括的なテストスイートを既に通過している必要があります。 ただし、テストしたいのは、コードが適切なタイミングで期待されるパラメーターで ` print()+`を呼び出しているかどうかです。 それは*行動*として知られています。

mocking実際のオブジェクトまたは関数によって動作をテストできます。 この場合、 `+ print()+`をモックして、呼び出しを記録および検証します。

注意:*あなたは、*ダミースタブスパイ、または*モック*という用語を同じ意味で使用しているのを聞いたことがあるかもしれません。 区別する人もいれば、区別しない人もいます。

Martin Fowlerは、https://martinfowler.com/bliki/TestDouble.html [短い用語集]でそれらの違いを説明し、それらを test doubles と総称します。

Pythonでのモックは2つあります。 まず、依存性注入を使用することにより、静的に型付けされた言語の従来のパスを使用できます。 これにより、テスト中のコードを変更することが必要になる場合があります。コードが外部ライブラリで定義されている場合、常に変更できるとは限りません

def download(url, log=print):
    log(f'Downloading {url}')
    # ...

これは、関数の構成について説明するために前のセクションで使用したのと同じ例です。 基本的に、同じインターフェースのカスタム関数で `+ print()+`を置き換えることができます。 適切なメッセージが出力されるかどうかを確認するには、モック関数を挿入してインターセプトする必要があります。

>>>

>>> def mock_print(message):
...     mock_print.last_message = message
...
>>> download('resource', mock_print)
>>> assert 'Downloading resource' == mock_print.last_message

このモックを呼び出すと、最後のメッセージが属性に保存されます。たとえば、 `+ assert +`ステートメントで、後で調べることができます。

少し代替的なソリューションでは、 `+ print()+`関数全体をカスタムラッパーで置き換える代わりに、メモリ内のファイルのような文字ストリームに標準出力をリダイレクトできます。

>>>

>>> def download(url, stream=None):
...     print(f'Downloading {url}', file=stream)
...     # ...
...
>>> import io
>>> memory_buffer = io.StringIO()
>>> download('app.js', memory_buffer)
>>> download('style.css', memory_buffer)
>>> memory_buffer.getvalue()
'Downloading app.js\nDownloading style.css\n'

今回は、関数は明示的に `+ print()`を呼び出しますが、 ` file +`パラメーターを外部に公開します。

しかし、オブジェクトをモックするよりPython的な方法は、組み込みの `+ mock +`モジュールを利用します。これはhttps://en.wikipedia.org/wiki/Monkey_patch[monkey patching]と呼ばれる技術を使用します。 この軽rog的な名前は、「ダーティハック」であることから生じます。 依存性注入ほどエレガントではありませんが、間違いなく迅速で便利です。

注意: `+ mock +`モジュールはPython 3の標準ライブラリに吸収されましたが、それ以前はサードパーティパッケージでした。 個別にインストールする必要がありました。

$ pip2 install mock

それ以外は、「+ mock 」と呼びましたが、Python 3では単体テストモジュールの一部なので、「 unittest.mock +」からインポートする必要があります。

モンキーパッチは、実行時に実装を動的に変更します。 このような変更はグローバルに表示されるため、望ましくない結果が生じる可能性があります。 ただし、実際には、パッチ適用はテスト実行中のコードにのみ影響します。

テストケースで + print()+`をモックするには、通常、 `+ @ patch + decoratorを使用し、パッチ適用のターゲットを指定します。モジュール名を含む完全修飾名でそれを参照することにより:

from unittest.mock import patch

@patch('builtins.print')
def test_print(mock_print):
    print('not a real print')
    mock_print.assert_called_with('not a real print')

これにより、モックが自動的に作成され、テスト関数に挿入されます。 ただし、テスト関数がモックを受け入れることを宣言する必要があります。 基になるモックオブジェクトには、動作を検証するための便利なメソッドと属性が多数あります。

そのコードスニペットについて何かおかしいことに気づきましたか?

関数にモックを注入しても、直接呼び出すことはできませんが、可能です。 注入されたモックは、後でアサーションを行うためだけに使用され、おそらくテストを実行する前にコンテキストを準備するために使用されます。

実際には、モックは、データベース接続などの依存関係を削除することで、テスト対象のコードを分離するのに役立ちます。 テストでモックを呼び出すことはめったにありません。それはあまり意味がないからです。 むしろ、モックを知らずに間接的に呼び出すのは他のコードです。

その意味は次のとおりです。

from unittest.mock import patch

def greet(name):
    print(f'Hello, {name}!')

@patch('builtins.print')
def test_greet(mock_print):
    greet('John')
    mock_print.assert_called_with('Hello, John!')

テスト対象のコードは、グリーティングを印刷する機能です。 かなり単純な関数ですが、値を返さないため簡単にテストできません。 副作用があります。

その副作用を排除するには、依存関係をモックアウトする必要があります。 パッチを適用すると、元の関数に変更を加えることを回避できます。これは、 `+ print()`にとらわれないままになる可能性があります。 それは ` print()+`を呼び出していると考えていますが、実際には、あなたが完全に制御しているモックを呼び出しています。

ソフトウェアをテストする理由はたくさんあります。 それらの1つはバグを探しています。 テストを書くとき、例えば、それをあざけることによって、 `+ print()+`関数を削除したいことがよくあります。 しかし逆説的に、同じ機能は、次のセクションで説明するデバッグの関連プロセス中にバグを見つけるのに役立ちます。

印刷のデバッグ

このセクションでは、控えめな `+ print()`関数から、 ` logging +`モジュールを介して、本格的なデバッガーまで、Pythonでデバッグするために利用可能なツールを見ていきます。 読んだ後、あなたはそれらのどれが与えられた状況で最も適切であるかについて教育された決定をすることができます。

*注:*デバッグとは、*バグ*またはソフトウェアの欠陥が発見された後にその根本原因を探し、それらを修正するための手順を実行するプロセスです。

*バグ*という用語には、その名前の由来についてhttps://en.wikipedia.org/wiki/Debugging#Origin_of_the_term[amusing story]があります。

トレース

*印刷デバッグ*または*穴居人デバッグ*とも呼ばれ、最も基本的なデバッグ形式です。 少し時代遅れですが、それでもまだ強力であり、用途があります。

アイデアは、プログラム実行のパスをたどり、突然停止するか、誤った結果が得られるまで、問題のある正確な命令を特定することです。 そのためには、慎重に選択された場所で目立つ言葉で印刷文を挿入します。

丸め誤差を示すこの例を見てください。

>>>

>>> def average(numbers):
...     print('debug1:', numbers)
...     if len(numbers) > 0:
...         print('debug2:', sum(numbers))
...         return sum(numbers)/len(numbers)
...
>>> 0.1 == average(3*[0.1])
debug1: [0.1, 0.1, 0.1]
debug2: 0.30000000000000004
False

ご覧のとおり、関数は期待値「0.1」を返しませんが、合計が少しずれているため、これがわかっています。 アルゴリズムのさまざまなステップで変数の状態をトレースすると、問題がどこにあるかを知ることができます。

この方法はシンプルで直感的で、ほとんどすべてのプログラミング言語で機能します。 言うまでもなく、これは学習プロセスのすばらしい練習です。

一方、より高度なテクニックを習得すると、バグをより迅速に見つけることができるため、元に戻すことは困難です。 トレースは手間のかかる手動プロセスであり、さらに多くのエラーをすり抜けることができます。 ビルドとデプロイのサイクルには時間がかかります。 その後、誤って本物に触れることなく、作成したすべての `+ print()+`呼び出しを慎重に削除することを忘れないでください。

また、コードを変更する必要がありますが、常に変更できるとは限りません。 リモートWebサーバーで実行されているアプリケーションをデバッグしている場合や、*事後*の方法で問題を診断したい場合があります。 単に標準出力にアクセスできない場合があります。

それこそが、https://realpython.com/courses/logging-python/[logging]が輝く場所です。

ロギング

eコマースウェブサイトを運営しているふりをしてみましょう。 ある日、怒った顧客が、失敗した取引について不平を言って電話をかけ、お金を失ったと言います。 彼はいくつかのアイテムを購入しようとしたと主張しますが、結局、その注文を完了するのを妨げるいくつかの不可解なエラーがありました。 しかし、彼が銀行口座を確認したとき、お金はなくなっていました。

誠実に謝罪し、払い戻しを行いますが、これが今後再び発生することを望みません。 どのようにデバッグしますか? 出来事の痕跡があれば、理想的にはイベントの時系列のリストとそのコンテキストの形で。

印刷デバッグを実行していることに気づいたときはいつでも、永続的なログメッセージに変換することを検討してください。 これは、アクセスできない環境で問題が発生した後に分析する必要がある場合に、このような状況で役立つ場合があります。

ログの集約と検索のための洗練されたツールがありますが、最も基本的なレベルでは、ログをテキストファイルと考えることができます。 各行は、システム内のイベントに関する詳細情報を伝えます。 通常、個人を特定する情報は含まれませんが、場合によっては法律で義務付けられることがあります。

一般的なログレコードの内訳は次のとおりです。

[2019-06-14 15:18:34,517][DEBUG][root][MainThread] Customer(id=123) logged out

ご覧のとおり、構造化された形式です。 説明的なメッセージの他に、イベントのコンテキストを提供するカスタマイズ可能なフィールドがいくつかあります。 ここには、正確な日時、ログレベル、ロガー名、およびスレッド名があります。

ログレベルを使用すると、メッセージをすばやくフィルタリングしてノイズを減らすことができます。 エラーを探している場合、たとえば、すべての警告やデバッグメッセージを表示する必要はありません。 設定を介して特定のログレベルでメッセージを無効にしたり有効にしたりするのは、コードに触れることなく簡単です。

ロギングを使用すると、デバッグメッセージを標準出力から分離できます。 すべてのログメッセージはデフォルトで標準エラーストリームに送られますが、これはさまざまな色で便利に表示できます。 ただし、個々のモジュールであっても、ログメッセージを個別のファイルにリダイレクトできます!

よくあることですが、ロギングの設定が間違っていると、サーバーのディスクのスペースが不足する可能性があります。 それを防ぐには、*ログローテーション*を設定します。これにより、ログファイルを指定された期間(1週間など)、または特定のサイズに達すると保持します。 それにもかかわらず、古いログをアーカイブすることは常に良い習慣です。 一部の規制では、顧客データを5年間保持することを強制しています!

他のプログラミング言語と比較して、https://realpython.com/python-logging/[Pythonでのロギング]は、 `+ logging +`モジュールが標準ライブラリにバンドルされているため、より簡単です。 わずか2行のコードでインポートして設定するだけです。

import logging
logging.basicConfig(level=logging.DEBUG)

*ルートロガー*にフックされているモジュールレベルで定義された関数を呼び出すことができますが、より一般的な方法は各ソースファイルの専用ロガーを取得することです:

logging.debug('hello')  # Module-level function

logger = logging.getLogger(__name__)
logger.debug('hello')   # Logger's method

カスタムロガーを使用する利点は、よりきめ細かな制御です。 これらは通常、 `+ name +`変数で定義されたモジュールにちなんで名前が付けられます。

注意: Pythonには多少関連する `+ warnings `モジュールがあり、メッセージを標準エラーストリームに記録することもできます。 ただし、アプリケーションの範囲はより狭く、そのほとんどはライブラリコードですが、クライアントアプリケーションは ` logging +`モジュールを使用する必要があります。

とはいえ、 `+ logging.captureWarnings(True)+`を呼び出すことで、それらを連携させることができます。

`+ print()+`関数からロギングに切り替える最後の理由の1つは、スレッドセーフです。 次のセクションでは、前者は複数の実行スレッドでうまく機能しないことがわかります。

デバッグ

真実は、トレースもロギングも実際のデバッグとはみなされないということです。 実際のデバッグを行うには、以下を実行できるデバッガーツールが必要です。

  • 対話形式でコードをステップ実行します。

  • 条件付きブレークポイントを含むブレークポイントを設定します。

  • メモリ内の変数を内省します。

  • 実行時にカスタム式を評価します。

ターミナルで実行される粗いデバッガーは、「Pythonデバッガー」の + pdb + という名前ですが、標準ライブラリの一部として配布されます。 これにより、常に利用可能になるため、リモートデバッグを実行するための唯一の選択肢になる可能性があります。 おそらく、それがそれに慣れる正当な理由でしょう。

ただし、グラフィカルインターフェイスは付属していないため、https://realpython.com/python-debugging-pdb/[using + pdb +]は少し注意が必要です。 コードを編集できない場合は、モジュールとして実行し、スクリプトの場所を渡す必要があります。

$ python -m pdb my_script.py

それ以外の場合は、コードにブレークポイントを直接設定できます。これにより、スクリプトの実行が一時停止され、デバッガーにドロップされます。 これを行う古い方法では、2つの手順が必要でした。

>>>

>>> import pdb
>>> pdb.set_trace()
--Return--
> <stdin>(1)<module>()->None
(Pdb)

これは対話型のプロンプトを表示しますが、最初は恐ろしく見えるかもしれません。 ただし、この時点でネイティブPythonを入力して、ローカル変数の状態を調べたり変更したりできます。 それとは別に、コードのステップ実行に使用したいデバッガ固有のコマンドはほんの一握りしかありません。

*注:*デバッガーを1行にスピンアップするための2つの命令を置くのが一般的です。 これにはセミコロンを使用する必要がありますが、これはPythonプログラムではめったに見つかりません。

import pdb; pdb.set_trace()

確かにPythonicではありませんが、デバッグが完了したら削除することを忘れないでください。

Python 3.7以降、組み込みの `+ breakpoint()+`関数を呼び出すこともできますが、これは同じことを行いますが、よりコンパクトな方法で、追加のhttps://realpython.com/python37-new-featuresを使用します。/#the-breakpoint-built-in [ベルとホイッスル]:

def average(numbers):
    if len(numbers) > 0:
        breakpoint()  # Python 3.7+
        return sum(numbers)/len(numbers)

ほとんどの場合、コードエディターと統合されたビジュアルデバッガーを使用します。 PyCharmには高性能を誇る優れたデバッガーがありますが、https://realpython.com/python-ides-code-editors-guide/[plenty代替IDEの]]デバッガー、有料および無料の両方で。

デバッグは、格言的な特効薬ではありません。 ロギングまたはトレースがより良いソリューションになる場合があります。 たとえば、https://en.wikipedia.org/wiki/Race_condition [race conditions]などの再現が難しい欠陥は、多くの場合、一時的なカップリングに起因します。 ブレークポイントで停止すると、プログラム実行の小さな一時停止が問題を隠してしまうことがあります。 https://en.wikipedia.org/wiki/Uncertainty_principle [ハイゼンベルグの原理]のようなものです。同時にバグを測定して観察することはできません。

これらの方法は相互に排他的ではありません。 彼らはお互いを補完します。

スレッドセーフ印刷

以前、スレッドセーフの問題について簡単に触れ、「+ print()」関数よりも「 logging +」を推奨しました。 まだこれを読んでいる場合は、https://realpython.com/intro-to-python-threading/[スレッドの概念]に慣れている必要があります。

スレッドセーフとは、複数の実行スレッド間でコードを安全に共有できることを意味します。 スレッドの安全性を確保するための最も簡単な戦略は、 immutable オブジェクトのみを共有することです。 スレッドがオブジェクトの状態を変更できない場合、その一貫性を損なうリスクはありません。

別の方法では、*ローカルメモリ*を利用して、各スレッドが同じオブジェクトの独自のコピーを受け取るようにします。 これにより、他のスレッドは、現在のスレッドで行われた変更を見ることができません。

しかし、それで問題は解決しませんか? 多くの場合、共有リソースを変更できるようにすることで、スレッドが協力することを望みます。 このようなリソースへの同時アクセスを同期する最も一般的な方法は、*ロック*することです。 これにより、一度に1つまたは場合によってはいくつかのスレッドへの排他的な書き込みアクセスが許可されます。

ただし、ロックは高価であり、同時スループットを低下させるため、原子変数*や *compare-and-swap アルゴリズムなど、アクセスを制御する他の手段が発明されました。

Pythonでは、印刷はスレッドセーフではありません。 `+ print()`関数は、共有グローバル変数である標準出力への参照を保持します。 理論的には、ロックがないため、複数の「 print()」呼び出しからのテキストのビットが絡み合って、「 sys.stdout.write()+」の呼び出し中にコンテキストの切り替えが発生する可能性があります。

*注意:*コンテキストスイッチとは、あるスレッドがその実行を自発的または非自発的に停止し、別のスレッドが引き継ぐことを意味します。 これは、関数呼び出しの途中であっても、いつでも発生する可能性があります。

ただし、実際にはそれは起こりません。 どんなに頑張っても、標準出力への書き込みはアトミックに思えます。 あなたが時々観察するかもしれない唯一の問題は、台無しにされた改行にあります:

[Thread-3 A][Thread-2 A][Thread-1 A]

[Thread-3 B][Thread-1 B]


[Thread-1 C][Thread-3 C]

[Thread-2 B]
[Thread-2 C]

これをシミュレートするために、基礎となる `+ .write()+`メソッドをランダムな時間スリープさせることにより、コンテキスト切り替えの可能性を高めることができます。 How? 前のセクションですでに知っているように、それをearlier笑することで:

import sys

from time import sleep
from random import random
from threading import current_thread, Thread
from unittest.mock import patch

write = sys.stdout.write

def slow_write(text):
    sleep(random())
    write(text)

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        print(f'[{thread_name} {letter}]')

with patch('sys.stdout') as mock_stdout:
    mock_stdout.write = slow_write
    for _ in range(3):
        Thread(target=task).start()

最初に、元の `+ .write()`メソッドを変数に保存する必要があります。変数は後で委任します。 次に、偽の実装を提供します。これには、実行に最大1秒かかります。 各スレッドは、名前と文字を使用していくつかの ` print()+`呼び出しを行います:A、B、C

以前にモックセクションを読んだ場合、印刷がそのように誤動作する理由がすでにわかっているかもしれません。 それにもかかわらず、それを非常に明確にするために、 `+ slow_write()+`関数に渡された値をキャプチャできます。 毎回わずかに異なるシーケンスが表示されます。

[
    '[Thread-3 A]',
    '[Thread-2 A]',
    '[Thread-1 A]',
    '\n',
    '\n',
    '[Thread-3 B]',
    (...)
]

`+ sys.stdout.write()`自体はアトミック操作ですが、 ` print()+`関数の1回の呼び出しで複数の書き込みが発生する可能性があります。 たとえば、改行は残りのテキストとは別に書き込まれ、それらの書き込み間でコンテキストの切り替えが行われます。

注意: Pythonの標準出力のアトミックな性質は、バイトコード命令のロックを適用するhttps://realpython.com/python-gil/[Global Interpreter Lock]の副産物です。 ただし、多くのインタープリターフレーバーにはGILがないため、マルチスレッド印刷では明示的なロックが必要です。

改行文字を手動で処理することにより、メッセージの不可欠な部分にすることができます。

print(f'[{thread_name} {letter}]\n', end='')

これにより、出力が修正されます。

[Thread-2 A]
[Thread-1 A]
[Thread-3 A]
[Thread-1 B]
[Thread-3 B]
[Thread-2 B]
[Thread-1 C]
[Thread-2 C]
[Thread-3 C]

ただし、 `+ print()`関数は空のサフィックスに対して別の呼び出しを行っているため、役に立たない ` sys.stdout.write( '')+`命令に変換されます。

[
    '[Thread-2 A]\n',
    '[Thread-1 A]\n',
    '[Thread-3 A]\n',
    '',
    '',
    '',
    '[Thread-1 B]\n',
    (...)
]

本当にスレッドセーフなバージョンの `+ print()+`関数は次のようになります。

import threading

lock = threading.Lock()

def thread_safe_print(*args, **kwargs):
    with lock:
        print(*args, **kwargs)

その関数をモジュールに入れて、他の場所にインポートできます:

from thread_safe_print import thread_safe_print

def task():
    thread_name = current_thread().name
    for letter in 'ABC':
        thread_safe_print(f'[{thread_name} {letter}]')

現在、各 `+ print()+`リクエストごとに2つの書き込みを行っていますが、1つのスレッドのみがストリームと対話でき、残りは待機する必要があります。

[
    # Lock acquired by Thread-3
    '[Thread-3 A]',
    '\n',
    # Lock released by Thread-3
    # Lock acquired by Thread-1
    '[Thread-1 B]',
    '\n',
    # Lock released by Thread-1
    (...)
]

ロックが共有リソースへのアクセスを制限していることを示すコメントを追加しました。

*注意:*シングルスレッドコードでも、同様の状況に巻き込まれる可能性があります。 具体的には、標準出力と標準エラーストリームを同時に印刷する場合。 片方または両方を個別のファイルにリダイレクトしない限り、両方が単一のターミナルウィンドウを共有します。

逆に、 `+ logging +`モジュールは設計上スレッドセーフです。これは、フォーマットされたメッセージにスレッド名を表示する機能に反映されています。

>>>

>>> import logging
>>> logging.basicConfig(format='%(threadName)s %(message)s')
>>> logging.error('hello')
MainThread hello

常に `+ print()+`関数を使用したくない場合があるもう1つの理由です。

Python Print Counterparts

今では、 `+ print()`について知っておくべきことがたくさんあります! ただし、サブジェクトは、対応するものについて少し話をせずには完成しません。 ` print()+`は出力に関するものですが、入力用の関数とライブラリがあります。

ビルトイン

Pythonには、ユーザーからの入力を受け入れるための組み込み関数が付属しています。これは予想どおり「+ input()+」と呼ばれます。 通常はキーボードである標準入力ストリームからデータを受け入れます。

>>>

>>> name = input('Enter your name: ')
Enter your name: jdoe
>>> print(name)
jdoe

この関数は常に文字列を返すため、それに応じて解析する必要がある場合があります。

try:
    age = int(input('How old are you? '))
except ValueError:
    pass

promptパラメーターは完全にオプションであるため、スキップしても何も表示されませんが、機能は引き続き機能します。

>>>

>>> x = input()
hello world
>>> print(x)
hello world

それにもかかわらず、アクションを説明する呼び出しをスローすると、ユーザーエクスペリエンスが大幅に向上します。

注意: Python 2の標準入力から読み取るには、代わりに `+ raw_input()`を呼び出す必要があります。 残念ながら、誤解を招くような名前の「 input()+」関数もありますが、これはわずかに異なる動作をします。

実際、標準ストリームから入力を取得しますが、Pythonコードであるかのように評価しようとします。 これは潜在的な*セキュリティ脆弱性*であるため、この関数はPython 3から完全に削除されましたが、 `+ raw_input()`は ` input()+`に名前が変更されました。

利用可能な機能とその機能の簡単な比較を次に示します。

Python 2 Python 3

raw_input()

input()

input()

eval(input())

おわかりのように、Python 3の古い動作をシミュレートすることはまだ可能です。

`+ input()`を使用してユーザーにパスワードを要求するのは、入力中にプレーンテキストで表示されるため、お勧めできません。 この場合、代わりに ` getpass()+`関数を使用する必要があります。これは、入力された文字をマスクします。 この関数は、同じ名前のモジュールで定義されており、標準ライブラリでも利用できます。

>>>

>>> from getpass import getpass
>>> password = getpass()
Password:
>>> print(password)
s3cret

`+ getpass +`モジュールには、環境変数からユーザーの名前を取得するための別の関数があります:

>>>

>>> from getpass import getuser
>>> getuser()
'jdoe'

標準入力を処理するためのPythonの組み込み関数は非常に限られています。 同時に、より洗練されたツールを提供するサードパーティのパッケージがたくさんあります。

第三者

ユーザーからデータを収集するための複雑なグラフィカルインターフェイスを構築できる外部Pythonパッケージがあります。 機能には次のものがあります。

  • 高度なフォーマットとスタイリング

  • ユーザーデータの自動解析、検証、およびサニタイズ

  • レイアウトを定義する宣言スタイル

  • インタラクティブなオートコンプリート

  • マウスのサポート

  • チェックリストやメニューなどの事前定義されたウィジェット

  • 入力したコマンドの検索可能な履歴

  • 構文の強調表示

このようなツールのデモはこの記事の範囲外ですが、試してみてください。 個人的にはhttps://pythonbytes.fm/[Python Bytes Podcast]でそれらのいくつかについて知るようになりました。 どうぞ:

それでも、Pythonスクリプトに強力な行編集機能を無料で追加する「+ rlwrap +」と呼ばれるコマンドラインツールに言及する価値はあります。 動作するために何もする必要はありません!

数字を追加するための手順を含む3つの手順を理解するコマンドラインインターフェイスを作成したと仮定します。

print('Type "help", "exit", "add a [b [c ...]]"')
while True:
    command,* arguments = input('~ ').split(' ')
    if len(command) > 0:
        if command.lower() == 'exit':
            break
        elif command.lower() == 'help':
            print('This is help.')
        elif command.lower() == 'add':
            print(sum(map(int, arguments)))
        else:
            print('Unknown command')

一見、それを実行すると典型的なプロンプトのように見えます:

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ add 1 2 3 4
10
~ aad 2 3
Unknown command
~ exit
$

ただし、間違えて修正したい場合は、ファンクションキーが期待どおりに機能しないことがわかります。 たとえば、[。keys]#Left#矢印を押すと、カーソルが後方に移動する代わりに次のようになります。

$ python calculator.py
Type "help", "exit", "add a [b [c ...]]"
~ aad^[[D

これで、同じスクリプトを `+ rlwrap +`コマンドでラップできます。 矢印キーを機能させるだけでなく、カスタムコマンドの永続的な履歴を検索したり、オートコンプリートを使用したり、ショートカットを使用して行を編集したりすることもできます。

$ rlwrap python calculator.py
Type "help", "exit", "add a [b [c ...]]"
(reverse-i-search)`a': add 1 2 3 4

それは素晴らしいことではないですか?

結論

これで、Pythonの `+ print()+`関数に関する多くの知識と周辺の多くのトピックで武装しました。 あなたはそれが何であり、どのように機能するのか、そのすべての重要な要素を深く理解しています。 数多くの例から、Python 2からの進化についての洞察が得られました。

それとは別に、次の方法を学びました。

  • Pythonの `+ print()+`でよくある間違いを避ける

  • 改行、文字エンコーディング、バッファリングに対処する

  • テキストをファイルに書き込む

  • ユニットテストで `+ print()+`関数をモックする

  • ターミナルで高度なユーザーインターフェイスを構築する

これがすべてわかったので、ユーザーと通信したり、一般的なファイル形式でデータを生成したりする対話型プログラムを作成できます。 コード内の問題を迅速に診断し、それらから保護することができます。 最後になりましたが、古典的なスネークゲームを実装する方法を知っています。

まだ情報が不足している、質問がある、または単に考えを共有したい場合は、下のコメントセクションでお気軽にお問い合わせください。