7
4

More than 3 years have passed since last update.

Pythonでのstatic変数/static関数について考える

Last updated at Posted at 2021-07-27

1. はじめに

 C++でコードを書いていく中で、「static変数」という変数宣言が出てきた。Pythonではデコレータとしてstaticmethodは標準搭載されているものの、「static変数」については搭載されていないようである。
 今回はC++でstatic変数を用いて実装したコードをPythonで書き換えてみて、Pythonではいかにして「static変数」を実装すべきかを考えるとともに、なぜPythonでは「static変数」の概念がなく、staticmethodだけが残されたかの理由について考えてみる。

2. static変数とは

こちらのstatic変数についての解説を下記に引用した。

プログラム中で使用する変数のうち、プログラムの開始から終了まで値が保持され続けるもの。特に、通常は生成と破棄を繰り返す関数やメソッド内部のローカル変数について、同じ内容を維持し続けるよう指定したもの。

上記を見てインスタンス変数では定義するのは難しいと感じた。というのもクラスをnewするたびに、そのインスタンス変数として定義していては、それぞれインスタンス間での変数の保持に不向きである。

3. C++のスクリプト

 今回はstatic変数について考える上で、Carクラスがインスタンス化されるたびに、車の台数をカウントするスクリプトを例として作成した。このスクリプト内では、Carクラス内にnewしたインスタンス数を数える変数( = car_count )をstatic変数、アクセス指定子をprivateに指定し、car_countを表示するためのstatic関数としてshowCounter関数を実装し、アクセス指定子はpublicとした。
 出力結果からも分かるようにnewするたびに車の台数が1台増え、deleteするたびに1台減っており、car_countがstatic変数として機能していることが確認できる。

main.cpp
#include "car.cpp"
#include <iostream>

using namespace std;

int main() {
    Car *car1, *car2, *car3;
    car1 = new Car();    
    Car::showCounter();
    car2 = new Car();
    Car::showCounter();
    delete car1;
    Car::showCounter();
    car3 = new Car();
    Car::showCounter();
    delete car2;
    Car::showCounter();
    delete car3;
    Car::showCounter();
    return 0;
}
car.h (ヘッダーファイル) 
#ifndef _CAR_H_
#define _CAR_H_

class Car {
public:
    //  Constructor
    Car();
    //  Destructor
    ~Car();
    static void showCounter();      // static関数
private:
    static int car_count;           // static変数
};
#endif
car.cpp
#include "car.h"
#include <iostream>

using namespace std;

int Car::car_count = 0;

//  Constructor
Car::Car() {
    car_count++;
}
//  Destructor
Car::~Car() {
    car_count--;
}
//  車の台数の出力
void Car::showCounter()
{
    cout << "車の台数は現在 " << car_count << " 台です" << endl;
}
出力
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 0 台です

4. Pythonのスクリプト

4-1. 方針

 さて、上記C++で実装したスクリプトをPythonでも同出力となるように作成した。インスタンス数をカウントするstatic変数を実装していくわけだが、2. static変数とはにも書いたとおり、以下の方針でスクリプトを作成した。

  • class外で仮想的にstatic変数 ( car_count ) を実装
  • class内でclass変数を仮想的にstatic変数として実装

4-2. 実装

4-2-1. class外で仮想的にstatic変数を実装

 class外で、countNum関数内でcountNum.counterをstatic変数に見立てて実装した。出力結果も同一であるが、countNum関数はクラス外で定義しているためアクセス指定子などは指定できず、どこからでもcountNum.counterの値にアクセスして変更できてしまう点が問題である。また、Carクラス内にcountNum関数が実装されていないことで、何のカウンターなのかを瞬時に判断することが難しく、可読性が下がるとともに、スクリプトもやや冗長になりがち

example1.py
def transStaticVar(var_name, value):
    def decorate(function):
        setattr(function, var_name, value)
        return function
    return decorate

@transStaticVar("counter", 0)
def countNum(counter_flag):
    """
    @param counter_flag True-> + , False -> -
    """
    if counter_flag:
        countNum.counter += 1
    else:
        countNum.counter -= 1

def showCounter():
    print("車の台数は現在 {} 台です".format(countNum.counter))


class Car(object):

    def __init__(self, counter_flag=True):
        """
        Constructor
        """
        countNum(counter_flag)

    def __del__(self, counter_flag=False):
        """
        Destructor
        """ 
        countNum(counter_flag)

if __name__ == "__main__":
    car1 = Car()
    showCounter()
    car2 = Car()
    showCounter()
    del car1
    showCounter()
    car3 = Car()
    showCounter()
    del car2
    showCounter()
    del car3
    showCounter()
出力
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 0 台です

4-2-2. class内でclass変数を仮想的にstatic変数として実装

 class変数として__counterをstatic変数に見立てて実装した。出力結果も同一である。4-2-1. class外で仮想的にstatic変数を実装で問題点として上げた外部からの書き換え問題も、class変数をprivate化することにより、外部から値の変更ができないように制御できた。また、C++同様に現在のcounter数を表示させるshowCounter関数をstatic関数としてクラス内に組み込むことにより、容易に何のカウンターを表しているかが分かるとともに可読性があがる

example2.py
class Car(object):
    __counter = 0

    @staticmethod
    def showCounter():
        print("車の台数は現在 {} 台です".format(Car.__counter))

    def __init__(self):
        """
        Constructor
        """
        Car.__counter += 1

    def __del__(self):
        """
        Destructor
        """
        Car.__counter -= 1

if __name__ == "__main__":
    car1 = Car()
    Car.showCounter()
    car2 = Car()
    Car.showCounter()
    del car1
    Car.showCounter()
    car3 = Car()
    Car.showCounter()
    del car2
    Car.showCounter()
    del car3
    Car.showCounter()
出力
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 2 台です
車の台数は現在 1 台です
車の台数は現在 0 台です

5. まとめ

 結局のところ、クラス変数 ≒ static変数なのでPythonではわざわざ「static変数」として宣言しなくなったということかもしれない。ただし、scopeを問わずにアクセスできると、変数値の書き換えなどを予期せぬ位置で容易にできてしまいバグの原因になり得るので、private変数化して、変数アクセスにgetterstatic関数として準備しておくのがベストプラクティスなのかもしれない。
 上記実装で、static変数の実装ができているという仮定のもとであれば、Pythonに「static変数」の概念はないが、static関数は標準搭載されていることの意義は納得できる。
※ご意見等ありましたらコメントいただけたら幸いです!

6. 参考サイト

7
4
7

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
7
4