Opened 12 years ago
Closed 12 years ago
#552 closed defect (fixed)
GMGTableBase uses default kwarg of {} in methods
Reported by: | nyergler | Owned by: | nyergler |
---|---|---|---|
Priority: | minor | Milestone: | 0.3.2 |
Component: | programming | Keywords: | |
Cc: | Parent Tickets: |
Description
A few places in GMGTableBase have method definitions that look something like:
def find_one(cls, query_dict={}): ...
This is almost certainly not what the authors intended. The default argument list is only evaluated once during execution, at import time. The code above creates a single dictionary as the default, and if this is mutated down the line, the mutated value becomes the default for subsequent calls. In these particular cases it's unlikely to cause problems -- that argument will almost certainly be passed in -- but in the event it's not, this can cause very strange, difficult to diagnose behavior. The correct approach is to use an immutable value as an argument default.
Note that this same problem exists for class level attributes: defining a class such as:
class Foo(object): bar = {}
means that for *every* instance of Foo
, self.bar
will point to the *same* dict.
Change History (7)
comment:1 by , 12 years ago
Status: | new → accepted |
---|
comment:2 by , 12 years ago
Priority: | major → minor |
---|
Problem at hand
First of all: Thanks for catching this!
The answer is manyfold:
- These functions should not modify an empty dict, really. If they do, something is completely wrong with them (despite the problem in this bug).
- These functions were written to emulate monogo(kit).
- They are allowed to go away. In fact, spaetz has started on this task.
- In a first step, one can kill the default argument and fix every caller that expects a default argument.
Your suggested patch
I think your suggested patch is wrong.
_fix_query_dict
modifies in place, right. So passing in query_dict or {
} will either modifiy query_dict
(good!) or just modify the {
} and leave query_dict
as None which will nicely fail the (*query_dict)
in the next line.
Let me know, if I miss something.
My suggestion:
... query_dict=Nnone): if query_dict is None: query_dict = {}
But the real fix is to find all callers using the default arg and either rewrite them in sqlalchemy or (easier!) rewrite them to pass {
} in and be done.
comment:3 by , 12 years ago
Oh, you're totally right, I wasn't paying attention. I'll update my branch with a corrected patch and update here.
Thanks for reviewing.
comment:4 by , 12 years ago
Hi all, thanks for highlighting this issue. a) I have a branch ready to remove the _fix_query_dict function completely as it does away with the ._id -> .id aliasing.
But until that is merged, the code should obviously be fixed, I have an even better suggestion than Elrond. _fix_query_dict is only ever useful with a non-empty query dict in any case, so I suggest:
def find(cls, query_dict=None): if query_dict: _fix_query_dict(query_dict) return cls.query.filter_by(**query_dict)
How about that?
P.S. Ahh, just noticed that this might pass down "None" as kwarg, which is obviously wrong. So I concur with Elrond's solution.
comment:5 by , 12 years ago
Thanks for the feedback, Spaetz and Elrond; I've updated the merge request per Elrond's suggestion.
comment:6 by , 12 years ago
Milestone: | → 0.3.2 |
---|
I really think this was merged also. Today...
Can someone confirm this?
comment:7 by , 12 years ago
Resolution: | → fixed |
---|---|
Status: | accepted → closed |
this was merged in today indeed
I've pushed a fix for this to my personal clone and opened a merge request.
https://gitorious.org/~nyergler/mediagoblin/nyerglers-mediagoblin/commits/552-kwarg-default-cleanup