1. Qiita
  2. 投稿
  3. Python

Python3.6 から追加された文法機能

  • 10
    いいね
  • 0
    コメント

Python3.6.0 が2016年12月23日にリリースされ、新たにアップデートされた項目がいくつかあります。
What’s New In Python 3.6

やはりPythonを使ってプログラミングをする人にとっては、新たに追加された文法機能などが特に気になるのではないでしょうか?
そこで今回は3.6.0で追加された文法機能である、

  • PEP 498: 書式化済み文字列リテラル
  • PEP 515: 数値リテラル内のアンダースコア
  • PEP 526: 変数アノテーションのシンタックス
  • PEP 525: 非同期ジェネレータ
  • PEP 530: 非同期内包表記

について紹介します。

書式化済み文字列リテラル(f-strings)

Pythonには、文字列(string)クラスにformat()メソッドが用意されており、str.format()を用いて変数置換や値のフォーマットを行うことができます。(このstr.format()の他にもフォーマッティングや変数の埋め込みの手法に %-formatting や string.Template があります。)
ただ、既存の方法はエラーが発生しやすく、柔軟性がなく、冗長な書き方をしないといけないところに問題点があるとのことです。

今回のf-stringsは、いわばformat()に相当する機能で、その特徴は 文字列内に変数や書式、実行式を埋め込める ことができ、 str.format()のように冗長な書き方をしなくてよい ところにあります。

基本的な使い方

接頭辞fを付けた文字列 f'' でf-stringsを宣言することができ、その中でプレースホルダに {} を使います。以下に例を示します。

# 基本的な変数展開 
hoge = 'hogehoge'
f'string value: {hoge}'  #=> 'string value: hogehoge'

str.format()では以下のように書いたものが、上記のように書けるようになったものです。

'string value: {hoge}'.format(hoge='hogehoge')

以降に様々な使い方を紹介しますが、f-stringsは.format(...)が省略できる分、確かに簡略化された書き方ができるというのが魅力ですね。(よくstr.format()を使った行ではPEP8のコードスタイル評価で、80文字をオーバーして怒られてたので、とてもありがたく思います)

様々な使い方

展開したい変数は{}内でネストすることができ、さらに展開した値を使ってフォーマットを指定することができます。

# フォーマットする
float_val = 0.123456
size = 4
f'result: {float_val:.{size}}'  #=> 'result: 0.1235'

{}内では実行式として評価されるため、以下のようにリストやディクショナリの展開、また加減算なども行えます。

# リスト
some_list = ['foo', 'bar', 'baz']
f'list item: {some_list[0]}'  #=> 'list item: foo'

# ディクショナリ
some_dict = {'foo': 'foofoo', 'bar': 'barbar'}
f'dict item: {some_dict["foo"]}'  #=> 'dict item: foofoo'

# operation
some_value = 10
f'add ops: {some_value + 20}'  #=> 'add ops: 30'

f'list length: {len(some_list)}'  #=> 'list length: 3'

{}内で関数を呼ぶこともできます。

# function
def multi(a, b):
    return a * b

x = 10
y = 20
f'multi value: {multi(x, y)}'  #=> 'multi value: 200'

{}は評価の前に暗黙の括弧で囲われるため改行することができます。

class Foo(object):

    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

f'''Instance method call: {Foo("foo").get_name(
    )}'''
#=> 'Instance method call: foo'

接頭辞にfでなく、frを使うことでエスケープした出力が得られます。

# raw f-strings
fr'raw output\n'  #=> 'raw output\\n'

数値リテラル内のアンダースコア

小さな追加機能ですが、数値リテラルにアンダースコア_が使えるようになりました。
ただし、アンダースコアの位置は先頭、末尾、あるいは連続した形では使えず、数字の間か基数指定子の後で入れることができます。
確かに000000...と連続して続いていると、少し見にくかったりしますが、それが解消されることになりそうです。

# 10進数
decimal = 10_000_000.0
print(decimal)  #=> 10000000.0

# 16進数
hexadecimal = 0xCAFE_F00D
print(hexadecimal)  #=> 3405705229

# 2進数
binary = 0b_0011_1111_0100_1110
print(binary)  #=> 16206

# binary string => int casting
binary_string = int('0b_1111_0000', 2)
print(binary_string)  #=> 240

変数アノテーションのシンタックス

PEP484(Python 3.5)で追加されたタイプヒンティング機能では、関数の仮引数についてタイプヒンティングのシンタックスが提供されていました。
今回のPEP526で追加されたのは、変数についてのタイプヒンティングのシンタックスが提供されました。
"変数"は通常の変数に加えて、クラス変数やインスタンス変数が含まれます。

from typing import List

vectors: List[float] = [0.1, 0.2]

counts: Dict[str, int] = {
    'foo': 10,
    'bar': 20,
    'baz': 30,
}

# クラス変数も利用可能
class Foo(object):

    name: str = 'foo'

ちなみにこうしたアノテーションは、Pythonインタプリタはこのアノテーションの結果で影響を受けることは無いので、Pythonランタイム上ではWarningを出したりなどはないです。この機能は今はサードパーティ製のツール(例えばmypy, pytype, PyCharm等)がアノテーション属性を簡単に参照することができるためにある機能、ということを知っておく必要があります。

url: str = ''

url = 100  #=>

print(url)  #=> Warning等が出ること無く 100 と表示

非同期ジェネレータ

PEP492(Python 3.5)で追加された async/await 構文では同一の関数に awaityield が使えませんでしたが、3.6から awaityield が同じ関数に用いることができる非同期ジェネレータがサポートされました。

import asyncio


async def ticker(delay, to):
    for i in range(to):
        yield i
        await asyncio.sleep(delay)


async def run():
    async for i in ticker(1, 10):
        print(i)


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(run())
finally:
    loop.close()

非同期内包表記

3.6から、リスト、セット、辞書内包表記ならびにジェネレータ式内での async for の使用がサポートされました。細かい変更ですが、より簡潔に書けるようになった、というところでしょうか。

import asyncio


async def aiter():
    n = 10
    for i in range(n):
        yield i
        await asyncio.sleep(0.5)

# 非同期内包表記
async def run():
    result = [i async for i in aiter() if i % 2]
    print(result)

# 上記の run() と同一の構文
async def run():
    result = []
    async for i in aiter():
        if i % 2:
            result.append(i)


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(run())
finally:
    loop.close()

--

以上、互換性を気にしなくても良い環境であればぜひ使っていきたいですね。

今回は文法の変更に限って紹介しましたが、3.6からは他にも

  • キーワード引数の順番の保持
  • クラス属性定義の順番の保持
  • secretsモジュールの追加

などの変更があります。詳しくは What’s New In Python 3.6 を参考にしてください。