Edited at

他言語経験のあるPython入門者のメモ 機能別12(+1)項目

More than 3 years have passed since last update.


はじめに

他の言語には幾つか触れていますが、最近になってPythonに入門した者です。

その学習過程において、他言語と比べて気になった機能を中心にいくつかピックアップして、12項目(+おまけ1)にまとめてみました。

各項目はほぼ独立していますので、飛ばして読んでも問題ありません。

(Qiitaには目次機能がありますから飛ばし読みも楽ですね!)

動作はPython 3.5.1のインタープリターで確認しています。


空の関数を定義(pass文)

空の関数などを定義するとき、関数の本体を括るのに波括弧({})を使う言語であれば一切迷うことはないのですが、Pythonは括弧を使わずインデントでブロックを表すので、どうすれば良いのか直観的には分かりません。

結論から言うと、pass文を使います。

ちなみに、ここでは書いていませんが、空のクラスの場合も同様です。

7.4. pass 文 (7. 単純文 (simple statement) — Python 3.5.1 ドキュメント)

http://docs.python.jp/3/reference/simple_stmts.html#the-pass-statement

>>> def foo():

... pass
...
>>> foo()
>>>

2行目にインデント付きの空行を書いても、以下のエラーになります。2行目を書かなくても同じエラーになります。

(例では空白だと分からないのでコメントを入れています。)

>>> def foo():

... #
...
File "<stdin>", line 3

^
IndentationError: expected an indented block
>>>

returnを書かずに適当な値だけを書いた行にすれば、pass文の場合と同様に機能するように見えますが、空の関数という意味が明確にならないので作法としては×でしょうね。

>>> def foo():

... pass
...
>>> foo()
>>> def bar():
... 0
...
>>> type(foo())
<class 'NoneType'>
>>> type(bar())
<class 'NoneType'>
>>>

当然ながら、passなので、オブジェクトとして使用することはできません。

>>> o = pass

File "<stdin>", line 1
o = pass
^
SyntaxError: invalid syntax
>>>


インポートと名前空間

以下のようなfoo/bar.pyというファイルがあるとします。

(先頭部分の記述は省略)

import inspect

def baz():
print('module=', __name__, ', func=', inspect.currentframe().f_code.co_name)

def qux():
print('module=', __name__, ', func=', inspect.currentframe().f_code.co_name)

inspect.currentframe().f_code.co_nameは、関数内部から関数名を取得するコードです。)

注:このfooをパッケージとして使用するにはfoo/ディレクトリーに空で良いので__init__.pyというファイルを置く必要があるみたいです。

ただし、この例では__init__.pyが無くても動作しています。

そしてその中の関数を呼び出したい場合は、完全な名前でないといけません。あくまでモジュールがインポートされているだけで、名前空間まではインポートされません。

>>> import foo.bar

>>> bar.baz()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'bar' is not defined
>>> foo.bar.baz()
module= foo.bar , func= baz
>>> foo.bar.qux()
module= foo.bar , func= qux
>>>

fooの中からbarを名前空間としてインポートするには、

from foo import bar

とします。

また、長い名前だと使いにくいですので、asで別名をつけることができます。

もちろん、2つを組み合わせることもできます。

>>> from foo import bar

>>> import foo.bar as fb
>>> from foo import bar as b
>>> bar.baz()
module= foo.bar , func=baz
>>> bar.qux()
module= foo.bar , func=qux
>>> fb.baz()
module= foo.bar , func=baz
>>> fb.qux()
module= foo.bar , func=qux
>>> b.baz()
module= foo.bar , func=baz
>>> b.qux()
module= foo.bar , func=qux
>>>

from foo.bar import bazのようにすると直接baz関数がインポートできてしまいますが、直接インポートするのが定番の一部の関数以外では名前空間が混乱するので通常は使わない方が良いでしょう。(ちなみにこの場合はqux関数がインポートされません。)

ここでは自作の(ローカル)モジュールを呼び出す方法を記載していますが、インストールされているライブラリーを使用する場合も、モジュールサーチパスの各ディレクトリーからの相対位置を指定する点が異なりますが、他は同じように指定します。


グローバル変数に別のスコープから書き換えを行いたい

モジュールの中でブロックに閉じていないスコープで定義した変数はグローバル変数になります。

(この辺の厳密な定義は少し自信ないです。デフォルトの名前空間はmainモジュール、で良いのかな。)

関数内でグローバル変数と同じ名前の変数を新たに定義した場合、ローカル変数になります。

ということは、関数内でグローバル変数を書き換え(再代入)ようとした場合、それはローカル変数として扱われてしまうということです。

関数内でグローバル変数を書き換えたい場合はどうすれば良いでしょうか。

>>> gv = 'global'

>>> def foo():
... gv = 'local'
... print(gv)
...
>>> gv
'global'
>>> foo()
local
>>> gv
'global'
>>>

キーワードglobalを使えば、そのスコープでグローバル変数を使うことを宣言できます。

>>> gv = 'global'

>>> def foo():
... global gv
... gv = 'reassign'
... print(gv)
...
>>> gv
'global'
>>> foo()
reassign
>>> gv
'reassign'
>>>

おまけ:

globals()locals()という関数で、それぞれグローバル変数とローカル変数の辞書が返されます。

グローバル変数はそこそこ大量に出てきますので、実際に実行して確かめてみて下さい。


仮引数のデフォルト値が可変オブジェクトの場合の挙動

Pythonでは仮引数に値を書いてデフォルト値を設定することができます。

>>> def foo(arr=[]):

... arr.append('A')
... print(arr)
...
>>> foo(['X', 'Y'])
['X', 'Y', 'A']
>>> foo()
['A']
>>>

ここまでは想像通りの挙動だと思います。

これに続けて、同じく引数を省略してfoo関数を呼んでみましょう。

>>> foo()

['A', 'A']
>>> foo()
['A', 'A', 'A']
>>>

思ってたのと違う...

どうやらPythonでは、仮引数に指定したデフォルト値は関数宣言の時に初期化されるため、何度呼び出しても常に同じインスタンスが使われてしまいます。そのため、可変オブジェクト(mutable object)を指定するとこのようになります。

デフォルト値を指定する場合は、不変オブジェクト(immutable object)を指定するようにしましょう。

なお、参考文献1では、デフォルト値にNoneを使ってif文で判定する方法が紹介されていました。

(デフォルト値を空タプルにしてlist関数でリストに、じゃダメかな?)


クロージャーのキャプチャーした値を共有した場合の挙動

これは特に問題なく、想定通りのごく普通のクロージャーの動作ですね。

キャプチャーした変数の参照を共有する状態になっているようです。

>>> def foo():

... a = []
... def bar():
... a.append(1)
... def baz():
... a.append(2)
... return a, bar, baz
...
>>> a, bar, baz = foo()
>>> bar()
>>> a
[1]
>>> baz()
>>> a
[1, 2]
>>> bar()
>>> a
[1, 2, 1]
>>>


可変長引数の関数に新たに引数を追加したい

可変長の引数を取る関数に、新たに引数を追加したいけど、後ろに追加しても可変長の引数として渡されてしまうし、前に追加したら互換性がおかしくなるし、どうすれば良いでしょう。

これは他にもサポートしている言語(具体的にはどれのことかは不明)があるので、それを知っていれば簡単かも知れません。

答えは、引数をデフォルト値付きで追加して、呼び出し時は「キーワード形式」で指定する、です。

ちょうど、組み込み関数のmaxでそのサンプルを見ることができます。

max関数(2. 組み込み関数 — Python 3.5.1 ドキュメント)

http://docs.python.jp/3/library/functions.html?highlight=%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF#max

max関数(2. 組み込み関数 — Python 2.7.x ドキュメント)

http://docs.python.jp/2/library/functions.html?highlight=%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF#max

max関数では、Python2.5未満では順序関数keyは指定できませんでしたが、Python2.5以降でできるようになりました。

keyはキーワード形式でしか指定することができませんが、互換性を維持しつつ拡張することができています。

でもまあ、maxのような関数のインターフェイスは、最初からkeyを持っていたとしてもキーワード形式で指定するでしょうね。

以下のコードは、max関数に似た引数の構成を持った関数を宣言した例です。

>>> def foo(*args, opt=None):

... print('args=', args, ', opt=', opt)
...
>>> foo(1, 2, 3)
args= (1, 2, 3) , opt= None
>>> foo(1, 2, 3, opt='hello')
args= (1, 2, 3) , opt= hello
>>>


プロパティー

C#のプロパティー、JavaのBeanにおけるアクセッサーなどのように、クラスの属性(メンバー変数)のアクセスをフックすることができます。

Fooクラスにtextという属性があるとき、property関数を使ってプロパティーを宣言できます。

>>> class Foo:

... def __init__(self, text):
... self.__text = text
... def get_text(self):
... print('get_text')
... return self.__text
... def set_text(self, text):
... print('set_text')
... self.__text = text
... text = property(get_text, set_text)
...
>>> foo = Foo('foo')
>>> foo.text
get_text
'foo'
>>> foo.text = 'FOO'
set_text
>>> foo.text
get_text
'FOO'
>>>

ちなみに、アンダースコアを2つ(__)を名前の先頭につけると、そのままの名前ではアクセスできなくなります。

アクセス制御が可能な言語のprivateメンバーのように完全に隠蔽することはできませんが、公開しない意図を伝えることはできます。

>>> foo.__text

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__text'
>>> foo._Foo__text
'FOO'
>>>

プロパティーは、デコレーターで設定することもできます。こちらの方が新しいスタイルらしいです。

デコレーターについてちょっとだけ。

デコレーターの見た目はC#のアトリビュートやJavaのアノテーションに似ていますが、どちらかというとマクロとかAOPに近いものだと思います。

詳しくはググってください。

>>> class Bar:

... def __init__(self, text):
... self.__text = text
... @property
... def text(self):
... print('property')
... return self.__text
... @text.setter
... def text(self, text):
... print('text.setter')
... self.__text = text
...
>>> bar = Bar('bar')
>>> bar.text
property
'bar'
>>> bar.text = 'BAR'
text.setter
>>> bar.text
property
'BAR'
>>>


リストとタプルの関係

Pythonは動的型付け言語なので、リストは可変、タプルは不変という点を除けば、原則としてどちらもシーケンスとして同じように使えます。

ちなみにPythonのタプルは、空のタプル以外は括弧無しでも書くことができますが、タプルであることを明示するために括弧を付けた方が良いでしょう。

>>> t1 = (1, 2, 3)

>>> t2 = 1, 2, 3
>>> t1
(1, 2, 3)
>>> t2
(1, 2, 3)
>>> type(t1)
<class 'tuple'>
>>> type(t2)
<class 'tuple'>
>>>

リストとタプルは、list関数、tuple関数を使ってお互いに変換できます。

>>> a = [1, 2, 3]

>>> type(a)
<class 'list'>
>>> aa = tuple(a)
>>> aa
(1, 2, 3)
>>> type(aa)
<class 'tuple'>
>>> b = (4, 5, 6)
>>> type(b)
<class 'tuple'>
>>> bb = list(b)
>>> bb
[4, 5, 6]
>>> type(bb)
<class 'list'>
>>>


文字列フォーマットの書式

いわゆるprintf風の書式は古いスタイルらしいです。

ただし、古いと言っても推奨されていないというわけでは無く、古いスタイルは機能が凍結されているだけで、そして新しいスタイルの方がより柔軟に書けるということのようです。

古いスタイルは、書式文字列 % 値のタプルのように書きます。

>>> 'x=%d, y=%d' % (10, 20)

'x=10, y=20'
>>>

新しいスタイルは、文字列のformatメソッドを使います。

こちらのスタイルは、渡す順番を変えられたり、引数に辞書を渡せたりと、いろいろと柔軟に使えます。

>>> dict = {'x': 10, 'y': 20, 'z': 30}

>>> 'z={z:d}, y={y:d}, x={x:d}'.format(**dict)
'z=30, y=20, x=10'
>>>

テンプレート文字列というのもありますが、使いどころが良く分かりませんでした。

>>> from string import Template

>>> s = Template('--- $foo --- $bar ---')
>>> s.substitute(foo='Foo', bar='Bar')
'--- Foo --- Bar ---'
>>> d = {'foo': 100, 'bar': 200}
>>> s.substitute(d)
'--- 100 --- 200 ---'
>>>

詳しくは、下記リファレンスを参照してください。

6.1.3.2. 書式指定例(6.1. string — 一般的な文字列操作 — Python 3.5.1 ドキュメント)

http://docs.python.jp/3/library/string.html#format-examples

6.1.4. テンプレート文字列(6.1. string — 一般的な文字列操作 — Python 3.5.1 ドキュメント)

http://docs.python.jp/3/library/string.html#template-strings


オブジェクトの文字列表現を定義

Rubyのto_s、JavaやJavaScriptのtoStringのようなメソッドを定義するにはどうすれば良いでしょうか。

Pythonのクラスには、__str____repr__という特殊メソッドがあり、それらを実装することで、オブジェクトの文字列表現を定義することができます。

__str__は、str関数で文字列に変換したりprint関数で出力するときの文字列表現を定義します。

__repr__はPython独特で、インタープリター上での文字列表現を定義します。(representationの略?)

>>> class Foo:

... def __init__(self, text):
... self.text = text
... def __str__(self):
... return self.text
... def __repr__(self):
... return '%s(\'%s\')' % (self.__class__.__name__, self.text)
...
>>> Foo('foo')
Foo('foo')
>>> print(Foo('foo'))
foo
>>> str(Foo('foo'))
'foo'
>>>

1つ目は、__repr__メソッドの結果がインタープリター上での表現として出力されています。

2つ目は、Foo()の文字列表現つまり__str__メソッドの結果がprint関数によって出力されています。

3つ目は、同じく__str__メソッドの結果がstr関数によって文字列に変換され、インタープリター上での表現として出力されています。

なお、repr関数を使うと、__repr__メソッドの結果を文字列として取得することができます。

>>> s = repr(Foo())

>>> s
"Foo('foo')"
>>>


ローンパターン(ファイルオープンのケース)

with文を使います。

try-finallyでも可能ですが、withのほうがより簡潔です。

8.5. with 文(8. 複合文 (compound statement) — Python 3.5.1 ドキュメント)

http://docs.python.jp/3/reference/compound_stmts.html#with

下記のデータ

1,2,3

4,5,6
7,8,9

を持つdata.txtというテキストファイルがあったとして、これを数値のタプルのリストに変換してみます。

>>> with open('data.txt', 'r') as f:

... a = [tuple(map(int, line.split(','))) for line in f.read().split()]
...
>>> a
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]
>>>

→この2行のもっと簡潔な書き方を教えていただきました。コメント欄を参照してください。

@shiracamus さん、ありがとうございます。

いろいろ詰め込んでいますが、個々の説明は省きます。

with文の行に注目してください。


登録した順序を維持した辞書を使いたい

Javaで言うところのLinkedHashMapのように、登録した順序でイテレートできる辞書が必要な場合は、

組み込み型の辞書ではなく、collectionsモジュールのOrderedDictを使います。

>>> from collections import OrderedDict

>>> d = dict([('y', 0), ('e', 0)])
>>> d
{'e': 0, 'y': 0}
>>> d['s'] = 0
>>> d
{'s': 0, 'e': 0, 'y': 0}
>>> for x in d:
... print(x)
...
s
e
y
>>> od = OrderedDict([('y', 0), ('e', 0)])
>>> od
OrderedDict([('y', 0), ('e', 0)])
>>> od['s'] = 0
>>> od
OrderedDict([('y', 0), ('e', 0), ('s', 0)])
>>> for x in od:
... print(x)
...
y
e
s
>>>

これ以外のコレクションについて知りたいならば、collectionsモジュールのドキュメントを調べてみると良さそうです。

8.3. collections — コンテナデータ型 — Python 3.5.1 ドキュメント

http://docs.python.jp/3/library/collections.html


おまけ

Pythonの思想やルールに関することを少しだけ。


PEP 8

コーディングルールとして、PEP 8というのがあります。とりあえずこれに従っておけば良いでしょう。

(これは標準機能ではありません。)

→PEP 8自体はガイドライン(文書)であり、「標準機能ではありません」というのはおかしいとご指摘いただきました。

PEP 8ルールをチェックしてくれるpep8や、後述のautopep8は、pipなどでインストールする必要があるので、これらのツールは「標準機能ではない」と書くべきでした。

@ryunix さん、ご指摘ありがとうございます。

はじめに — pep8-ja 1.0 ドキュメント

https://pep8-ja.readthedocs.io/ja/latest/

Python のコーディング規約 PEP8 に準拠する - Qiita

http://qiita.com/ynakayama/items/8616f4c0c6e372de9a42

autopep8というツールで自動適用することもできます。

(人間がしなくて良い仕事はしないに限ります。)

追記:pep8コマンドはpycodestyleに改名されたそうです。

詳しくは、コメント欄をご覧ください。

@matobaa さん、ありがとうございます。


PEP 20

PEP 20というのは、Pythonの思想を表した詩形式の何か。詳しくは下記。

Pythonにまつわるアイデア:PEP 20 - Life with Python

http://www.lifewithpython.com/2013/01/pep-20-the-zen-of-python.html


PEPとは

PEPとは、Python Enhancement Proposalのことです。

詳しくは英語版WikipediaのPythonの"Development"の項などを参照。

Python (programming language) - Wikipedia, the free encyclopedia

#Development

https://en.wikipedia.org/wiki/Python_(programming_language)#Development


Python2との互換性

Python2系とPython3系は互換性がありません。

まだ浅い私の経験上では、特に文字列の扱いが大きく変わっていると感じました。

Web上に散在するサンプルコードは、どれがPython2でどれがPython3なのかが分からないものが割とたくさんありますので、注意が必要です。


おわりに

この記事を書くこと自体がかなり練習になりました。

ここで採り上げた機能は、Pythonのごく基本的な部分だけですので、これからは多くのライブラリーやツールやフレームワークと格闘して本格的にPythonを使っていきたいと思います。

最後までお読みいただき、ありがとうございました。


参考文献

#1

入門 Python3 (O'REILLY)

#2

Python 3.5.1 ドキュメント

http://docs.python.jp/3/

他多数