5 ms·
Possibly something like this? (Code for illustration purposes only, of course -- please no one use this.) class AntiAntiPattern: 2 """A quick and
by neoncontrails 5y ago
Possibly something like this? (Code for illustration purposes only, of course -- please no one use this.)
class AntiAntiPattern:
2 """A quick and dirty attempt to mock a Python class instance that 'undoes' the antipattern
3 described here:
4 https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html)"""
5 __mutable_args__ = {} # TODO: a real implementation would probably use a WeakMap of some sort
6 def __init__(self, a, b=None, c={}, d=set()):
7 self.a = a
8 self.b = "foo" if b is None else b
9 self.c = c
10 self.d = "bar" if d is None else d
11 def __getattr__(self, k):
12 return self.__mutable_args__[k] if k in self.__mutable_args__ else super().getattr(k)
13 def __setattr__(self, k, v):
14 if v in self.__init__.__defaults__:
15 self.__mutable_args__[k] = v
16 else:
17 self.__dict__[k] = v
18 def __repr__(self):
19 return json.dumps(dict(self.__dict__, **self.__mutable_args__), default=str)
>>> t = AntiAntiPattern(4, d = 'hello!')
>>> t.__mutable_args__
{'b': None, 'c': {}}
>>> t.__dict__
{'a': 4, 'd': 'hello!'}
>>> t
{"a": 4, "d": "hello!", "b": null, "c": {}}
Edit: Oops, fixed a tiny logic bug in __init__. Trying to ween myself off of the 'a = a or "my_a_default"' syntax as instructed by previous commenters in this thread. :)
Edit2: It dawned on me that this doesn't actually resolve the 'gotcha' unless `v` is replaced with `deepcopy(v)` on line 15; otherwise the same exact problem rears its head:
>>> t = AntiAntiPattern(3, {})
>>> t.d
set()
>>> t.d.update([3,4])
>>> t
{"a": 3, "b": {}, "c": {}, "d": "{3, 4}"}
>>> t2 = AntiAntiPattern(4)
>>> t2
{"a": 4, "b": {}, "c": {}, "d": "{3, 4}"} # aw, heck
In which case I gather `__mutable_args__` is superfluous, and the solution involves simply making sure that class instances avoid pointing to the same memory objects?
- davidatbu 5y agoThanks for this writeup. By "what is your proposed solution?" I meant to ask "how would you change the language semantics to remove this 'idiotic' footgun", not "how would you work around this footgun?" I apologize for the ambiguity. I initially thought this specific footgun was an unavoidable consequence in languages that primarily pass around references (like Python and Javascript). But it turns out that Javascript's default arguments do "make a special case exception for when the expression that constitutes a function default is evaluated!" https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters#evaluated_at_call_time https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...