In python __init__ dictionary arguments defaulting to previous class values [duplicate] - python

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument
I'm finding that dictionary arguments to the init() function of my class are defaulting to values I've previously set in previous instances. I really don't understand this behavior, and it doesn't seem to happen with lists or basic variables. Example code:
class TestClass:
def __init__(
self,
adir={},
alist=[],
avar=None
):
print("input adir: " + str(adir)) #for test2, shows test1.mydir
self.mydir = adir
self.mylist = alist
self.myvar = avar
test1 = TestClass()
test1.mydir['a'] = 'A'
test1.mylist = ['foo']
test1.myvar = 5
test2 = TestClass()
print(test2.mydir) #has same value of test1!
print(test2.mylist)
print(test2.myvar)
The output looks like this:
initializing test1
input adir: {}
initializing test2
input adir: {'a': 'A'}
{'a': 'A'}
[]
None
Why does the dictionary argument (adir) to test2 get set to test1.mydir? Especially, why is the behaviour different than other mutable types like list?
Thank you!

As DSM says, don't modify mutable default arguments. Do this instead:
class TestClass:
def __init__(self, adir=None, alist=None, avar=None):
if alist is None:
alist = []
if adir is None:
adir = {}

Related

Best way to declare method parameter type list

I know the good way to put a parameter type list is:
def test(my_list = None):
if my_list is None:
my_list = []
But I don't understand why I cannot simply do something like:
def test(my_list = list)
I tried it in the console mode and works
Update
In '“Least Astonishment” and the Mutable Default Argument' they explain why we don't should use
def test(my_list = [])
not why I cannot use
def test(my_list = list)
If you do l=list, you'll get this error:
def a(l=list):
print([x for x in l])
a()
# >> TypeError: 'type' object is not iterable
What you want to do is def a(l=list()), which is exactly the same as def a(l=[]).
Then we fall back to the problem here: "Least Astonishment" and the Mutable Default Argument
Update
If I understood you well, why not simply do:
def a(l=[]):
l = l or []
# ...rest of the method

How to tell a class method which variable should be processed

sorry for the noob question. Lets say i have a class which holds 3 lists and a method to combine one of the lists to a string. How can i tell the method which list it should take ? or should i move the method out of the class into a function ?
Here is what i mean:
class images():
def __init__(self, lista, listb, listc):
self.lista=lista
self.listb=listb
self.listc=listc
def makelist(self):
items = ""
for item in self.whateverListIWant:
items=items+item
return items
test=images([1,2,3],["b"],["c"])
print (test.makelist(WhichListIWant))
You can do this
class images():
def __init__(self, lista, listb, listc):
self.lista=lista
self.listb=listb
self.listc=listc
def makelist(self, param):
items = ""
for item in param:
items= items + str(item)
return items
test=images([1,2,3],["b"],["c"])
print (test.makelist(test.lista))
Be aware that "class method" (indicated by the decorator #classmethod) is not what you have in your example. Yours is a standard object method that acts on the object you create, as highlighted by your use of "self" as the first parameter.
The object method acts on the object, and can refer to the data contained within self. A "class method" would act only on a class, and only use parameters available to that class.
Use of a class method would be something like this:
class ClassyImages():
lista = []
listb = []
listc = []
#classmethod
def makelist(cls, which):
if which == 'a':
the_list = cls.lista
elif which == 'b':
the_list = cls.listb
elif which == 'c':
the_list = cls.listc
else:
raise ValueError("Invalid value " + str(which))
return the_list[:] # returns a soft copy of the list
And you would use it as follows:
ClassyImages.lista.extend([1,2,3])
ClassyImages.listb.add("b")
ClassyImages.listc.add("c")
print(ClassyImages.makelist("a"))
See the difference? In your example, you're using methods on the instance of an object, which you create. In this case, we're only using class-level variables, and never using an instance of an object.
However, the nice feature of the #classmethod decorator, is you still can use the class method on an object. So you can also do this:
my_classy_object_1 = ClassyImages()
my_classy_object_2 = ClassyImages()
print(my_classy_object_1.makelist("b")) # prints: ['b']
ClassyImages.listb.add("bb")
print(my_classy_object_1.makelist("b")) # prints: ['b', 'bb']
print(my_classy_object_2.makelist("b")) # prints: ['b', 'bb']
My answer glossed over what you may really be asking - how to tell it which list to look at. You can inject the list into the argument, as suggested by sjaymj64. Or you can pass a string or a number into the function, similar to how I did for the class-level makelist. It's largely up to you - provide whatever way seems most fitting and convenient for letting the logic of makelist choose which of its components to look at.

dynamically create methods in python [duplicate]

This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
Why to use __setattr__ in python?
(7 answers)
Closed 6 months ago.
given the following snippet:
def fun(ret):
return ret
class A:
def __init__(self):
for string in ['a', 'b']:
setattr(self, string, lambda: fun(string))
>>> a = A()
>>> a.a()
'b'
I want a method a() which returns 'a' and a method b() which returns 'b'. As long as I don't use a lambda expression but setting the attribute to a simple string, the association is correct.
I think my intention is clear? So where am I wrong?
In Python, a function will lookup non-local names in the scope where it was defined or in the global scope if the name still does not exist there. If the value associated to the name changed, so will the returned value. Note that this is not specific to lambda functions.
A way around this is to create a closure by writing a helper function.
def fun(ret):
return ret
class A:
def __init__(self):
def apply_fun(item):
return lambda: fun(item)
for string in ['a', 'b']:
setattr(self, string, apply_fun(string))
print(A().a()) # 'a'
Alternative solution
In that particular case, using __getattr__ might be more suited as it is intended to dynamically return attributes.
def fun(ret):
return ret
class A:
def __getattr__(self, item):
if item in ['a', 'b']:
return lambda: fun(item)
print(A().a()) # 'a'

Python creating new object of class contains values of older object [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 7 years ago.
I have got a class with a list-variable and a function to append items to this list. I cannot append items directly, because I have to validate them:
class Foo:
def __init__(self, elements=[]):
self.elements = elements
def append(self, element):
self.elements.append(element)
If I instantiate an object of this class, add items and then create another new object, this object contains the items of the first object.
foo = Foo()
print foo.elements # []
foo.append(element=4)
print foo.elements # [4]
foo.append(element=7)
print foo.elements # [4, 7]
bar = Foo()
print bar.elements # [4, 7]
Can someone explain my, why this happens?
A possible solution for me could be this, but I don't like it...
class Foo:
def __init__(self, elements=None):
if elements is None:
self.elements = []
else:
self.elements = elements
def append(self, element):
self.elements.append(element)
Thanks for all answers!
You've got your answer yourself already. Initializing with a mutable object ist never a good idea. It will be initialized once the class gets defined and reused for every instance.

Python class constructor with default arguments [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument
Can anyone explain the following strange behaviour?
I have the following class:
class Zoo:
def __init__(self,alist=[]):
self.animals = alist
def __len__(self):
return len(self.animals)
def add(self,a):
self.animals.append(a)
and when I do the following,
In [38]: z=Zoo()
In [39]: z.add(2)
In [40]: z.add(23)
In [41]: len(z)
Out[41]: 2
In [42]: z2=Zoo()
In [43]: len(z2)
Out[43]: 2
Why is z2.animals not an empty list?
Thanks, Matthias
You are mutating the default argument in your constructor (you are just copying a reference to the same list into each of your instances). You can fix this as follows:
class Zoo:
def __init__(self,alist=None):
self.animals = alist or []
def __len__(self):
return len(self.animals)
def add(self,a):
self.animals.append(a)
The default argument list is the same object for all instances, hence assigning it to a member just assigns a reference to the same object.
here's an example:
>>> class foo():
... def __init__(self, x = []):
... print id(x)
...
>>> x = foo()
140284337344168
>>> y = foo()
140284337344168
>>> z = foo()
140284337344168
you can see that x is the same object in all instances.

Categories

Resources