2017/06/27

Python 3 の print() 関数の使い方

Python 3 の print() まわりの機能をご紹介します。

print('Hello world')

Python 2 と Python 3 では print() の機能が大きく異なります。 Python 2 の print 文については次の記事などを参考にしてみてください。

Python 2 の print 文の使い方


Python 3 の print() の基本的な使い方


Python 3 の print() は文字列を出力するための関数です。

宣言部は次のようになっており、出力対象のオブジェクトの他にもさまざまな引数を受け取ることができます。

print(*objects, sep, end, file, flush)

各引数の意味合いはそれぞれ次のとおりです。

  • objects: 出力対象のオブジェクト。複数個渡すことができる。
  • sep: objects が複数個渡された場合の区切り文字(セパレータ)。デフォルトは半角空白。
  • end: 最後の要素の末尾に付けられる文字。デフォルトは改行文字。
  • file: 出力先。デフォルトは標準出力だが、ファイルオブジェクトなどを指定することもできる。
  • flush: バッファなしで出力するかどうか。デフォルトは False で、出力先によって自動的に定められる。

いくつかのパターンで使ってみましょう。

# 文字列を出力する
print('Python')
# => Python

# 複数の文字列をまとめて出力する
print('This', 'is', 'a', 'chair.')
# => This is a chair.

# 文字列以外のオブジェクトを出力する
print('Total: ', 1000)
# => Total:  1000

print('List: ', [3, 5, 7])
# => List:  [3, 5, 7]

# カスタムクラスのオブジェクトを出力する
class Dog: 
    def __init__(self, name): 
        self.name = name

    def __str__(self): 
        return 'Dog (' + self.name + ')'

jiro = Dog('Jovani')
print(jiro)
# => Dog (Jovani)

# 区切り文字を変える
# デフォルトは半角空白
print('P', 'T', 'A', sep='__')
# => P__T__A

# 通常最後に追加される改行を別の文字列に変える
print('ABC', end='\n-------\n')
print('DEF')
# => ABC
# => -------
# => DEF

# 通常最後に追加される改行を削除する
print('ABC', end='')
print('DEF')
# => ABCDEF

# 標準出力ではなくファイルに出力する
with open('out.txt', 'w') as f: 
    print('result 1', file=f)
    print('result 2', file=f)

print(open('out.txt').read())
# => result 1
# => result 2

print() 関数の機能については以上です。


str.format()


print() 関数といっしょに使うことが多いもののひとつに文字列オブジェクトの str.format() があります。

これは文字列の中に変数を展開して挿入してくれるメソッドです。いくつかサンプルを見てみましょう。

# 細かな指定は行わず {} だけで出力する
key = 'name'
value = 'python'
print('{}: {}'.format(key, value))
# => name: python

# 引数のインデックスを指定して出力する
# この場合、同じ引数を何度も利用できる
value = 'pain'
print("It's your {0} or my {0} or somebody's {0}".format(value))
# => It's your pain or my pain or somebody's pain

# キーワード引数の形式で指定する
print('{fruit}食えば鐘が鳴るなり{temple}'.format(temple='法隆寺', fruit='柿'))
# => 柿食えば鐘が鳴るなり法隆寺

# インデックスを指定して一部を出力する
company = {
  'name': 'Sharp',
  'description': 'Meno tsuke dokoro ga sharp.',
}
print('{0[name]} -- {0[description]}'.format(company))

# アトリビュート名を指定して一部を出力する
class Dog: 
    def __init__(self, name): 
        self.name = name

kiyoshi = Dog('Campanella')
print('name: {0.name}'.format(kiyoshi))
# => name: Campanella

# 型とフォーマットを指定して出力する
print('{:0.2f}'.format(15))
# => 15.00

print('{:^10d}'.format(15))
# => '    15    ' ( 10 文字の中で中央寄せされた 15 )

str.format() はレシーバである文字列の中の {} に引数を挿入して展開してくれます。各 {} に対して、どの引数を挿入するのか、どのようなフォーマットで挿入するのかといったことを細かく指定することができます。

ただ、 str.format() は機能が豊富なので、一気に覚えるのではなく、まず「 format() でどういうことができるのか」だけ把握しておいて、具体的なオプションの指定方法については必要になったときに都度覚えていくのがよいかと思います。

詳しくは公式のドキュメントを参考にしてください。

6.1. string — Common string operations — Python documentation
6.1. string — 一般的な文字列操作 — Python ドキュメント


フォーマットつき文字列リテラル


Python 3.6 以降に限定とはなりますが、 f'' という形式で記述するフォーマットつき文字列リテラルというものも利用できます。

first = '奥山に紅葉踏み分け鳴く鹿の'
second = '声聞く時ぞ秋は悲しき'

tanka = f'{first}\n  {second}'

print(tanka)
# => 奥山に紅葉踏み分け鳴く鹿の
# =>   声聞く時ぞ秋は悲しき

str.format() よりも直感的な形で文字列の中に変数を組み込むことができます。

このフォーマット付きの文字列リテラルに対応する PEP は 498 です。興味のある方は以下のページなどもご覧になってみるとよいかもしれません。

PEP 498 -- Literal String Interpolation | Python.org

2017/06/16

ライブラリ: attrs

Python のパッケージ attrs をご紹介します。

import attr

attrs はカスタムクラスを作成するときのマジックメソッドの記述を省略できる機能を提供するライブラリです。具体的には、クラスのアトリビュート(プロパティ)とイニシャライザ、その他いくつかのマジックメソッドの定義を省略することができます。

名前がよく似た attr というパッケージもあります。今回取り上げるのはそれではなく末尾に s がついた attrs の方なのでご注意ください。

こちらです。

- attrs : Python Package Index

こちらではありません。

- attr : Python Package Index

attrs も attr もコード内では s のつかない import attr でインポートする点は共通なので注意が必要です。

インストール


インストールには pip を使いましょう。

pip install attrs

上述のとおり attrs の末尾の s は必要なのでご注意ください。

使い方


サンプルコードを見ながら使い方を見ていきましょう。

import attr
    
@attr.s
class Order:
    id = attr.ib()
    created_at = attr.ib()

クラス Order の定義に @attr.s と attr.ib() という 2 つのものが使われています。これで Order クラスに id と created_at という 2 つのアトリビュートが追加されました。また、イニシャライザの引数に id と created_at が渡せるようになりました。

試しに Order インスタンスを作ってみましょう。

o1 = Order(5, 1497500000)
print(o1)
# => Order(id=5, created_at=1497500000)
print(o1.id)  # => 5
print(o1.created_at)  # => 1497500000

第 1 引数が id に、第 2 引数が created_at にそれぞれ渡されていることが確認できます。 `__init__()` をまったく書いていないのにこの挙動。これは attrs が裏側でよきようにやってくれているからです。

インスタンスを print() に渡したときの表示もきれいになっていることに注目してください。こちらも attrs の機能で、裏側でよきようにやってくれているためです。

引数はキーワード指定で渡すことも可能です。

o2 = Order(id=5, created_at=1497500000)
print(o2)
# => Order(id=5, created_at=1497500000)

attr.ib() で定義されたアトリビュートをイニシャライザに渡さないとどうなるでしょうか。

o2 = Order()  
# => # TypeError: __init__() missing 2 required positional arguments: 'id' and 'created_at'

TypeError が出ました。

attr.ib() の引数に default を指定するとそのアトリビュートのデフォルト値を設定することができます。デフォルト値が設定されたアトリビュートはイニシャライザの必須から外れます。

import attr

@attr.s
class Order:
    id = attr.ib()
    created_at = attr.ib(default=0)

o4 = Order(id=10)
print(o4)
# => Order(id=10, created_at=0)

デフォルト値はファクトリ機能を使って動的な値にすることもできます。

from datetime import datetime
import attr

@attr.s
class Order:
    id = attr.ib()
    created_at = attr.ib(default=attr.Factory(datetime.now))

o5 = Order(id=15)
o6 = Order(id=20)
print("{}\n{}".format(o5, o6))
# => Order(id=15, created_at=datetime.datetime(2017, 6, 15, 7, 1, 29, 262216))
# => Order(id=20, created_at=datetime.datetime(2017, 6, 15, 7, 1, 29, 262274))

また、 attrs を使って作られたクラスのインスタンスは比較演算子で比較できるようになります。これは attrs の機能を使ってクラスを書くと attrs が比較系のマジックメソッドを自動で登録してくれるためだそうです。

from datetime import datetime
import attr

@attr.s
class Order:
    id = attr.ib()
    created_at = attr.ib(default=attr.Factory(datetime.now))

o7 = Order(25)
o8 = Order(23)
print(o7 < o8)  # => False

now = datetime.now()
o9  = Order(100, now)
o10 = Order(100, now)
print(o9 == o10)  # => True
print(o9 is o10)  # => False

各アトリビュートにはバリデーションロジックをつけることもできます。

from datetime import datetime
import attr
from attr.validators import instance_of

@attr.s
class Order:
    id = attr.ib(validator=instance_of(int))
    created_at = attr.ib(default=attr.Factory(datetime.now))

o11 = Order('15')
# => TypeError

バリデーションはインスタンスの生成時に加えてその他のタイミングでも行うことができます。

o12 = Order(50)
o12.id = 'invalid'
attr.validate(o12)
# => TypeError

シンプルなコードを書くだけで各種マジックメソッドが自動的に定義されるので少し Explicit ではない感じもしますが、ほぼ定型のコードを毎度書くのは少しわずらわしかったりもするので、 attrs を使ってこのあたりが楽できるのはよいかもしれません。

他にもオリジナルのバリデータを指定したりなどさまざまなことができるので、興味のある方は公式のドキュメントをご覧になってみてください。

以上です。

参考

Using attrs for everything in Python | Hacker News
Deciphering Glyph :: The One Python Library Everyone Needs

公式

attrs: Classes Without Boilerplate — attrs documentation
python-attrs/attrs: Python Classes Without Boilerplate

2017/06/06

Python Tips:標準入力がどのように渡されているのかをチェックしたい

今回は Python で標準入力を扱う際に標準入力がどのように渡されているのかをチェックする方法についてご紹介したいと思います。

標準入力の渡し方は大きく分けて、ファイルからのリダイレクトやパイプによって渡す場合とキーボードからインタラクティブに渡す場合の 2 通りに分けることができます。

例えば、リダクレクトやパイプのときにだけ処理を行いたいような場合は次のようなコードを書くことになります。

import sys

if (標準入力がキーボードから渡されている):
    sys.stderr.write("キーボードからの標準入力には対応していません。\n")
    exit()

(やりたい処理)

ここで「(標準入力がキーボードから渡されている)」のところは具体的にどのように書けばいいのでしょうか。早速結論ですが、こちらは sys.stdin の isatty() メソッドを使えば OK です。

import sys

if sys.stdin.isatty():
    sys.stderr.write("キーボードからの標準入力には対応していません。\n")
    exit()

isatty() は読んでそのまま「 is a tty 」の意味らしく、標準入力がキーボードからの入力の場合(あるいは標準入力に何も渡されていない場合)は True を返します。ファイルのリダイレクトやパイプの場合には False を返します。

サンプルを見てみましょう。

check_stdin.py:

# coding: utf-8
import sys

if sys.stdin.isatty():
    sys.stderr.write('パイプあるいはリダイレクトで標準入力を渡してください。\n')
else:
    sys.stderr.write('標準入力をそのまま標準出力に流します。\n')
    sys.stdout.write(sys.stdin.read())

こちらを使うと次のようになります。

$ python check_stdin.py
パイプあるいはリダイレクトで標準入力を渡してください。
$ echo 'hello' | python check_stdin.py
hello
標準入力をそのまま標準出力に流します。

標準入力の渡し方を識別できていることがわかります。

以下はもう少し実用的なサンプルで、 csv 形式のテキストを標準入力から受け取る例です。

read_csv.py:

# coding: utf-8

"""標準入力から csv を読む
"""

import sys
import csv
from itertools import islice


def main():
    """標準入力で与えられた csv を読み込む

    - 最初の 5 行だけ、ヘッダーなしで読み込む
    """
    rows, header = csv_read_stdin(5, True)

    print("Rows: {}".format(list(rows)))


def csv_read_stdin(number, is_headerless):
    """標準入力から csv を読み込む
    """
    if sys.stdin.isatty():
        sys.stderr.write("標準入力はパイプまたはリダイレクトで渡してください。\n")
        exit()
    reader = csv.reader(sys.stdin)
    header = [] if is_headerless else next(reader)
    rows = islice(reader, number)

    return rows, header


if __name__ == "__main__":
    main()

試してみます。

$ python read_csv.py
標準入力はパイプまたはリダイレクトで渡してください。
$ python read_csv.py <<EOS
> Takeda,Shingen
> Takeda,Katsuyori
> EOS
Rows: [['Takeda', 'Shingen'], ['Takeda', 'Katsuyori']]

正しく識別できています。

以上です。シンプルでわかりやすいですねー。