PythonのリストのとあるメソッドでNoneが返る現象にハマる

f:id:ts0818:20210117143749j:plain

www.itmedia.co.jp

 蚊の触覚にあり、人のにおいを感知する「嗅覚受容体」を組み込んで作ったセンサーで、人の呼気から、がんの進行度を示す微量のにおいを検出することに成功したと、東京大学と神奈川県立産業技術総合研究所の研究チームが発表した。米科学誌に13日掲載された。将来的には、がんの進行度を示す数値「腫瘍マーカー」を呼気から検出することも可能になるといい、研究チームは民間企業と連携し、今後10年以内の実用化を目指している。

蚊の嗅覚で微量のにおい検出成功 東大 がんの予兆感知に期待 - ITmedia NEWS

⇧ これまで生物の生き血を貪り続けてきた蚊が、人類に何かをもたらすことを誰が予想できたということでしょう!まさに「蚊の恩返し」ってやつですか、どうもボクです。

研究者さんに頑張っていただいて一刻も早い実用化を実現して欲しい今日この頃です。

というわけで、今回はPythonについてです。

レッツトライ~。 

 

リストのとあるメソッドの実行結果がNoneを返すんだけど...、それはPythonの仕様らしい

むちゃくちゃハマったんだけど、どうしてくれようか...

docs.python.org

insertremovesort などのリストを操作するメソッドの戻り値が表示されていないことに気が付いたかもしれません。これらのメソッドは None を返しています。

これは Python の変更可能なデータ構造全てについての設計上の原則となっています。

https://docs.python.org/ja/3/tutorial/datastructures.html

他の言語では変更可能なオブジェクトを返して、d->insert("a")->remove("b")->sort(); のようなメソッドチェインを許している場合もあります。

https://docs.python.org/ja/3/tutorial/datastructures.html

⇧ だよね~、Pythonさん、やってくれるじゃないの...

Pythonの変更可能なデータ構造全てについての設計上の原則」っていうのが、一体全体どこに記載されてるものなのか、どのドキュメントを参照した末の発言なのかについては何も説明してはくれないんだね...

 

python.ms

なんで要らないのにわざわざ None を返しているかというと、 Python の関数は値を返さない作りになっているからです。 とりあえず None を返しておくか、という流れになっています。

Python における null, None ってなに? | 民主主義に乾杯

⇧ 上記サイト様によりますと、Pythonの関数は値を返さない作りになってるんだそうな。(その決まりがどのドキュメントに記載されてるかを知りたいところですが...)

 

っていうか、Javaみたいに標準APIのドキュメント(Javaだと「javadoc」とかでメソッドの戻り値なんかが明記されてる)で各メソッドに関して「戻り値」とかの説明が無いのが辛いわな...

Pythonを使ってらっしゃる皆さん、何を参考にコーディングしてらしゃるのかしら?

 

■駄目な例

def rotate_left3(nums):
  lists = nums[1:]
  return lists.append(nums[0])

list_2d = [[1, 2, 3], [11, 9, 5], [7, 0, 0], [1, 2, 1], [0, 1, 0]]
for list_1d in list_2d:
    print('変更前:\n{}'.format(list_1d))
    print('変更後:\n{}'.format(rotate_left3(list_1d)))

■OKな例

def rotate_left3(nums):
  lists = nums[1:]
  lists.append(nums[0])
  return lists

list_2d = [[1, 2, 3], [11, 9, 5], [7, 0, 0], [1, 2, 1], [0, 1, 0]]
for list_1d in list_2d:
    print('変更前:\n{}'.format(list_1d))
    print('変更後:\n{}'.format(rotate_left3(list_1d)))

f:id:ts0818:20210113212306p:plain

⇧ というわけで、Pythonで「None」を戻り値とするメソッドの扱いには注意が必要ということですね。

Javaだと、

docs.oracle.com

void add​(int index, E element)
このリスト内の指定された位置に、指定された要素を挿入します(オプションの操作)。 その位置とそれ以降に要素があればそれらを右に移動させ、各要素のインデックスに1を加えます。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/List.html#add(int,E)

⇧ ちゃんと、メソッドの「戻り値」について記載してくれてるんだけどな。(「jacadoc」の説明が分かり辛いっていつもボヤいてましたけど、説明があるだけでも御の字と思わないといけない感じですかね...)

 

Pythonに話を戻すと、

class list([iterable])

リストの構成にはいくつかの方法があります:

  • 角括弧の対を使い、空のリストを表す: []

  • 角括弧を使い、項目をカンマで区切る: [a][a, b, c]

  • リスト内包表記を使う: [x for x in iterable]

  • 型コンストラクタを使う: list() または list(iterable)

 

https://docs.python.org/ja/3/library/stdtypes.html#lists

⇧ リストの説明にある「内包表記」ってのがね、これまた独特なんですよね...

Pythonのドキュメントでリストを構成する上で、推奨してるっぽいfor文の記述が「内包表記」になってて慣れないな~... 

■宜しくない例

# 宜しくない例
combs = []
for x in [1, 2, 3]:
    for y in [3, 1, 4]:
        if x != y:
            combs.append((x, y))
print('宜しくない例:\n{}'.format(combs))

■良い例

# 良い例
combs = [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
print('良い例:\n{}'.format(combs))

f:id:ts0818:20210113221306p:plain

 まぁ、慣れていかねばならないんでしょうけど...

あと、

teratail.com

⇧ リストの複数の「インデックス」を取得することも「内包表記」でできるっぽい。(もちろん、リストの「要素」も取得できるし、「インデックス」「要素」の両方を同時に取得もできるそうな)

■「内包表記」で記述した場合

nums = [1, 3, 2, 5, 22, 7, 8, 10, 9, 13, 33, 64]
even_nums = [index for index, value in enumerate(nums) if value % 2 == 0]
print(even_nums)

■普通のfor文で記述した場合

nums = [1, 3, 2, 5, 22, 7, 8, 10, 9, 13, 33, 64]
even_nums = []
for index in range(len(nums)):
    if nums[index] % 2 == 0:
        even_nums.append(index)
print(even_nums)

f:id:ts0818:20210117144434p:plain
 

ただ、

qiita.com

結局,内包表記で行う操作をdellistというラムダ式で隠蔽してるだけ.
itemgetterの削除バージョンのようなものはないのだろうか…

[python] リストから複数のインデックスを指定して値を取得・削除するまとめ - Qiita

⇧ 上記サイト様によりますと、「内包表記」がいつでもベストプラクティスとはなり得無さそうな書きっぷりなんですよね。

nihaoshijie.hatenadiary.jp

リスト内包表記がもめたかどうかは知らないのですが。 三項演算子を採用するかどうか議論した時はかなり揉めたそうです。 代入文に至っては採用にあたって Guido が辞めてしまいました。

PEP 8 と矛盾すると感じる3つの構文 -

⇧ 上記サイト様によりますと、「Python」界隈でも意見が様々だったってことですかね。

ちなみに、Guidoさんは、

グイド・ヴァンロッサムGuido van Rossum、1956年1月31日 - )はオランダ出身のアメリカ在住のプログラマーである。

プログラミング言語Pythonの生みの親として知られる。

グイド・ヴァンロッサム - Wikipedia

1989年12月に趣味の延長としてプログラミング言語 Python の開発を始めた。以来、Pythonは平易かつ高機能なスクリプト言語として世界的に普及し、ヴァンロッサムは Pythonの開発に関する最終的な意思決定者、いわゆる「優しい終身の独裁者」として開発コミュニティを先導した

グイド・ヴァンロッサム - Wikipedia

⇧ ってな方らしいです。

Python」の生みの親だったとは。それにしても自分で作ったプログラミング言語が、ここまで人口に膾炙するとは、Guido氏の胸中はいかに。

 

Pythonの起源について、ヴァンロッサム自身は1996年に次のように書いている。

Over six years ago, in December 1989, I was looking for a "hobby" programming project that would keep me occupied during the week around Christmas. My office … would be closed, but I had a home computer, and not much else on my hands. I decided to write an interpreter for the new scripting language I had been thinking about lately: a descendant of ABC that would appeal to Unix/C hackers. I chose Python as a working title for the project, being in a slightly irreverent mood (and a big fan of Monty Python's Flying Circus).— Guido van Rossum、Foreword for "Programming Python"
(日本語訳)6年以上前の1989年12月、私はクリスマス前後の週の暇つぶしのため「趣味」のプログラミングプロジェクトを探していた。オフィスは閉まっているが、自宅にはホームコンピュータがあるし、他にすることがなかった。私は最近考えていた新しいスクリプト言語インタプリタを書くことにした。それは、ABCからの派生であり、Unix/Cハッカーの注意をひきつけるかもしれないと考えた。ちょっとしたいたずら心から(『空飛ぶモンティ・パイソン』の熱烈なファンだったというのも理由の1つ)、プロジェクトの仮称をPythonにした— グイド・ヴァンロッサム、「Programming Python」の序文

グイド・ヴァンロッサム - Wikipedia

⇧ やること無いから作っちゃったって、天才やん...

Ruby」を作った「まつもと ゆきひろ」氏も時間が余ってたから作ったって言ってたけど、どういう頭の構造してるのか...

 

いまいち、Pythonの流儀ってものが捉えにくいですが、もうちょいドキュメントを何とかして欲しいと思う今日この頃ですかね...

今回はこのへんで。