Python Class And Instance Attribute Confusion
Solution 1:
You can see what's happening here:
>>>classValidationResult():...def__init__(self, passed=True, messages=[], stop=False):... self.passed = passed... self.messages = messages...printid(self.messages)... self.stop = stop...>>>foo = ValidationResult()
4564756528
>>>bar = ValidationResult()
4564756528
The default argument is always the same object in memory. One quick workaround for lists is to create a copy of the list for each instantiation:
>>>classValidationResult():...def__init__(self, passed=True, messages=[], stop=False):... self.passed = passed... self.messages = messages[:]...printid(self.messages)... self.stop = stop...>>>foo = ValidationResult()
4564756312
>>>bar = ValidationResult()
4564757032
Solution 2:
This is a little quirk in python functions and can be seen just as well without the class:
deffoo(bar=[]):
bar.append('boo')
print bar
foo()
foo()
The "problem" is that the default argument (bar) is created when the module is loaded. The same object continues to be passed as the default argument of foo if you don't explicitly pass something else.
The canonical way to use default arguments that are mutable is use a sentinel value (typically None
) which can be tested using the is
operator to indicate that the user didn't pass anything (unless mutating a default argument is desired in your function of course). e.g.:
deffoo(bar=None):
if(bar isNone):
bar=[]
bar.append('boo')
print bar
Here's a link to the documentation -- pay close attention to the "Important Warning" section.
Solution 3:
Nope, nothing to do with class attributes vs instance attributes.
The trouble is that all assignment in Python is simply binding references to objects, so whatever value was used as the messages
parameter to __init__
ends up being the value stored as the messages
attribute; it isn't copied. This is a problem when that value is also used elsewhere and can be mutated, since then changes to the object referenced by the messages
attribute end up affecting other references to the same object.
Since you have a default value for the messages
parameter to __init__
, every time you call it without providing a value you get the same object filled in. Default values are default values, not recipes for creating new values, so every time the default value is used you get the same list.
People refer to this as the "mutable default argument" problem, but I think it's more general than that. It could easily bite you if you explicitly pass messages
as well. The trouble really is that you're mutating an input parameter (by storing a reference to it in an instance attribute, which you then mutate), which is never a good idea unless mutating the object is the purpose of the function. That's normally not true of a constructor, so you should copy the list if you plan on mutating it.
Solution 4:
This self.messages
is an alias of the default parameter. The default parameter is constructed with the function and not with the caller.
classValidationResult():
def__init__(self, passed=True, messages=None, stop=False):
self.passed = passed
self.messages = messages if messages isnotNoneelse []
self.stop = stop
>>> foo = ValidationResult()
>>> bar = ValidationResult()
>>> foo.messages.append("Foos message")
>>> print foo.messages
['Foos message']
>>> print bar.messages
[]
Post a Comment for "Python Class And Instance Attribute Confusion"