Pythonのインスタンス変数の定義場所と注意点

Published
2022-07-01
Author
Takuma Fujimoto 🦊
Tags
Share

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'


基本的な考え方としては(自分でクラスを定義するときは特に)まず初期化メソッドでインスタンス変数を定義するのがいいと思います!


💡
質問内容においてのDjangoのコースのコードについては、事前に実行順序を知っていたのであのような書き方をしていました。 継承したクラスで実行順序がよくわからないとかであれば、初期化メソッド自体をオーバーライドして定義しておくのもいいと思います。


@takuxone

記事を読んでいただきありがとうございます!ツイッターではプログラミング以外についてや、たまにクーポン情報もツイートしたり、しなかったり。。。ツイッターでもお待ちしてますー。

🎓✍️ コース一覧

プログラミング関係のビデオコースを提供しています。気になるものがあるか、一度チェックしてみてください。