LoginSignup
9
12

More than 3 years have passed since last update.

入門書には載っていない割と新しいPythonの文法と機能

Posted at

セイウチ演算子とは何ぞ。

先日、Qiitaで記事のトレンドを見ていると"セイウチ演算子"なるもの見かけました。
セイウチ演算子自体は、Pythonの新しい文法なのですが、自分自身、あまり最新仕様というものは追っかけていませんでした。
過去には、「たぶん知らないPythonマイナー文法の世界」という記事を書きましたが、そろそろ自分の知識もアップデートしたい。と思って、最近追加された文法を追ってみることにしました。
プログラミング言語を触るだけなら、for,if,関数定義,クラス定義あたりを覚えていれば大抵何とかはなりますが、短く綺麗に書こうと思うと、専用の構文を使うと非常に効果的です。私としては、Pythonはこの選び方が秀逸なので好きです。
ここでは、すべては紹介しませんが、自分が使いやすそうだなぁ。と思うもののみ取り上げます。
※わかりやすい例が提示できる自信がなかったので、非同期構文は取り上げていません。
※Type Hinting系も話が多岐にわたるので、省略します。
※割と新しいとか言いながら、Python3.5は5年前ぐらいです(

PEP 448 - 追加可能なアンパッキングへの一般化

PEP 448 によって、 * イテラブルアンパッキング演算子と ** 辞書アンパッキング演算子の利用方法が拡張されました。 関数呼び出し で任意の数のアンパッキングで使えるようになりました:

へぇ。と思いました。例えば、Python2.7系だと以下のような動きになります。

>>> def f(a,b,c,d):
...     return 1
...
>>> f(*[0,1,2,3])
1
>>> f(*[0,1],*[2,3])
  File "<stdin>", line 1
    f(*[0,1],*[2,3])
             ^
SyntaxError: invalid syntax

このように*を使い、リストをアンパックしつつ、関数を呼び出す場合、リストは1つしか指定することはできませんでした。しかし、Python3.5からは複数も動くようになっています。

>>> def f(a,b,c,d):
...     return 1
...
>>> f(*[0,1,2,3])
1
>>> f(*[0,1],*[2,3])
1

おー。
Python公式のドキュメントだと、以下のような、タプル、辞書の例も取り上げられています。

>>> {*range(4), 4, *(5, 6, 7)}
{0, 1, 2, 3, 4, 5, 6, 7}

>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}

ちょっと面白いのがrange(4)のようなgeneratorも出来るようになってるんですね。

導入:Python3.5

PEP 498: フォーマット済み文字列リテラル

フォーマット済み文字列リテラルはプレフィックスに 'f' をとり str.format() による書式文字列に似ています。これらには中括弧で囲まれた置換フィールドがあります。置換フィールドは実行時に評価される式で、 format() プロトコルによってフォーマットされます:

>>> name = "Fred"
>>> f"He said his name is {name}."
'He said his name is Fred.'

これは普通に便利なやつです。頭にfのついた文字列だと、{name}の形式で書くだけで文字列展開をやってくれます。これは以前まではショートハンドとして、

name = "Fred"
"He said his name is {name}.".format(**locals())

みたいな書き方は出来ましたが、そんなめんどくさい書き方するぐらいなら、%でいいや。と思って、使っていませんでした。しかし、f文字列が導入されて、こちらの方が圧倒的に打鍵数が少ないですね。

Pythonの公式だと、{}部分を入れ子にする複雑な例も取り上げられていました。

>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"  # nested fields
'result:      12.35'

しかし、ちょっと試したところ変数に入れた後の再展開はされないのですね。

>>> template = "{name}"
>>> f"{template}"
'{name}'

(まぁそこをやろうとするとf文字列だけでチューリング完全とかになりそうですよね・・・)

導入:Python3.6

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

PEP 515 により、可読性向上のために数値リテラル内でアンダースコアを使えるようになりました。

>>> 1_000_000_000_000_000
1000000000000000
>>> 0x_FF_FF_FF_FF
4294967295

まぁあんまり業務では使わないですね・・・しかし、数値計算系でループをぶん回すときに、パッと見た感じで、10万か100万か分かりにくいので、こういうは良いですね。

導入:Python3.6

PEP 553: 組み込みの breakpoint()

文法ではないのですが、デバッグをするうえで非常に便利なので紹介します。

break.py
i = 1
print(i)
breakpoint()
i += 1
print(i)

というコードを書いたとします。これを実行すると、

$ python break.py
1
> /home/kotauchisunsun/work/python_practice/break.py(4)<module>()
-> i += 1
(Pdb) i
1

というかたちで、breakpoint()呼び出したところで、pdbが動き出して、変数の内容が確認できたりします。
IDEビルトインされている場合はあまり関係ありませんが、コマンドラインのみでやりたい場合はこれが便利だったりします。

導入:Python3.7

dict オブジェクトの挿入順序を保存する

dict オブジェクトの挿入順序を保存するという性質が、公式に Python 言語仕様の一部であると 宣言されました 。

Python3.7の好きな新機能という記事で知ったのですが、 辞書型が順序保存するようになりました。 マジか。

python2.7の場合、

$ python2.7
Python 2.7.17 (default, Nov  7 2019, 10:07:09)
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> {"c":1,"b":2,"a":3}.keys()
['a', 'c', 'b']
>>> {"a":1,"b":2,"c":3}.keys()
['a', 'c', 'b']

python3.8系の場合

$ python
Python 3.8.0 (default, Jun 27 2020, 00:43:29)
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> {"c":1,"b":2,"a":3}.keys()
dict_keys(['c', 'b', 'a'])
>>> {"a":1,"b":2,"c":3}.keys()
dict_keys(['a', 'b', 'c'])

前には、それ専用のクラスとしてOrderedDictがcollectionsにありますが、dictが挿入順になったのですね。
これ自身は良いことですが、逆にPython3.7系を想定して書いたコードを、Python3.5系とかに持っていくときにハマりそうですね。この変更は頭に置いておく方が良さそうです。

導入:Python3.7

セイウチ演算子

大きな構文の一部として、変数に値を割り当てる新しい構文 := が追加されました。 この構文は セイウチの目と牙 に似ているため、「セイウチ演算子」の愛称で知られています。

if (n := len(a)) > 10:
    print(f"List is too long ({n} elements, expected <= 10)")

公式のこの例だとうーん?そんなに嬉しくはない・・・という感じでしたが、以下の例が良かったです。

[clean_name.title() for name in names
 if (clean_name := normalize('NFC', name)) in allowed_names]

この例は良いな。と思って、旧来の書き方だと

normailze_name_list = map(x: normalize('NFC',x),names)
clean_name_list = filter(x: x in allowed_names, normalize_name_list)
[x.title() for x in clean_name_list]

という書き方など、結構冗長な部分が多かったのですが、短めにすっきり書けますね。
たまに内包表現のif文として判定に利用した結果を要素として持ち込みたいことがあったので、これは割とうれしいです。

導入:Python3.8

f-string での = を用いたオブジェクトの表現

フォーマット済み文字列リテラル(f-string) において = 指定子が有効になりました。f'{expr=}' のようなf-string は、f-string に書かれた式、イコール記号、式を評価されたものの文字列表現、の順で表示されます。

user = 'eric_idle'
member_since = date(1975, 7, 31)
f'{user=} {member_since=}'
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

私としては、「えっ・・・なぜ・・・」と思いましたが、導入されたようです。
個人的に否定的な意見なのは、これが働くのは基本的にはデバッグの時のみで、あまり本番のコードには入らない構文なので、うーん。と思ってしまいました。それ以外の用途あるのかな?と思って、元の議論を見に行きましたが、やはり主にはデバッグ用途のようです。

導入:Python3.8

感想

割と新しめの言語を見ていると、genericsが入る入らない。だったり、null合体演算子がーとか。エルビス演算子がどうのこうの。みたいな話を聞いたりするのですが、Pythonは割とそういう話を聞かないな。という印象でした。Type Hintingは確かに大きな話ですが、Optionalな話ですし、async/awaitは個人ではあんまり使わず、興味が湧いていませんでした。そんな中で、セイウチ演算子という変なものがPythonに入る。と聞いて、ちょっとそそられたので調べました。この辺の文法は知ってると嬉しい。という程度なので、あまり入門書には載っていない内容だと思います。そういうものはやっていくうちに知るのか、こういう記事で知るのか。というのは結構難しい課題だなぁ。と思います。

参考

https://docs.python.org/ja/3.5/whatsnew/3.5.html
https://docs.python.org/ja/3.6/whatsnew/3.6.html
https://docs.python.org/ja/3.7/whatsnew/3.7.html
https://docs.python.org/ja/3.8/whatsnew/3.8.html

9
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
12