Bizarre Python dictionary behavior using objects as keys [duplicate] - python

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 7 years ago.
I'm defining a dictionary within a class (let's call it Thingy). Sometimes I need to use other Thingy instances as keys in the dictionary for thing1. However, if I try to add thing2 as a key with value 1 (say) to the dictionary of thing1, then thing2:1 also appears in the dict of thing2!
Please confirm if you can reproduce (python 2.7.6):
# class definition
class Thingy:
def __init__(self, d={}):
self.dict = d
# Test code
thing1 = Thingy()
thing2 = Thingy()
thing1.dict[thing2] = 1
print('Thing1.dict is {}'.format(thing1.dict))
print('Thing2.dict is {}'.format(thing2.dict))
gives me
Thing1.dict is {<__main__.Thingy instance at 0x7f79fdeed998>: 1}
Thing2.dict is {<__main__.Thingy instance at 0x7f79fdeed998>: 1}
even though I never changed Thing2.dict!

Never use mutable objects as default parameter:
class Thingy:
def __init__(self, d={}):

Related

How to correctly initialise a subclass of dict with extra arguments in its constructor? [duplicate]

This question already has answers here:
Subclassing dict: should dict.__init__() be called?
(5 answers)
Closed 3 years ago.
Using a similar approach to a previous question about subclassing a float class yields no keys in the dict. For example:
class Foo(dict):
def __new__(cls, value, extra):
return super().__new__(cls, value)
def __init__(self, value, extra):
dict.__init__(value)
self.extra = extra
Running Foo({'a':1}, 1).keys() returns empty dict keys dict_keys([]).
How to correctly subclass a dict with extra arguments in Python?
Change the line:
dict.__init__(value)
To:
dict.__init__(self, value)
Also consider composition instead of inheritance, because you're breaking away from the API of dict here (see LSP).

python constructor default argument list [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 9 years ago.
Is the default argument list the same object for all instances?
class X():
def __init__(self,a=[]):
self.member=a
print id(a)
k=X([1,2,3])
g=X([1,2,3])
t=X()
h=X()
The output surprises me:
140072782781832
140072782901976
140072782816536
140072782816536
As you can see, the id is different when a equals [1,2,3] but stays the same when a is empty. However, if I delete self.member, now the code looks like this:
class X():
def __init__(self,a=[]):
print id(a)
k=X([1,2,3])
g=X([1,2,3])
t=X()
h=X()
The output becomes like this:
140033294171528
140033294171528
140033294206232
140033294206232
The id stay the same when a equals [1,2,3].
I am totally confused... Anyone can explain that?
Yes, which is why you are supposed to do
class X():
def __init__(self, a=None):
self.a = [] if a is None else a
Edit:
I would point out that
class X():
def __init__(self,a=[]):
print(id(a))
k = X([1,2,3])
g = X([1,2,4]) # <- different list values
t = X()
h = X()
also gives
42678232
42678232
42680152
42680152
so I would expect the answer is something like "if you create a list, delete it, and create another list, the odds are good it will reuse the same allocated memory location".

Unexpected python behavior related to default argument for mutable object [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 8 years ago.
#! /usr/bin/python
class my_class:
# 1. __init__
def __init__(self):
self.my_set = set()
# 2. __init__
#def __init__(self, arg_set = set()):
# self.my_set = arg_set
c1 = my_class()
c1.my_set.add('a')
print c1.my_set
c2 = my_class()
c2.my_set.add('b')
print c1.my_set
my_class has 2 ways of defining __init__:
If I use 1st way, output is as expected:
set(['a'])
set(['a'])
If I use 2nd way, output is unexpected:
set(['a'])
set(['a', 'b'])
What is wrong with 2nd way? How can modification of C2 (a separate object), result in modification of c1?
Edit: Updated the question title to reflect specific area of concern
From http://docs.python.org/2/reference/compound_stmts.html#function-definitions
Default parameter values are evaluated when the function definition is
executed. This means that the expression is evaluated once, when the
function is defined, and that the same “pre-computed” value is used
for each call. This is especially important to understand when a
default parameter is a mutable object, such as a list or a dictionary:
if the function modifies the object (e.g. by appending an item to a
list), the default value is in effect modified.
Thats the reason why your second method appends values everytime.
Moreover, modify the second __init__ like this
def __init__(self, arg_set = set()):
print id(arg_set)
self.my_set = arg_set
Now, when you run the code, you ll always get the same address (id function in CPython returns the address of the object in memory). So, default arguments are not created everytime the function is invoked, but when evaluated the first time.
thefourtheye is completely right on the cause of this behavior. If you want to use the second version of your constructor, it should be like this:
def __init__(self, arg_set=None):
if arg_set is None:
self.my_set = set()
else:
self.my_set = arg_set

default arguments in Python [duplicate]

This question already has answers here:
"Least Astonishment" and the Mutable Default Argument
(33 answers)
Closed 9 years ago.
I am confusing about the default arguments in Python. Here are my code:
#!/usr/bin/env python
import sys
class Node(object):
def __init__(self, ID=str(), items=[]):
self.ID = ID
self.items = items
if __name__ == '__main__':
a = Node('1')
b = Node('2')
b.items.append('sth.')
c = Node('3')
print a.items
print b.items
print c.items
The output is:
['sth.']
['sth.']
['sth.']
I just change the b instance. Why all instances are changed?
This is a case of reference. Since the list items is mutable (can be directly changed with an internal method), any change you make to it in any function is reflected in all references to it.
For example, if you have the following code:
def f(x):
x.append(5)
a = [1, 2, 3]
f(a)
# a is now [1, 2, 3, 4], even in the global scope
This occurs because a is a mutable list, and so it is passed by reference.
So when you use items = [], you are creating a blank list when the program is started, but not every time you create a new instance. Instead, each instance refers to the same list, created when the class was "declared". So, since each instance refers to the same list, they are all changed.
To fix this, change your constructor to:
def __init__(self, ID=str(), items=None): # you can also use '' instead of str()
if not items: items = []
# everything else
A few good links to explain this better/in a different way:
Immutable Objects and References
Mutable Default Arguments
Default List Arguments
There are a ton of other questions like this out there, just search [python] None as default argument.

How to assign cache to a method in an OOP fashion? [duplicate]

This question already has answers here:
What is memoization and how can I use it in Python?
(14 answers)
Closed 9 years ago.
Suppose that I have class A and this class has a method called function. Can I assign a cache as a property to this method? In the sense that I could call it like a property?
class A:
def __init__(self,value):
self.value=value
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
cache=[]
cache.append([a,a+1])
return a+1;
a=A(12)
print a.function(12)
print a.function.cache
Which gives me the error:
AttributeError: 'function' object has no attribute 'cache'
I know it is possible to assign a cache to the main class but I am looking for a possible way of assigning it as a property to the method object.
class A:
def __init__(self,value):
self.value=value
self.cache = {}
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
# Add a default value of empty string to avoid key errors,
# check if we already have the value cached
if self.cache.get(a,''):
return self.cache[a]
else:
result = a + 1
self.cache[a] = result
return result
As far as I know there is no way of having the cache as a property of the method. Python doesn't have such a feature. But I think perhaps this solution will satisfy your needs.
EDIT
Upon further research, there is indeed a way to do this in Python 3
class A:
def __init__(self,value):
self.value=value
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
# Add a default value of empty string to avoid key errors,
# check if we already have the value cached
if self.function.cache.get(a,''):
return self.function.cache[a]
else:
result = a + 1
self.function.cache[a] = result
return result
function.cache = {}
a=A(12)
print(a.function(12))
print(a.function.cache)
This is because in Python 3 instance methods are just functions. BTW in Python 2 it is indeed possible to add attributes to functions, but not to instance methods. If you need to use Python 2 then there is a solution to your problem involving decorators that you should look into.

Categories