魔術師見習いのノート

プロフィール

魔術師見習い
Author魔術師見習い-_-.
Twitter魔術師見習い

コンピュータ関係のメモを主に書きます.

MENU

Pythonの特徴

投稿日:
修正日:
タグ:

以前少しだけPythonを触ったが、今回改めてちゃんと覚えようと勉強している。本稿はその学習のメモやまとめ、特に本稿ではPythonの特徴についてのまとめである。学習には主に次の2つの情報源を使用した。

Pythonの方針は次の通りである。


オンラインマニュアル

Pythonは、さまざまなバージョンの公式のオンラインマニュアルが用意されている(例えば2と3に互換性がない)。また、その内容もしっかりしており、いくつかのプログラミング言語で曖昧としているようないくつかの細かな仕様がしっかり規定されている。それゆえ非常に学習し易い。


開発者にとって便利な仕様

Pythonにはコードリーディングやコードライティングを助けるための仕様がいくつもある。以下にそれらをいくつか紹介する。

組み込み関数

Pythonには、コーディングやデバッギング助ける関数がある。また、Pythonではドキュメントの読み書きを行う共通のインタフェースがあり、ドキュメントを読むための関数がある。

  • locals関数
  • globals関数
  • dir関数
  • help関数

locals関数
globals関数
locals関数はローカルの名前空間の、globals関数はグローバルの名前空間の名前(識別子)の一覧を返す。これらの関数はデバッグの際に有効である。例えば次のような関数があったとする。
def func():
  v = 100
  w = "hoge"
  print(locals())
この時、ローカルの名前空間の名前一覧を画面に出力すると次のようになる。
{'x': 'hoge', 'w': 100}
次にglobals関数を使った対話的処理を以下に示す。
user% python3
>>> globals()
{'__builtins__': <module 'builtins' (built-in)>, '__name__': '__main__',
'__doc__': None, '__package__': None}
>>> import sys
>>> v = 0
>>> globals()
{'__builtins__': <module 'builtins' (built-in)>, '__package__': None,
'sys': <module 'sys' (built-in)>, 'v': 0, '__name__': '__main__',
'__doc__': None}
help関数
Pythonには、モジュールやクラス、関数などのドキュメントを読み書きするための共通のインタフェースがある。そしてドキュメントを読むのに使用するインタフェースがhelp関数である。help関数は、環境変数PAGERが設定されていればそのページャを使用する。全てのモジュールやクラスなどにドキュメントをしっかり設定していれば、help関数の使い方さえ覚えていれば、他の関数の使い方を知らなかったり忘れたりしていてもどうにかなるだろう(help関数自身もドキュメントはあるが)。ドキュメントの書き方については、「ドキュメンテーション文字列」で説明する。
help(dir)
Help on built-in function dir in module builtins:

dir(...)
    dir([object]) -> list of strings
    
    If called without an argument, return the names in the current
    scope.
    Else, return an alphabetized list of names comprising (some of) the
    attributes
    of the given object, and of attributes reachable from it.
    If the object supplies a method named __dir__, it will be used;
    otherwise
    the default dir() logic is used and returns:
      for a module object: the module's attributes.
      for a class object:  its attributes, and recursively the
    attributes
        of its bases.
      for any other object: its attributes, its class's attributes, and
        recursively the attributes of its class's base classes.
オブジェクトを指定した場合、その型のドキュメントが表示される。
dir関数
指定したモジュールやクラス、インスタンスなどの名前空間の名前一覧を返す関数である。もし引数を省略した場合は現在のスコープの名前一覧を返す。これを使用すれば、モジュールに含まれたクラスの名前や、クラス内のメンバの名前を調べることができる。そして、名前さえ分かればそれをhelp関数で詳細を調べることができる。例えばsysモジュール内の名前を調べるにはこのようにする。
>>> import sys
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__',
'__package__', '__stderr__', '__stdin__', '__stdout__',
'_clear_type_cache', '_current_frames', '_getframe', '_mercurial',
'_xoptions', 'abiflags', 'api_version', 'argv', 'builtin_module_names',
'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook',
'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
'getfilesystemencoding', 'getprofile', 'getrecursionlimit',
'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace',
'hash_info', 'hexversion', 'int_info', 'intern', 'maxsize',
'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks',
'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2',
'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
'subversion', 'version', 'version_info', 'warnoptions']
class Cls:
  v = 100
  w = "hoge"
この時dir(Cls)は次のような値を返す。
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__',
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
'__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'v', 'w']

ドキュメンテーション文字列

優れたコードには可読性を向上させるためのコメントが、そしてライブラリにはその使い方を正しく理解するためのコメントやマニュアルがある。前述の通り、Pythonにはそれらをサポートするための仕様が備わっている。ドキュメンテーション文字列は、そのようなドキュメントを書くためのインタフェースである。ユーザが関数やクラス、モジュールを定義する場合、ドキュメンテーション文字列を書くことで前述のhelp関数を使ってドキュメントを読むことができる。

関数アノテーション

Pythonでは、関数アノテーションを用いて関数の引数や返り値の説明を記述できる。例えば、次のコードは、関数sumの返り値と第1引数addee、第2引数adderの説明を記述している。

def sum(addee:"被加数", adder:"加数") -> "和":
  return addee + adder
この関数は次のような実行結果となる。
>>> sum(1, 4)
5
関数のドキュメントは、help関数で閲覧できる。
>>> help(sum)
Help on function sum in module __main__:

sum(addee: '被加数', adder: '加数') -> '和'

ドックストリング

Pythonでは、ドックストリングによって関数やクラス、モジュールの説明を記述できる。ドックストリングの記述するには、スイート内の文が始まる前に文字列リテラルを置く。

def sum(addee:"被加数", adder:"加数") -> "和":
  """
  2つの値を受け取ってその加算結果を返す
  """
  return addee + adder
Python文字列リテラルの書き方は4つある。
文字列引用符説明
'string'一重引用符
"string"二重引用符
'''string'''三連一重引用符エスケープなしで改行や引用符を書くことが可能。
"""string""" 三連二重引用符
Help on function sum in module __main__:

sum(addee: '被加数', adder: '加数') -> '和'
    2つの値を受け取ってその加算結果を返す

ドキュメンテーション文字列は、共通化されたコメントのようなもので省略可能である。しかし、省略してもあまりメリットはないだろう。

名前(識別子)

Pythonの名前(識別子)に使用できる文字は、次のいずれかである。

  • 大文字アルファベット
  • 小文字アルファベット
  • アンダースコア(_)
  • 数字
  • ASCII以外の文字(詳しくはPEP-3131を参照されたし)
Cでは、ASCII文字以外の文字を使用できないが、Pythonでは、日本語名の変数や関数などが定義できる。なおCと同様に先頭文字に数字は使用できない。

また、Cでは次の名前はいくつかのライブラリや言語処理系で予約されている。

  • _が接頭辞のグローバルな名前
  • _が接頭辞でそれに大文字の名前
  • __が接頭辞の名前
これは言語的な制約ではなく、名前の競合を防ぐための注意やマナーである。

他にも、Cでは、ユーザが定数を全て大文字にしたり、グローバル変数の接頭辞をg_にしたりすることで、定義した場所以外でもそれらの意図を分かりやすくなるような工夫がある。これはコードの可読性を上げ、コードリーディングを助ける。Pythonには言語仕様で特別な意味を持つ名前があり、その分可読性が高い。

  • _で始まる名前は、モジュールから*(ワイルドカード)でインポートする場合に除外される。
  • クラス内の__で始まるメンバの名前はクラス内でだけ有効である。
なお2つ目のルールは、実際には呼び出せない訳ではなく、Pythonによって名前が変更され、オリジナルの名前でアクセスできない状態となる(それゆえdir関数で名前を調べられる)。

これに加えて、Pythonでメンバ関数を定義する場合、インスタンス変数へのアクセスは、メンバ関数の第一引数を通じて行う。

class Cls:
  def set(self, val):
    self.v = val
  def get(self)
    return self.v
そのため、他の変数(ローカル変数やクラス変数など)との区別がしやすいだろう。

文法

Cでは、空白文字(スペースや改行、タブ)に字句の区切り以外の意味はない。そして、文は;(セミコロン)までを1文として、ブロックは{と}を使って表す。それゆえ"Hello, World"を画面に出力するためのプログラムは次のように記述できる。

int printf(const char *, ...);int func(){int
i=0;while(i<3){printf("Hello, World\n");i += 1;}return 0;}
しかし多くのユーザは、次のような工夫によってコードの可読性を挙げる。
  • 1文を1行に記述して文の区切りを分かりやすくする。
  • 同じブロックの文を同じサイズのインデントにしてブロック階層を分かりやすくする。
これを適用すると、前述のコードは次のように記述できる(改行の位置はユーザのスタイルによって異なる)。
int printf(const char *, ...);
int func()
{
  int i=0;
  while(i<3){
    puts("Hello, World");
    i += 1;
  }
  return 0;
}
Pythonでは、言語仕様によって改行やインデントで文やブロックの区切りを表すように定められている。それゆえ、自然と可読性の高いコードとなる。以下に前述のコードをPythonで記述したものを紹介する。
def func():
  i = 0
  while(i<3):
    print("Hello, World")
    i += 1
  return 0

いくつかの言語には、データのコンセプトを表すがある。よくある組み込み型には、整数や小数、文字を表すものがある。Pythonには、これらの基本的な型に加えて開発者向けのコンセプトを表す型がある。

NotImplemented

Pythonには、未実装を表す型としてNotImplementedがある。この型の値は1つだけで、かつこのオブジェクトは全て同一である。また、評価結果は常に真となる。

def func():
  return NotImplemented

Ellipsis

Pythonには、省略を表す型としてEllipsisがある。この型の値は1つだけで、かつこのオブジェクトは全て同一である。また、評価結果は常に真となる。

def func():
  return Ellipsis

None

Cのvoidに該当する型として、Pythonには値が存在しないことを表すNone型がある。Cのvoid型がオブジェクトも値も持っていないのに対し、None型のオブジェクトは1つだけあり、値も1つ(None)だけある。それゆえ、引数としてNoneを指定でき、返り値もなにも返さない訳ではなくNoneを返す。Pythonの関数がreturn文に到達しなかった場合、その関数はNoneの値を返す。

def func():
  pass

print(func())
Cでは、返り値を持たない関数が存在する。それらはサブルーチンのような意図で記述されるものだが、定義上それは関数である。しかし、Pythonには値がないというコンセプトのデータを返すことで、全ての関数が返り値を持つ。

Noneの値はCのNULLポインタに該当し、評価結果は常に偽となる。


最適化のために

多くのスクリプト言語では、書き方の柔軟性や多様性、利便性などに優れている。Pythonもその1つだが、Pythonはそれに加えてプログラムの処理速度を向上させるようないくつかの工夫がある。以下にいくつか紹介する。

tuple

Pythonには、組み込みシーケンス型のコンテナが2つある。

  • list
  • tuple
この2つの型は基本的にほとんど性質を持つが、tupleがイミュータブルで、listがミュータブルであるという違いがある。

tupleを使用する利点は以下の2つである。

  • ハッシュ(辞書型)のキーとして利用できる。
  • list型より速い。
v = {(1,1):0, (1,2):2, (3, 5):4}
v[(1, 2)]

比較演算

Pythonの比較演算子は以下の通りである。

  • <
  • <=
  • ==
  • !=
  • >=
  • >
  • is
  • is not
  • in
  • not in
Cとは異なり、比較演算子の優先順位は全て同じである。また、Cでは比較演算子は2つの項を受け取り1つの値を返す単純なものであるが、Pythonではブール演算のように左から右に連鎖する。例えばこのように記述する。
10 > func() >= 3
これがCのコードならば、まず"10 > func()"を評価し、その結果として1か0が返され、それと3を>=で比較する意味となる(すなわち常に0になる)。もし範囲を表したいコードを書きたいならば、次のようなコードとなる。
10 > func() && func() >= 3
しかしこのコードはfunc()が2回評価される。 それゆえ、呼び出し毎に返り値が異なる場合に問題となる。また、2回呼び出される分冗長である。これを避けるため、Cでは次のように記述する。
10 > (tmp=func()) && tmp >= 3
これに対してPythonはこのように記述するだけで良い。
10 > func() >= 3
この時、"10 > func()"の評価結果がFalseの場合は処理を終了してその結果を返し、Trueの場合、func()の結果と3を>=演算で比較し、その結果(TrueかFalse)を返す。それゆえコードの可読性が高いだけでなく、効率が良い。


注意点

Cっぽい言語としてPythonを使おうとすると,いくつかの落とし穴がある。前述に紹介した比較演算などがそれである。また,わかりづらい表現も1つでない。ここでは,それらのいくつかを紹介する。

除算

Pythonの除算には2種類がある。型を持つ多くの言語では,整数同士の除算の場合,結果は整数を返し,余りはモジュロ演算によって計算される。これに対し,Python3の場合は次のような除算結果となる。

>>> 5/2
2.5
>>> 5//2
2
なお前述の結果はPythonのバージョンが3の結果で,2の場合は以下のようになる。
>>> 5/2
2
>>> 5//2
2
>>> 5.0//2
2.0
>>> 5.0/2
2.5

また,計算機の世界では符号付き除算の結果は,言語や実行環境に依存する。Pythonの場合,それは言語の仕様として定義されている。ここで気をつけないといけないのは,数学などで習う結果とは異なる結果を返す点である。 Pythonのモジュロ演算の結果は、0か符号が常に除数(この場合y)と同じになる。モジュロ演算の結果の絶対値は、常に除数以下になる。また、Pythonでは、切り捨て除算演算結果と除数の積に剰余を加えた値は、元の値と常に等しい。 以下に例を示す。

切り捨て除算演算除算結果モジュロ演算剰余
5//225%21
-5//-22-5%-2-1
-5//2-3-5%21
5//-2-35%-2-1

2進数

Pythonで2進数を扱う場合,いくつかの注意が必要である。Pythonは負の値を表現するのに2の補数が使用される。それゆえ,ビット反転と加算を使うと次のように符号を反転できる。

>>> 5
5
>>> (~5)+1
-5
>>> ~5
-6
それにも関わらず,負の値の時,2の補数の文字列を返す関数は,次のような結果を返す。
>>> bin(5)
'0b101'
>>> bin(-5)
'-0b101'
同様に2進数のリテラルを記述する場合,マイナスを表現するには次のように表現する。
>>> 0b101
5
>>> -0b101
-5


デコレータ

いくつかの言語では、次のような関数やクラスのための関数やクラスを定義できる。

  • 関数やクラスを受け取りその振る舞いを変更する関数
  • クラス生成の振る舞いを変更するクラス
Pythonでは、このようなものを記述するための仕様がある。これはコードの意図を分かりやすくし、可読性を向上させる。

関数デコレータ

Pythonには、関数に機能を明示的に追加または変更するための機能がある。それが関数デコレータである。Pythonを含むいくつかの言語では、関数を別の関数に渡すことができる。これを利用すれば関数デコレータと同様のコードを記述できるが、関数デコレータを使用すればその意図を明示的に表すことができる。関数デコレータは次のように定義し、使用できる。

def decorator_called(f):
  def wrapper():
    print("called ", f.__name__)
    return f()
  return wrapper

@decorator_called
def hello():
  print("Hello, World")
hello関数を呼び出した結果を以下に示す。
called  hello
Hello, World
このように関数デコレータは、関数定義の際に生成する関数に対して機能の追加や変更ができる。

関数デコレータには以下のようなものが用意されている。
  • @staticmethod
  • @classmethod
  • @abstractmethod
ちなみにabstractmethodはabcモジュールで定義されている。

クラスデコレータ

デコレータは関数だけでなく、クラスにも適用できる。これをクラスデコレータという。関数デコレータのようにクラスデコレータは、クラス定義の際にクラス情報を受け取り処理を加えたクラスを生成できる。

import sys
def py_major_version(major):
  def wrap(cls):
    if sys.version_info[0] < major:
      raise RuntimeError("version error")
    return cls
  return wrap

@py_major_version(3)
class Hoge:
  pass
このコードは、Pythonのメジャーバージョンが2以下の場合に例外を発生させる。 Python2の場合このコードは問題なく実行できるが、python2で実行すると次のような結果となる。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in wrap
RuntimeError: version error
クラスデコレータは、クラスが定義された(クラスオブジェクトが生成された)後で呼び出される。これに対し、クラスの生成方法そのものを変更する方法がある。それをメタクラスという。

メタクラス

メタクラスは、クラスを生成するためのクラスである。

def ham(self, arg):
  print(arg)

class MetaSpam(type): #組込み型typeを基に
  @classmethod
  def __prepare__(metacls, name, bases):
    print(name)
    print(bases)
    return {'ham':ham}

class Hoge(metaclass=MetaSpam):
  pass
メタクラスのメンバ関数
関数説明
__prepare__(metacls, name, bases)classブロックの実行前に呼び出されるクラスメソッド。ブロックに含まれる変数やメソッドの名前とオブジェクトの辞書を返す。
__init__(cls, bases, classdict)クラスオブジェクトの初期化
__new__(metacls, bases, classdict)クラスオブジェクトの生成
変数名説明
nameクラス名
bases基底クラスを格納したタプル
clsクラス
metaclsメタクラス
classdictクラスのメンバを格納した辞書
メタクラスは次のように指定する。
  • class クラス名(metaclass=メタクラス)
  • class クラス名(親クラス, metaclass=メタクラス)
  • class クラス名(metaclass=メタクラス, 引数名=引数)

用意されたメタクラスには次のようなものがある。

abc.ABCMeta仮想サブクラス実際には継承関係にないクラスの基底クラスとなれる


ライブラリ

Pythonには、標準や外部を含め、実にさまざまなライブラリがある。以下にいくつか紹介する。

モジュールサンプルコード
sys
import sys
print(sys.argv)
os
import sys
import os

r, w = os.pipe()
pid = os.fork()
if pid:
  os.close(w)
  out = os.read(r, 10)
  while True:
    buf = os.read(r, 10)
    if buf:
      out += buf
    else:
      break
  os.write(sys.stdout.fileno(), out)
  os.close(r)
else:
  os.close(r)
  os.dup2(w, sys.stdout.fileno())
  os.close(w)
  os.execlp("ls", "ls", "-l")
re
import re
m = re.match("^a(.*?)o", "aiueo")
print(m.group(1)) # iueを抽出
gtk
import gtk

win = gtk.Window()
win.show_all()
webkit
import gtk
import webkit

win = gtk.Window()
web = webkit.WebView()

web.load_uri("http://www.google.co.jp")
win.add(web)
win.show_all()
GTKとWebKitはPython2で行なっている。なお各ライブラリ(モジュール)の使い方は、help関数を使って調べることができる。

一覧