ラベル is演算子 の投稿を表示しています。 すべての投稿を表示
ラベル is演算子 の投稿を表示しています。 すべての投稿を表示

2015年11月25日

ベンチマーク7 Noneチェック

こちらで「a is None は a == None よりも速い」と述べました。実際にどのくらい速いのか、ベンチマークを計測してみました。


ベンチマークのソースコードです。

from benchmarker import Benchmarker

with Benchmarker(10000000, cycle=3, extra=1) as bench:
    a = 0

    @bench("a is")
    def _(bm):
        for _ in bm:
            a is None

    @bench("a ==")
    def _(bm):
        for _ in bm:
            a == None
計測結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.4.0
## python compiler:     MSC v.1600 32 bit (Intel)
## python platform:     Windows-8-6.2.9200
...

## Ranking     real
a is         3.3138  (100.0) ********************
a ==         3.6289  ( 91.3) ******************

確かに a is None の方が a == None よりも速かったです。劇的と言う程でもありませんが。

2015年11月24日

Noneチェックにはis演算子

ある変数 a に対して、a が None であるどうかを調べるには、is演算子を用いて a is None という判定方法を用いるべきです。こちらで述べたように、これは is演算子の代表的な使い道です。


a is None と a == None の違いは何でしょうか?

まず、a is None の方が若干動作速度が速いです。
これは、a is None が単純な id の比較になるのに対し、a == None は a.__eq__()メソッドが呼ばれるからです。

次に、こちらの方が大きな理由なのですが、仮に a がクラスインスタンスで、__eq__() メソッドをオーバーライドしている場合に、a == None は思わぬ結果となることがあります。
以下の例を見てください。

# __eq__() をオーバーライドしているクラス
>>> class Sample(object):
...     def __init__(self):
...         self.value = 0
...
...     # rhs が None である場合を想定していない
...     def __eq__(self, rhs):
...         return self.value == rhs.value
...

>>> a = Sample()

# a is None は正しく動作
>>> a is None
False

# a == None なら a.__eq__(None) が呼ばれる
>>> a == None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __eq__
AttributeError: 'NoneType' object has no attribute 'value'
オーバーライドした __eq__() メソッドが正しく型チェックを行っていれば、この問題は発生しません。しかし、全てのクラスが正しい作法で __eq__() メソッドを定義しているという保証はありません(特に他人が作ったクラスを使う場合)。

「Noneチェックには常に is演算子を用いる」という方針で Noneチェックをしておけば、__eq__() について悩む必要もなく、汎用的で動作も軽い Noneチェックを行うことができます。よって、Noneチェックには常に is演算子を用いるべきです。

2015年11月20日

is演算子はimmutable変数に使わないこと

is演算子の注意点について述べます。


Python にはビルトインの演算子として is があります。これは変数が同一かどうかを判定するもので、動作は id(lhs) == id(rhs) と同値です。
この is演算子を immutable変数に対して使うと、奇妙な結果になります。
(※ 以下の例はある環境での動作結果です。どの環境でも同じ結果になるとは限りません。)

>>> a = 'abc'

>>> a is 'abc'
True

>>> a is ('a' + 'b' + 'c')
True

>>> a is 'abc'.lower()
False

出てくる文字列はどれも同一ではない文字列です(値はどれも 'abc' ですが)。しかし、is演算子の結果は True であったり False であったりしています。
この結果は、Python がキャッシュを持っていることが原因です。そもそも immutable変数は常時値渡しですから、a is aとしない限り is演算子の結果は常に False となるはずです。しかし、多くの Python の実装では高速化のためにキャッシュを用いています。そして、変数が同じキャッシュを指していれば、それらの変数は同一と見なされます。

キャッシュが使われているのかどうか、キャッシュがどのように使われるのか、などは Python の実装に依存しますので、immutable変数に対して is演算子を使った場合の結果は一定となりません。
よって、is演算子は immutable変数に対して使わないこと、というのがここでの結論となります。



もう一歩進めて言うと、上記の問題もあり、is演算子の使い道はそれほど多くありません。
事実上以下の2つだけと言えます。

  • Noneチェック
  • 自己代入チェック
それぞれの内容については、項を改めて説明します。