Java や PHP などの一般的なオブジェクト指向言語経験者が Python を書いた時に書いてしまいがちな、誤ったコードを書きます。
私の会社でも、経験の浅い方のコードレビューでたまに見ますので、解説のために記事を書きました。
間違っているコード
class Employee:
name: str = ''
def __init__(self, name: str):
self.name = name
この書き方は、無駄なクラス変数を定義しており、おそらく間違いです。
Python 経験者であれば、そこは @dataclass
デコレータを使ったほうがいいという話になりますが、その話題ではありません。
他の言語の場合
このクラスは、 Java でいうと
public class Employee {
public static String name = "";
public String name;
public Employee(String name) {
this.name = name;
}
}
このコードに相当します。
PHP では
public class Employee {
public static string $name = '';
public string $name;
function __construct(string $name) {
$this->name = $name;
}
}
こうなります。
どちらも、 name
という同名の static 変数 と インスタンス変数 を定義してしまっています。
ちなみに、どちらもコンパイル時にエラーとなりますが、それは無視します。
Java や PHP のコードを見ると、 static 変数の static String name
は、紛らわしいだけで意味が無い変数に見えます。
せめて static 変数でクラス自体の名前? みたいなものを定義する変数が必要なら、インスタンス変数で使う name
と同名の変数名である name
はやめてくれと思います。(実際、コンパイルエラーで使えません。)
それと同じ意味のコードが、冒頭の Python コードです。
Python コードの解説
class Employee:
name: str = '' # name という名前のクラス変数 (staticみたいなもの)
def __init__(self, name: str):
self.name = name # name という名前のインスタンス変数
Python はインスタンス変数の定義というものがそもそもありません。
エディタによっては、 __init__
(コンストラクタ) の中で self
に代入したものをインスタンス変数の定義とみなします。
そのため
class Employee {
public String name;
public Employee(String name){
this.name = name;
}
}
このコードを、Python で真似ると
class Employee:
def __init__(self, name: str):
self.name = name
こうなります。
Python は、クラス定義内に、インスタンス変数の定義は書くことができません。
最初の例のように、 class Employee
の下に name: str = ''
と書くと、それはクラス自体に所属する変数となり、インスタンス変数とは別の変数なので、おそらく使われることがありません。
もし、クラス変数とインスタンス変数を同名で意図的に定義しているとしたら、ミスの発生しやすいコードとなります。
クラス変数とインスタンス変数
それでは、実際に下記のクラス変数がどのように動くか、実験をしてみます。
class Employee:
name: str = 'クラス変数'
def __init__(self, name: str):
self.name = name
print('Employee.name:', Employee.name)
Employee.name: クラス変数
↑ クラスをインスタンス化せずに、直接クラス変数を表示しています。
employee = Employee('インスタンス変数')
print('employee.name:', employee.name)
employee.name: インスタンス変数
↑ クラスのインスタンスを作り、コンスタラクタの引数で name を設定しています。
employee2 = Employee('インスタンス変数2')
print('employee2.__class__.name:', employee2.__class__.name)
employee2.__class__.name: クラス変数
↑ 新しいインスタンスを作りました。 インスタンスの .__class__
で、そのインスタンスのクラスにアクセスできます。
print('id(employee.__class__):', id(employee.__class__), ', id(employee2.__class__):', id(employee2.__class__))
id(employee.__class__): 67727568 , id(employee2.__class__): 67727568
↑ id()
は、そのクラスのメモリの番地みたいなものを表示する関数です。同じIDであれば、同じオブジェクトであることを表します。最初に作ったインスタンス employee と、次に作った employee2 の __class__
は、同じメモリを参照しています。
print(employee.__class__ is employee2.__class__)
True
↑演算子 is は、同一のオブジェクトかどうかを表します。ID が同じであれば True になります。
employee.__class__
と employee2.__class__
は、同一のメモリを参照しており、全く同じものです。
print(employee.__class__ is Employee)
True
そして、 employee.__class__
は Employee
と全く同じものです。
employee2.__class__.name = '破壊したクラス変数'
print('employee.__class__.name:', employee.__class__.name) # employee2 ではなく employee
employee.__class__.name: 破壊したクラス変数
↑ employee2
の __class__
の状態を変更すると、employee.__class__
も同様に変化します。同じものだからです。
print('Employee.name:', Employee.name)
Employee.name: 破壊したクラス変数
↑ Employee.name
も変化しています。同じものだから。
変数の参照優先度
もう一つ、Python のインスタンス変数とクラス変数の特徴的な動作を書きます。
class Fruit:
name: str = 'クラス変数'
fruit = Fruit()
print('fruit.name:', fruit.name)
fruit.name: クラス変数
↑クラス変数しか持たないクラスをインスタンス化し、インスタンスから変数にアクセスすると、クラス変数が取得できます。
print(fruit.name is fruit.__class__.name)
True
print(id(fruit.name), id(fruit.__class__.name))
140144425798800 140144425798800
↑ class を指定してもしなくても、両方は同じメモリを指しています。
fruit.name = 'インスタンス変数'
print(fruit.name is fruit.__class__.name)
False
↑ ただし、インスタンスの変数にセット(代入)を行うと、先程のクラス変数には代入されず、新たなインスタンス変数が生成されます。
print(fruit.name, fruit.__class__.name)
インスタンス変数 クラス変数
↑ インスタンス変数ができている場合、優先して表示されます。
del fruit.name
print(fruit.name, fruit.__class__.name)
クラス変数 クラス変数
↑さきほど作ったインスタンス変数を削除した場合、その前から存在していたクラス変数にアクセスできるようになります。
インスタンス.変数
にアクセスした際は、インスタンスに変数名が存在するかを調べ、あればそれを返す。
なければ、次はクラスに探しに行って、あれば返す。という動作になります。
print(fruit.name)
とした場合、name がインスタンス変数なのか、クラス変数なのか、コードを見ただけではわかりません。
その時のインスタンスの状態により、動的に変化します。
@dataclass
デコレータ
クラス変数とインスタンス変数に対し、同じ名前をつけると混乱につながりがちですが、 dataclasses
パッケージの @dataclass
デコレータをつけたクラスは別です。
@dataclass
class Employee:
name: str = ''
このようなクラスを定義した場合、
employee = Employee(name='鈴木')
を行うと、自動的に employee インスタンスのインスタンス変数 name に 鈴木 が入ります。
@dataclass
デコレータを使うと、定義したクラス変数を引数にとるコンストラクタが自動生成され、コンストラクタの中で代入処理が行われるようになります。
コードの見た目としても、クラス内にメンバー変数の一覧が定義されているように見えるため、他のオブジェクト指向言語のような見た目となります。
詳しくは、Python ドキュメントの dataclass をご覧ください。