はじめに
他の言語には幾つか触れていますが、最近になって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](http://docs.python.jp/3/reference/compound_stmts.html#with)
下記のデータ
```
1,2,3
4,5,6
7,8,9
```
を持つ`data.txt`というテキストファイルがあったとして、これを数値のタプルのリストに変換してみます。
```pycon
>>> 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`を使います。
```pycon
>>> 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](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/](https://pep8-ja.readthedocs.io/ja/latest/)
Python のコーディング規約 PEP8 に準拠する - Qiita
[http://qiita.com/ynakayama/items/8616f4c0c6e372de9a42](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](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](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/](http://docs.python.jp/3/)
他多数