関数を再帰呼び出しした時の、関数内のクラスのidが同じことについて


python 3.6.4で以下のコードを作成しました。

[code]

    class Number(object):
        list = []
        def extend(self,arg):
            self.list.extend(arg.list)
    
    def multi(arg):
        cls = Number()
        tmp = []
    
        file = open('data.txt', 'r')
        for line in file:
            tmp.append(int(line))
        file.close
        print(tmp)
    
        file = open('data.txt', 'w')
        file.write('')
        file.close
    
        for item in tmp:
            cls.extend(multi(item))
    
        cls.list.append(arg)
        print ('arg:',arg,' cls.list:',cls.list," id of cls:",id(cls))
        return cls
    
    def main():
        ans = multi(5)
        print('ans.list:',ans.list)
    
    if __name__ == '__main__':
        main()
    
[/code]

そして、data.txtには以下のデータを保存しておきます。

[code]

    5
    4
    3
    2
    1
    
[/code]

このコードへ期待する動作は、  
ans.listが[5,4,3,2,1]となっていることでした。  
しかし、実際は再帰呼び出しされたclsのidがすべて同じため、  
ans.listは期待とは違った値となっています。  
なぜclsのidは再帰呼び出しされても同じidのままなのでしょうか。

例えば以下のコードでは、再帰呼び出しされたクラスのidはすべて異なっており、  
期待通りの動作をします。

[code]

    class Number(object):
        num = 5
    
    def multi(arg):
        num_cls = Number()
        if arg > 1:
            num_cls.num = arg * multi(arg - 1)
        else:
            num_cls.num = 1
        print ('arg:',arg,' num:',num_cls.num," id of num_cls:",id(num_cls))
        return num_cls.num
    
    def main():
        ans = multi(5)
        print (ans)
    
    if __name__ == '__main__':
        main()
    
[/code]

pythonのどの仕様がそうさせているのか、お伺いできればと思います。




Pythonのidはその変数のライフタイムの間でしかユニークであることを保障されず、今回の場合は初めに呼ばれるmulti(5)以外のmulti(5),
multi(4), multi(3),
...がそれぞれ再帰的な呼び出され方ではなく別々のスコープの範囲内となるため、idが再利用されているのではないかと思います。

<https://docs.python.org/2/library/functions.html#id>

このコードがおかしな挙動を見せているのは、fileがcloseされていないのと、list変数がNumberクラスの静的変数になってしまっている点によるのではないでしょうか。  
該当部分を書き換えたものを以下にあげます。

ただし、この書き方でもファイルから読んだ値以外で、初めの引数であるargの5がリストに追加されるので、ans.listの最終的な出力は[5,4,3,2,1,5]となります。

[code]

    class Number(object):
        def __init__(self):
            self.list = []
    
        def extend(self, arg):
            self.list.extend(arg.list)
    
    
    def multi(arg):
        cls = Number()
        tmp = []
    
        file = open('data.txt', 'r')
        for line in file:
            tmp.append(int(line))
        file.close()
        print(tmp)
    
        file = open('data.txt', 'w')
        file.write('')
        file.close()
    
        file = open('data.txt', 'r')
        for line in file:
            print(line)
        file.close()
    
        for item in tmp:
            cls.extend(multi(item))
    
        cls.list.append(arg)
        print('arg:', arg, ' cls.list:', cls.list,
              " id of cls:", id(cls))
        return cls
    
    
    def main():
        ans = multi(5)
        print('ans.list:', ans.list)
    
    
    if __name__ == '__main__':
        main()
    
[/code]




Pythonのidはその変数のライフタイムの間でしかユニークであることを保障されず、今回の場合は初めに呼ばれるmulti(5)以外のmulti(5),
multi(4), multi(3),
...がそれぞれ再帰的な呼び出され方ではなく別々のスコープの範囲内となるため、idが再利用されているのではないかと思います。

<https://docs.python.org/2/library/functions.html#id>

このコードがおかしな挙動を見せているのは、fileがcloseされていないのと、list変数がNumberクラスの静的変数になってしまっている点によるのではないでしょうか。  
該当部分を書き換えたものを以下にあげます。

ただし、この書き方でもファイルから読んだ値以外で、初めの引数であるargの5がリストに追加されるので、ans.listの最終的な出力は[5,4,3,2,1,5]となります。

[code]

    class Number(object):
        def __init__(self):
            self.list = []
    
        def extend(self, arg):
            self.list.extend(arg.list)
    
    
    def multi(arg):
        cls = Number()
        tmp = []
    
        file = open('data.txt', 'r')
        for line in file:
            tmp.append(int(line))
        file.close()
        print(tmp)
    
        file = open('data.txt', 'w')
        file.write('')
        file.close()
    
        file = open('data.txt', 'r')
        for line in file:
            print(line)
        file.close()
    
        for item in tmp:
            cls.extend(multi(item))
    
        cls.list.append(arg)
        print('arg:', arg, ' cls.list:', cls.list,
              " id of cls:", id(cls))
        return cls
    
    
    def main():
        ans = multi(5)
        print('ans.list:', ans.list)
    
    
    if __name__ == '__main__':
        main()
    
[/code]

