LoginSignup
208
201

More than 5 years have passed since last update.

わかっちゃいるけど、やめられない - Pythonあるある凡ミス集

Last updated at Posted at 2016-02-07

言語仕様はわかっていても、ついやってしまう間違いってありますよね。
そんなついやってしまいがちなをうっかりミスをまとめてみました。

文字列の配列でカンマを付け忘れる

Pythonでは文字列リテラルを続けて書くと自動的に連結されます。

>>> "aaa" "bbb"
'aaabbb'

このおかげで、文字列リテラルの配列を定義するときにカンマを付け忘れても、エラーにならない為気付かないことがあります。

>>> [
... 1  # 文字列以外ではカンマを付け忘れるとエラーになる
... 2
  File "<stdin>", line 3
    2
    ^
SyntaxError: invalid syntax

>>> [
... "a"  # 文字列リテラルではカンマを付け忘れてもエラーにならない
... "b",
... "c"
... ]
['ab', 'c']

組み込み関数/組み込み型と同じ名前の変数を作ってしまう

初心者が犯しがちなミスです。
例えばリストを作成して変数に代入するときにlistという変数名を付けてしまう、などです。

>>> list((1, 2, 3))
[1, 2, 3]
>>> list = list((1, 2, 3))
>>> list = list((1, 2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable

listという変数名を付けてしまうと、その後list()が呼べなくなってしまいます。

標準モジュールと同じ名前のモジュールを作ってしまう

何かちょっとした確認をしたいときにtest.*という名前のファイルを作ることはよくあります。
でもPythonでこれをやっちゃダメなんです。標準モジュールにtestがあるので名前が衝突してしまうからです。

test以外にもPython標準モジュールにはごくありふれた名前のものが多いので、同じ名前を使わないように注意しましょう。

Pythonでtest.pyを作るな! - Qiita
[python] 細かすぎて伝わりにくい、Pythonの本当の落とし穴10選 | 私の小岩ホッチキス

要素が一つのタプルでカンマをつけ忘れる

タプルは、x, y, zのようにカンマで区切って定義しますが、要素が一つの時にはx,のように後ろにカンマを付ける必要があります。

>>> 1, 2, 3
(1, 2, 3)
>>> 1,
(1,)

タプルを実際に使用する際には文の構成上、(x, y, z)のように()で囲って使う事が多いのですが、この事から()で囲えばタプルになるとつい思い込んでしまうと、要素が一つの時にカンマをつけ忘れるミスが発生します。

>>> (1, 2, 3)  # ()で囲えばタプルになると思ってしまうと...
(1, 2, 3)
>>> (1)  # 要素が一つの時にカンマをつけ忘れる
1

これを回避するには、要素が複数の時にも最後の要素の後にカンマを付ける習慣にする事です。

>>> (1, 2, 3,)  # 最後の要素の後にカンマを付ける習慣があると...
(1, 2, 3)
>>> (1,)  # 要素が一つの時に間違わない
(1,)

モジュール名と同名のクラスや関数でインポートを間違える

標準モジュールの中には、モジュール名と同名のクラスや関数が存在するものがあります。
datetime.datetimepprint.pprintなどです。

モジュールをインポートする場合と内部のクラスをインポートする場合では、呼び出すコードの書き方は当然変わるのですが、モジュールとクラスが同名だと間違いを起こしやすいです。

特にIDEの補完機能でインポート文を挿入していると、モジュールをインポートしたつもりが内部のクラスをインポートしてしまったり、その逆だったりという事があります。

モジュールをインポートする場合
import datetime
datetime.datetime.now()
クラスをインポートする場合
from datetime import datetime
datetime.now()

属性参照と辞書参照を混同する

Pythonではオブジェクトの属性を参照するときはobject.name、辞書の要素を参照するときはobject["name"]という書き方をします。

JavaScriptではobject.nameobject["name"]も同じ結果になりますが、Pythonでは明確に違う動作になります。

オブジェクトの属性を参照
>>> class A(object):
...     def __init__(self):
...         self.attr1 = 123
... 
>>> a = A()
>>> a.attr1
123
>>> a["attr1"]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'A' object is not subscriptable
辞書の要素を参照
>>> b = {"key1": 123}
>>> b["key1"]
123
>>> b.key1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'key1'

存在チェックの方法も異なり、オブジェクトの属性はhasattr(object, "name")、辞書の要素は"name" in object となります。
辞書オブジェクトに対して属性存在チェックの書き方をしてもエラーにはならないので注意が必要です。

オブジェクト属性の存在チェック
>>> a = A()
>>> "attr1" in a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable
>>> hasattr(a, "attr1")
True
辞書要素の存在チェック
>>> b = {"key1": 123}
>>> "key1" in b
True
>>> hasattr(b, "key1")  # 例外は発生しない!
False

クラス変数をインスタンス変数と錯覚する

Pythonではインスタンス変数とクラス変数は次のように作ります。

class A(object):
    val1 = [1, 2, 3]  # クラス変数
    def __init__(self):
        self.val2 = [4, 5, 6]  # インスタンス変数

Pythonでのクラス変数の書き方は、静的型付け言語でのインスタンス変数の定義に似ています。
また、クラス変数へのアクセスは、インスタンス変数と同じようにself.nameでもアクセスできるので、インスタンス変数を操作しているつもりが、実はクラス変数だったという事があります。

>>> class A(object):
...     val = [1, 2, 3]  # インスタンス変数を作ったつもり
... 
>>> a = A()
>>> b = A()
>>> a.val  # インスタンス変数のようにアクセスできる
[1, 2, 3]
>>> b.val
[1, 2, 3]
>>> a.val.append(10)  # オブジェクトaのインスタンス変数を操作しているつもりが...
>>> a.val
[1, 2, 3, 10]
>>> b.val  # オブジェクトbも変わってしまった...
[1, 2, 3, 10]
>>> A.val  # 実はクラス変数を操作していた
[1, 2, 3, 10]

インスタンスメソッドをインスタンス変数で上書きしてしまう

Pythonではインスタンスメソッドとインスタンス変数で同時に同じ名前を使うことはできません。
関数が第一級オブジェクトで動的型付けの言語である事を考えると当然なのですが、インスタンスメソッドと同じ名前のインスタンス変数に値を代入をしてしまうと、インスタンスメソッドが値で上書きされてしまいます。

>>> class A(object):
...     def hoge(self, x):
...         self.hoge = x  # メソッドhogeとは別のインスタンス変数hogeに設定しているつもり
... 
>>> a = A()
>>> a.hoge(123)  # この時点ではa.hogeはメソッドなので呼べる
>>> a.hoge(456)  # この時点ではa.hogeは数値(123)になっているので呼べない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

※正確にはインスタンスメソッド自体が上書きされているわけではなく、次のような動作からそのように見えるだけです。

  • インスタンスメソッドhogeは、クラス変数hogeとして保持される。(A.hogeで参照できる)
  • self.hogeを参照した時には、インスタンス変数がなければクラス変数が参照される。
  • self.hoge = 123と代入文を書いた時には、クラス変数hogeの有無に関わらず、インスタンス変数hogeに代入される。

クラスメソッドやスタティックメソッドのオーバーライドでデコレーターをつけ忘れる

Pythonではクラスメソッドとスタティックメソッドはデコレーターを付けて定義します。

class A(object):
    @classmethod
    def hoge(cls, x):
        pass
    @staticmethod
    def meke(x):
        pass

また、インスタンスメソッドと同様にクラスメソッドとスタティックメソッドもサブクラスでオーバーライドが可能です。
その際には、オーバーライドしたメソッドにスーパークラスと同じデコレーターを付ける必要があります。(あえてメソッドの特性を変更したい場合は別ですが)

class B(A):
    @classmethod  # オーバーライドする時にも必要
    def hoge(cls, x):
        pass
    @staticmethod  # オーバーライドする時にも必要
    def meke(x):
        pass

オーバーライドする時にデコレーターをつけ忘れてもそれ自体がエラーになる事はありませんが、第1引数の値がインスタンスオブジェクト(self)に変わってしまうので注意が必要です。

PyCharmの補完でやってるとデコレーターまでは引き継いでくれないのでたまにミスります。

デフォルト引数にオブジェクトや動的な値を指定してしまう

関数のデフォルト引数の値は、関数が定義されたタイミングで一度だけ評価されます。
関数を呼び出す度に毎回評価されるわけではありません。

>>> class A(object):
...     def __init__(self):
...         print("A()が呼ばれたよ!")
... 
>>> def hoge(a=A()):
...     print("hoge()が呼ばれたよ!")
... 
A()が呼ばれたよ!  # hogeを定義した直後にデフォルト引数が評価される
>>> hoge()
hoge()が呼ばれたよ!  # hogeを呼び出した時点ではデフォルト引数は評価されない
>>> hoge()
hoge()が呼ばれたよ!

この為、以下のような値をデフォルト引数にすると期待外れの結果になります。

  • 配列や辞書などのオブジェクト

    >>> def append(v, l=[]):
    ...     l.append(v)
    ...     return l
    ... 
    >>> append(1)  # 空配列に1を追加
    [1]
    >>> append(2)  # 空配列に2を追加...のはずが...
    [1, 2]
    
  • 現在日時などの動的な値

    >>> from datetime import datetime
    >>> import time
    >>> def printtime(t=datetime.now()):
    ...     print(t)
    ... 
    >>> printtime()
    2016-02-07 12:30:00.000000
    >>> time.sleep(5)  # 5秒待機
    >>> printtime()
    2016-02-07 12:30:00.000000  # 時間が止まった!?
    

これを回避するには、デフォルト値はNoneにして、関数内で本来の値を設定するようにします。

>>> def append(v, l=None):
...     if not l:
...         l = []
...     l.append(v)
...     return l
... 
>>> append(1)
[1]
>>> append(2)
[2]

配列を渡す引数で文字列を渡してしまっても気付かない

Pythonの文字列は文字のイテレーターとして動作します。
その為、引数が配列の関数を呼び出す時に[]をつけ忘れても気づかない場合があります。

>>> def print_items(items):
...     for i, x in enumerate(items):
...         print("[%d]=%s" % (i, x))
... 
>>> print_items([1, 2])
[0]=1
[1]=2
>>> print_items(["ab"])  # 正しくはこう書くところを...
[0]=ab
>>> print_items("ab")  # []をつけ忘れるとこうなる。
[0]=a
[1]=b
>>> print_items(["a"])  # でも1文字だと...
[0]=a
>>> print_items("a")  # []をつけ忘れても結果は同じなので気付かない
[0]=a
208
201
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
208
201