The Python list should be empty when initializing an instance of the class, but it is not. What for?

I would like to instantiate a class containing a list that is empty by default; instead of later setting this list to the final complete list, I would like to add elements to it sequentially. Here is a sample code example to illustrate this:

#!/usr/bin/python class test: def __init__(self, lst=[], intg=0): self.lista = lst self.integer = intg name_dict = {} counter = 0 for name in ('Anne', 'Leo', 'Suzy'): counter += 1 name_dict[name] = test() name_dict[name].integer += 1 name_dict[name].lista.append(counter) print name, name_dict[name].integer, name_dict[name].lista 

When I ran the above program, I expected to get

Anne 1 [1]
Leo 1 [2]
Susie 1 [3]

since I assumed that lista always initialized with an empty list.

Instead, I got the following:

Anne 1 [1]
Leo 1 [1, 2]
Susie 1 [1, 2, 3]

If I replaced self.lista = lst with self.lista = [] , it works just as if adding the string name_dict[name].lista = [] to the for loop.

Why are the contents of previous lists of objects preserved, but their integer values ​​are not? I'm new to Python, so it would be great if someone could tell me where my thoughts / assumptions got lost.

Thanks so much for your answers.

+5
source share
4 answers

It is a bad idea to use a mutable object as the default, as you are here:

 def __init__(self, lst=[], intg=0): # ... 

Change it like this:

 def __init__(self, lst=None, intg=0): if lst is None: lst = [] # ... 

The reason your version does not work is because an empty list is created only once when the function is defined, and not every time the function is called.

In some Python implementations, you can see the default values ​​for the function by checking the value of func_defaults :

 print test.__init__.func_defaults name_dict[name] = test() # ... 

Output:

  ([],)
 Anne 1 [1]
 ([1],)
 Leo 1 [1, 2]
 ([12],)
 Suzy 1 [1, 2, 3] 
+12
source

The problem is this line:

 def __init__(self, lst=[], intg=0): 

You should not use the list as the default argument. The first time __init__ is called without lst , the specified Python interpreter will define an empty list [] . Subsequent function calls will work in the same list if lst not specified without declaring a new list. This causes strange problems.

Instead, you should use the default value of None and add a check at the beginning of the function:

 def __init__(self, lst=None, intg=0): if lst is None: lst = [] 

See this post for more details. Quote:

The default arguments are evaluated at the time the function is defined, so they are constant between calls. This has some interesting (and confusing) side effects. Example:

 >>> def foo(d=[]): ... d.append('a') ... return d 

If you have not tried this before, you probably expect foo to always return ['a'] : it should start with an empty list, add 'a' to it and return it. Here is what he actually does:

 >>> foo() ['a'] >>> foo() ['a', 'a'] >>> foo() ['a', 'a', 'a'] 

This is because the default value for d is allocated when the function and not when it is called. every time the function calls, the value is still hanging from the last call. It gets even weirder if you throw the threads into the mix. If two different threads perform functions at the same time, and one of them changes the default argument, they will both see the change.

Of course, all this is true only if the default argument value is a mutable type. If we change foo to is defined as

 >>> def foo2(d=0): ... d += 1 ... return d 

then it will always return 1. (The difference here is that in foo2 , the variable d reassigned, while in foo its value has been changed.)

+4
source

The problem is the default constructor argument. You have to read this question to find the answer to your question: "Least surprise" in Python: argument argument resolved by argument

+1
source

Python evaluates default arguments once when defining a function:

 def init_a(): """Initialize a.""" print("init_a") return 1 def test(a_parameter=init_a()): """A test function.""" print("Execute test") print("whatever") test() test() 

gives

 init_a whatever Execute test Execute test 

So, your list is determined once. You are using the same list as before. That is why you should use a template

 def a_function(a_parameter=None): # Create 'None' once if a_parameter is None: a_parameter = [] # Create a new list - each time 
0
source

Source: https://habr.com/ru/post/1333421/


All Articles