Pythonのクラスではインスタンス変数を定義できますが、定義する場所について解説してみます。
本記事の要約
インスタンス変数は
- 基本的に初期化メソッドで定義するのが良い。
- 通常のメソッド内で初めて定義する場合は実行順序に注意。
いただいた質問の内容
本記事は、Djangoのコースでいただいたご質問をもとに解説するものです。質問内容を要約すると次のような感じです。
get_querysetメソッドで定義したself.totalなどは、同じクラス内にあるget_context_dataメソッドなどでもselfの値は保持されるという解釈でいいでしょうか?
...
class CartListView(ListView):
model = Item
template_name = 'pages/cart.html'
def get_queryset(self):
cart = self.request.session.get('cart', None)
if cart is None or len(cart) == 0:
return redirect('/')
self.queryset = []
self.total = 0 # 🔴 ここでself.totalを定義
for item_pk, quantity in cart['items'].items():
obj = Item.objects.get(pk=item_pk)
obj.quantity = quantity
obj.subtotal = int(obj.price * quantity)
self.queryset.append(obj)
self.total += obj.subtotal
self.tax_included_total = int(self.total * (settings.TAX_RATE + 1))
cart['total'] = self.total
cart['tax_included_total'] = self.tax_included_total
self.request.session['cart'] = cart
return super().get_queryset()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["total"] = self.total # 🔴 ここで使用
context["tax_included_total"] = self.tax_included_total
except Exception:
pass
return context
このコードをシンプルな形に直して解説してみます。
インスタンス変数は初期化メソッドで定義するのが一番いい
まず前提として、できるならインスタンス変数は初期化メソッド(__init__
メソッド)で設定した方がいいと思います。
例のコードにおいてはそうではなく、任意のメソッドの中で新しく定義しています。
なぜかというと、
- 継承している親クラスのメソッドが呼ばれる実行順序を知っていた
ので、わざわざ初期化メソッドを呼び出さなくてもいいかな、という判断でこのようにしました。
本記事はこのように初期化メソッドを使わずに、インスタンス変数を定義、使用する場合の挙動についての解説になります。
💡 継承していても初期化メソッドを呼び出してインスタンス変数を定義することはできます。初期化メソッドの中でまず親クラスの初期化メソッドを実行してあげれば大丈夫です。
簡単なコードで再現
では初期化メソッドを使わないでインスタンス変数を定義する場合の挙動を確認していきます。
まずは例のコードではなく簡単なコードに書き換えてみます。
class Dog():
def born(self):
print('bornメソッドが呼ばれた。')
self.dog = 'ワンッワンッ'
def bark(self):
print('barkメソッドが呼ばれた。')
print(self.dog)
self.dog
はbornメソッドで定義され、
barkメソッド内で呼び出しています。
Dogクラスをインスタンス化して実行。下記のようにうまく「ワンッワンッ」と表示されています。
my_dog = Dog()
my_dog.born()
my_dog.bark()
# 実行結果 ---
# bornメソッドが呼ばれた。
# barkメソッドが呼ばれた。
# ワンッワンッ
うまくいかないケース
先ほどうまくいったのは実行する順番が正しかったからです。
次のように、my_dog.born()
を実行する前に my_dog.bark()
を実行すると、self.dog
が定義されていないため、失敗することに。。。
my_dog = Dog()
# my_dog.born()
my_dog.bark()
# 実行結果 ---
# barkメソッドが呼ばれた。
# Traceback (most recent call last):
# ...
# AttributeError: 'Dog' object has no attribute 'dog'
このように実行順序が違えばうまくいかないことが起こり得るので、
基本的な考え方としては(自分でクラスを定義するときは特に)まず初期化メソッドでインスタンス変数を定義するのがいいと思います。
継承したクラスの場合でも、実行順序がよくわからないとかであれば、初期化メソッド自体をオーバーライドして定義しておくのが無難です。
以上、なぜインスタンス変数をインスタンスメソッドの中で新しく定義したのかについての解説でした。