Python
ポエム
ネタ
トリビア
内包表記

たぶん知らないPythonマイナー文法の世界


概要

私はPythonが好きで10年ぐらい使っています.QiitaでもPythonの記事を結構読みますが,その中で,あまり見かけないPythonのマイナーな文法.マイナーな記法について紹介したいと思います.

実用性は・・・あるかどうかは知りません.マイナーな文法なんて趣味の世界でしかないですし.それはマイナーじゃねぇ!と言われるかもしれませんが,あくまで筆者が他の人のコードで見たことないものです.


リスト以外の内包表現

軽いジャブということで,リスト型以外の内包表現を取り扱いたいと思います.


辞書型

>>> {i:"a%03d"%i for i in range(3)}                                                                                                                                                             

{0: 'a000', 1: 'a001', 2: 'a002'}

いわゆるdictのkey-valueの両方に対して,ループ変数を使うことができます.


集合型

>>> { i%10 for i in range(20)}                                                                                                                                                                  

{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

そもそも集合型がマイナーかもしれません.ライブラリとしての実装はよくありますが,プログラミング言語の仕様レベルで集合型扱ってるものって意外に少ないかもしれないですね.いわゆるユニークな値のみを扱う型です.これに対しても内包表現が使えます.


ジェネレーター

>>> (i*2 for i in range(10))                                                                                                                                                                    

<generator object <genexpr> at 0x7fb2f5e41db0>

ジェネレーターも内包表現できます.個人的には,あんまりジェネレーター自体を変数に入れたりすることが少ないので,お目にかかることはあまりないです.


内包表記内内包表記 != 2変数内包表記

内包表記の中に内包表記を入れることができるのは割と想像に難くないことだと思います.

>>> [ 

... i*3
... for i
... in [
... j*2
... for j
... in range(10)
... ]
... ]
[0, 6, 12, 18, 24, 30, 36, 42, 48, 54]

割と普通に組んでる分にもやることはありますが,あまり可読性が良くないのでお勧めはしない記法です.これはPythonを普通に学んでいるとわかる範囲だと思います.

しかし,以下の表記はいかがでしょうか.

>>> [ 

... (i*3,j*2)
... for i in range(2)
... for j in range(3)
... ]
[(0, 0), (0, 2), (0, 4), (3, 0), (3, 2), (3, 4)]

pythonの内包表現内でforは連続して書けます,

使いどころとしてはかなり限られますが,以下のようなときに便利だったりします.

>>> [ 

... (i,j)
... for i in [-1,0,1]
... for j in [-1,0,1]
... if abs(i) + abs(j) == 1
... ]
[(-1, 0), (0, -1), (0, 1), (1, 0)]

じみーな例ですが,格子を扱いたいときに,縦横の近傍が欲しいときがあります.そういう時に,しれっとかけるのが便利だったりします.地味にflattenするのがめんどくさかったり,このためだけに,for文を重ねて無駄に段数作りたくなかったり,でも手で打ちたくない.みたいなときにやります.あと,ifの節を書き換えれば簡単に斜めも含んだ条件式に出来るので,そういう意味でも楽だったりします.


タプルの作り方

私は簡易的にタプルでデータ構造作ったりすることはあるんですが,普通タプルは

>>> (1,2,3) 

(1, 2, 3)

と使うことが多いと思います.しかし,以下の構文はすべてタプルを返します.

>>> () 

()
>>> 1,2,3
(1, 2, 3)
>>> 1,
(1,)

普通にpythonの勉強をすると,「()で囲まれたリストみたいなのがタプル」と学ぶと思います.

しかし,実際はそんなことはなく,カッコ無しでも大体タプルになります.特に空のタプルが()というのは中々直感と反しているのではないでしょうか.実用上,あんまり空のタプルを使うことも少ない気がしますが・・・


for-else

for文にはelse文を付けることができます.正直使ったことないです.

どういうときに動くかというと,for文がbreakされなかった時,最後に動きます.

>>> for i in range(10): 

... a = 1*1
... else:
... print("run_else")
run_else

一方でbreakされると動かないです.

>>> for i in range(10): 

... a = 1*2
... break
... else:
... print("run_break")
...
>>>


何もしないをする


pass文

何もしない文というものがあります.pythonではpassという文法があります.

>>> for i in range(10): 

... pass
...
>>>

for文の後には必ず何かしらの処理が必要になります.デバッグ等々で何もしない.をしたい.ことがあります.そういう時にpassと書くと何もしないをすることができます.これは見ることがあると思います.


渚の『……』

>>> print(...)

Ellipsis
>>> print(Ellipsis)
Ellipsis

ちょこちょことnumpyのスライス記法で出てくるのですが,圧倒的にGooglabilityが低すぎて調べられないやつの1つだと思います.こいつはEllipsisと言います.「主に拡張スライス構文やユーザ定義のコンテナデータ型において使われる特殊な値」らしいです.また,Ellipsisという単語も組み込みとして入っているので,Ellipsisと打つとEllipsisを表すようです.そしてEllipsisは...です.なんだそれ.

pass文との違いは値として利用可能なので,変数に入ります.

>>> a = ... 

>>> a
Ellipsis

なんだそれ.


虚数はiかjかで宗派が分かる

pythonには虚数も組み込みで入っています.バッテリー同梱済みです.

>>> print(2j)                                                                                                                                                                                   

2j
>>> print((0.5+0.5j) * (0.0 + 1.0j))
(-0.5+0.5j)

虚数を意味しているのはjです.電気系だと電流がIであらわされることが多いので,かぶるのを嫌ってjが使われたりしますが,そういうことなんでしょうか.

あんまり私は虚数を使うプログラムは組まないですが,2次元の回転とかを表すのに便利だったりします.そんなわけで,競技プログラミングやってる人が2次元の幾何だと便利だ.みたいなことを聞いたりします.


累乗・切り捨て

全然マイナーではありませんが,pythonには累乗演算子があります.

>>> 2 ** 3 

8
>>> (-1) ** 0.25
(0.7071067811865476+0.7071067811865475j)

**で,累乗を表すことができます.負の数も0.xの数字で累乗して虚数にできます.これ地味にすごいと思いますが,使ってるの見たことない.

では,//は?

>>> 2 // 3                                                                                                                                                                                      

0
>>> 2 / 3
0.6666666666666666

割り算の切り捨て演算になります.

その昔,「Python3系になったら1/2が0.5を返す!やばい!どうやったら0が手に入るんだ!」みたいなことを言ってた時に,「これからは1//2だ.」みたいなことをよく読んだ気がします.今もそんな感じなのかな.


@演算

pythonには@演算子というものがあります.

正直に言って見たことないですし,実装したこともないです.

>>> class Obj: 

... def __init__(self,value):
... self.value = value
...
... def __matmul__(self,obj):
... return "self:%d@obj:%d"%(self.value,obj.value)
...
>>> a = Obj(2)
>>> b = Obj(3)
>>> print(a@b)
self:2@obj:3

一応行列の演算を表す演算子なのですが,実装されてるライブラリあるんですかね・・・?

たまたま,このマイナー文法企画を思いついたときに公式ドキュメントを漁って見つけましたが,他では聞いたことないです.


まとめ

長いこと触っていると変なものにぶち当たったり変なことを経験していたりします.お金になることは少ないですが.あとはショートコーディングをしていると,こういう知識がついたりします.ショートコーディング自身はただの技芸的な遊びになりがちなのですが,限界まで短くすることをやっていると,今まで見たことなかったような関数や,言語仕様に出会うことがあります.コードを書いてて,なんかダサいなぁとか,なんかもうちょっと書き方あるんじゃないかなーとか感じることがあると思いますが,そういう時は言語のライブラリを使いこなせてなかったり,回りくどい文法を使っていることが多いです.そういう時に,ショートコーディングなんかをしてみると,意外な発見があって,言語の力を引き出す別の面を垣間見れます.プログラミング言語は2週間でマスターする.みたいなことも言われたりしますが,保守しやすいコードだったり,短くきれいなコードを書くには,こういう地味な知識というのが効いてきたりします.ここに書いてある内容はトリビアルなものが多いですが,読んでいただいて,この記事が「なんかどっかで見たことある」ぐらいの記憶の片隅にあれば幸いです.