2023-05-03 11:06:20 +02:00
== Why is this an issue?
2021-04-28 16:49:39 +02:00
In Python function parameters can have default values. These default values are expressions which are executed when the function is defined, i.e. only once. The same default value will be used every time the function is called, thus modifying it will have an effect on every subsequent call. This can create some very confusing bugs.
It is also a bad idea to store mutable default value in another object (ex: as an attribute). Multiple instances will then share the same value and modifying one objet will modify all of them.
This rule raises an issue when:
* a default value is either modified in the function or assigned to anything else than a variable and it has one of the following types:
** builtins: set, dict, list.
** https://docs.python.org/3/library/collections.html[collections] module: deque, UserList, ChainMap, Counter, OrderedDict, defaultdict, UserDict.
* or when an attribute of a default value is assigned.
2021-04-28 18:08:03 +02:00
2023-05-03 11:06:20 +02:00
=== Noncompliant code example
2021-04-28 16:49:39 +02:00
In the following example, the parameter "param" has ``++list()++`` as a default value. This list is created only once and then reused in every call. Thus when it appends ``++'a'++`` to this list, the next call will have ``++['a']++`` as a default value.
2022-02-04 17:28:24 +01:00
[source,python]
2021-04-28 16:49:39 +02:00
----
def myfunction(param=list()): # Noncompliant.
param.append('a') # modification of the default value.
return param
print(myfunction()) # returns ['a']
print(myfunction()) # returns ['a', 'a']
print(myfunction()) # returns ['a', 'a', 'a']
----
In the following example the same list is used for multiple instances of MyClass.param.
2022-02-04 17:28:24 +01:00
[source,python]
2021-04-28 16:49:39 +02:00
----
class MyClass:
def __init__(self, param=list()): # Noncompliant.
self.param = param # The same list is used for every instance of MyClass
def process(self, value):
self.param.append(value) # modifying the same list
a1, a2 = (MyClass(), MyClass())
a1.process("value")
print(a1.param) # ['value']
print(a2.param) # ['value']
----
2021-04-28 18:08:03 +02:00
2023-05-03 11:06:20 +02:00
=== Compliant solution
2021-04-28 16:49:39 +02:00
2022-02-04 17:28:24 +01:00
[source,python]
2021-04-28 16:49:39 +02:00
----
def myfunction(param=None):
if param is None:
param = list()
param.append('a')
return param
print(myfunction()) # returns ['a']
print(myfunction()) # returns ['a']
print(myfunction()) # returns ['a']
----
2022-02-04 17:28:24 +01:00
[source,python]
2021-04-28 16:49:39 +02:00
----
class MyClass:
def __init__(self, param=None):
if param is None:
self.param = list()
else:
self.param = param
def process(self, value):
self.param.append(value)
a1, a2 = (MyClass(), MyClass())
a1.process("value")
print(a1.param) # ['value']
print(a2.param) # []
----
2021-04-28 18:08:03 +02:00
2023-05-03 11:06:20 +02:00
=== Exceptions
2021-04-28 16:49:39 +02:00
In some very rare cases modifying a default value is ok. For example, default values can be used as a cache.
No issue will be raised when the parameter's name contains "cache" or "memo" (as in memoization).
2021-04-28 18:08:03 +02:00
2023-05-03 11:06:20 +02:00
== Resources
2021-04-28 16:49:39 +02:00
* https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments[The Hitchhiker's Guide to Python - Common Gotchas]
* https://docs.python.org/3/reference/compound_stmts.html#function-definitions[Python documentation - Function definitions]
2021-04-28 18:08:03 +02:00
2021-09-20 15:38:42 +02:00
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
2023-05-25 14:18:12 +02:00
=== Message
Change this default value to "None" and initialize this parameter inside the function/method.
=== Highlighting
* Primary: the default value
* Secondaries:
** code modifying the parameter:
message: 'The parameter is modified.'
** code assigning the parameter to something else
message: 'The parameter is stored in another object.'
2021-09-20 15:38:42 +02:00
endif::env-github,rspecator-view[]