In answer to the question of good uses for mutable default argument values, I offer the following example:
A mutable default can be useful for programing easy to use, importable commands of your own creation. The mutable default method amount to having private, static variables in a function that you can initialization on the first call (very much like a class) but without having to resort to globals, without having to use a wrapper, and without having to instantize a class object that was imported. It is in its own way elegant, as I hope you will agree.
Consider these two examples:
def dittle(cache = []):
from time import sleep # Not needed except as an example.
# dittle's internal cache list has this format: cache[string, counter]
# Any argument passed to dittle() that violates this format is invalid.
# (The string is pure storage, but the counter is used by dittle.)
# -- Error Trap --
if type(cache) != list or cache !=[] and (len(cache) == 2 and type(cache[1]) != int):
print(" User called dittle("+repr(cache)+").\n >> Warning: dittle() takes no arguments, so this call is ignored.\n")
return
# -- Initialize Function. (Executes on first call only.) --
if not cache:
print("\n cache =",cache)
print(" Initializing private mutable static cache. Runs only on First Call!")
cache.append("Hello World!")
cache.append(0)
print(" cache =",cache,end="\n\n")
# -- Normal Operation --
cache[1]+=1 # Static cycle count.
outstr = " dittle() called "+str(cache[1])+" times."
if cache[1] == 1:outstr=outstr.replace("s.",".")
print(outstr)
print(" Internal cache held string = '"+cache[0]+"'")
print()
if cache[1] == 3:
print(" Let's rest for a moment.")
sleep(2.0) # Since we imported it, we might as well use it.
print(" Wheew! Ready to continue.\n")
sleep(1.0)
elif cache[1] == 4:
cache[0] = "It's Good to be Alive!" # Let's change the private message.
# =================== MAIN ======================
if __name__ == "__main__":
for cnt in range(2):dittle() # Calls can be loop-driven, but they need not be.
print(" Attempting to pass an list to dittle()")
dittle([" BAD","Data"])
print(" Attempting to pass a non-list to dittle()")
dittle("hi")
print(" Calling dittle() normally..")
dittle()
print(" Attempting to set the private mutable value from the outside.")
# Even an insider's attempt to feed a valid format will be accepted
# for the one call only, and is then is discarded when it goes out
# of scope. It fails to interrupt normal operation.
dittle([" I am a Grieffer!\n (Notice this change will not stick!)",-7])
print(" Calling dittle() normally once again.")
dittle()
dittle()
If you run this code, you will see that the dittle() function internalizes on the the very first call but not on additional calls, it uses a private static cache (the mutable default) for internal static storage between calls, rejects attempts to hijack the static storage, is resilient to malicious input, and can act based on dynamic conditions (here on the number of times the function has been called.)
The key to using mutable defaults not to do anything what will reassign the variable in memory, but to always change the variable in place.
To really see the potential power and usefulness of this technique, save this first program to your current directory under the name "DITTLE.py", then run the next program. It imports and uses our new dittle() command without requiring any steps to remember or programing hoops to jump through.
Here is our second example. Compile and run this as a new program.
from DITTLE import dittle
print("\n We have emulated a new python command with 'dittle()'.\n")
# Nothing to declare, nothing to instantize, nothing to remember.
dittle()
dittle()
dittle()
dittle()
dittle()
Now isn't that as slick and clean as can be? These mutable defaults can really come in handy.
========================
After reflecting on my answer for a while, I'm not sure that I made the difference between using the mutable default method and the regular
way of accomplishing the same thing clear.
The regular way is to use an importable function that wraps a Class object (and uses a global). So for comparison, here a Class-based method that attempts to do the same things as the mutable default method.
from time import sleep
class dittle_class():
def __init__(self):
self.b = 0
self.a = " Hello World!"
print("\n Initializing Class Object. Executes on First Call only.")
print(" self.a = '"+str(self.a),"', self.b =",self.b,end="\n\n")
def report(self):
self.b = self.b + 1
if self.b == 1:
print(" Dittle() called",self.b,"time.")
else:
print(" Dittle() called",self.b,"times.")
if self.b == 5:
self.a = " It's Great to be alive!"
print(" Internal String =",self.a,end="\n\n")
if self.b ==3:
print(" Let's rest for a moment.")
sleep(2.0) # Since we imported it, we might as well use it.
print(" Wheew! Ready to continue.\n")
sleep(1.0)
cl= dittle_class()
def dittle():
global cl
if type(cl.a) != str and type(cl.b) != int:
print(" Class exists but does not have valid format.")
cl.report()
# =================== MAIN ======================
if __name__ == "__main__":
print(" We have emulated a python command with our own 'dittle()' command.\n")
for cnt in range(2):dittle() # Call can be loop-driver, but they need not be.
print(" Attempting to pass arguments to dittle()")
try: # The user must catch the fatal error. The mutable default user did not.
dittle(["BAD","Data"])
except:
print(" This caused a fatal error that can't be caught in the function.\n")
print(" Calling dittle() normally..")
dittle()
print(" Attempting to set the Class variable from the outside.")
cl.a = " I'm a griefer. My damage sticks."
cl.b = -7
dittle()
dittle()
Save this Class-based program in your current directory as DITTLE.py
then run the following code (which is the same as earlier.)
from DITTLE import dittle
# Nothing to declare, nothing to instantize, nothing to remember.
dittle()
dittle()
dittle()
dittle()
dittle()
By comparing the two methods, the advantages of using a mutable default in a function should be clearer. The mutable default method needs no globals, it's internal variables can't be set directly. And while the mutable method accepted a knowledgeable passed argument for a single cycle then shrugged it off, the Class method was permanently altered because its internal variable are directly exposed to the outside. As for which method is easier to program? I think that depends on your comfort level with the methods and the complexity of your goals.