Skip to content

Commit

Permalink
Removing mutable default args
Browse files Browse the repository at this point in the history
This is a common pitfall in Python that causes unexpected behavior.

https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments
  • Loading branch information
elayeek committed Aug 13, 2024
1 parent a1eda57 commit b40c109
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 19 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ tabbycat/static/jsi18n/
tabbycat/static/vue/
tabbycat/static/fonts/

# IDE files
.idea/
.vscode/

# Translations
.tx/

Expand Down
9 changes: 7 additions & 2 deletions tabbycat/adjfeedback/tests/test_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ def _dt(self, debate, t):
def _da(self, debate, a):
return DebateAdjudicator.objects.get(debate=debate, adjudicator=self._adj(a))

def _create_debate(self, teams, adjs, votes, trainees=[], venue=None):
def _create_debate(self, teams, adjs, votes, trainees=None, venue=None):
"""Enters a debate into the database, using the teams and adjudicators specified.
`votes` should be a string (or iterable of characters) indicating "a" for affirmative or
"n" for negative, e.g. "ann" if the chair was rolled in a decision for the negative.
The method will give the winning team all 76s and the losing team all 74s.
The first adjudicator is the chair; the rest are panellists."""

if trainees is None:
trainees = []
if venue is None:
venue = Venue.objects.first()
debate = Debate.objects.create(round=self.rd, venue=venue)
Expand Down Expand Up @@ -134,7 +136,10 @@ def _create_feedback(self, source, target, confirmed=True, ignored=False):
# From team
# ==========================================================================

def assertExpectedFromTeamTracker(self, debate, t, expected, fulfilled, count, submissions, targets, tracker_kwargs={}): # noqa
def assertExpectedFromTeamTracker(self, debate, t, expected, fulfilled, count, submissions, targets, tracker_kwargs=None): # noqa
if tracker_kwargs is None:
tracker_kwargs = {}

tracker = FeedbackExpectedSubmissionFromTeamTracker(self._dt(debate, t), **tracker_kwargs)
self.assertIs(tracker.expected, expected)
self.assertIs(tracker.fulfilled, fulfilled)
Expand Down
8 changes: 6 additions & 2 deletions tabbycat/breakqual/liveness.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ def get_coefficient(m, k):
return half_row + half_row[-2::-1]


def liveness_twoteam(is_general, current_round, break_size, total_teams, total_rounds, team_scores=[]):
def liveness_twoteam(is_general, current_round, break_size, total_teams, total_rounds, team_scores=None):
if team_scores is None:
team_scores = []

if total_teams < break_size or (not is_general and len(team_scores) <= break_size):
return 0, -1 # special case, everyone is safe
Expand Down Expand Up @@ -64,7 +66,9 @@ def liveness_twoteam(is_general, current_round, break_size, total_teams, total_r
return safe, dead


def liveness_bp(is_general, current_round, break_size, total_teams, total_rounds, team_scores=[]):
def liveness_bp(is_general, current_round, break_size, total_teams, total_rounds, team_scores=None):
if team_scores is None:
team_scores = []

if total_teams < break_size or (not is_general and len(team_scores) <= break_size):
return -1, -1 # special case, everyone is safe
Expand Down
15 changes: 10 additions & 5 deletions tabbycat/draw/generator/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ class BasePairing:
This is a base class for functionality common to both two-team pairings and
BP pairings."""

def __init__(self, teams, bracket, room_rank, flags=[], team_flags={}):
def __init__(self, teams, bracket, room_rank, flags=None, team_flags=None):
"""'teams' must be a list of two teams, or four teams if it's for BP.
'bracket' and 'room_rank' are both integers.
'flags' is a list of strings."""
if flags is None:
flags = []
if team_flags is None:
team_flags = {}

self.teams = list(teams)
self.bracket = bracket
self.room_rank = room_rank
Expand Down Expand Up @@ -95,7 +100,7 @@ class Pairing(BasePairing):

sides = [DebateSide.AFF, DebateSide.NEG]

def __init__(self, teams, bracket, room_rank, num_sides=2, flags=[], team_flags={}):
def __init__(self, teams, bracket, room_rank, num_sides=2, flags=None, team_flags=None):
super().__init__(teams, bracket, room_rank, flags, team_flags)
assert len(self.teams) == 2, "There must be two teams in a Pairing"

Expand Down Expand Up @@ -147,7 +152,7 @@ class ResultPairing(Pairing):
This class is the data structure expected by DrawGenerator classes, when
taking information about the results of the previous round."""

def __init__(self, teams, bracket, room_rank, flags=[], team_flags={}, winner=None):
def __init__(self, teams, bracket, room_rank, flags=None, team_flags=None, winner=None):
super().__init__(teams, bracket, room_rank, flags, team_flags)
self.set_winner(winner)

Expand Down Expand Up @@ -176,7 +181,7 @@ def winner(self):
class PolyPairing(BasePairing):
"""Pairing class for British Parliamentary and Public Speaking."""

def __init__(self, teams, bracket, room_rank, num_sides=4, flags=[], team_flags={}):
def __init__(self, teams, bracket, room_rank, num_sides=4, flags=None, team_flags=None):
super().__init__(teams, bracket, room_rank, flags, team_flags)

def __repr__(self):
Expand All @@ -191,7 +196,7 @@ class BPEliminationResultPairing(PolyPairing):

sides = [DebateSide.OG, DebateSide.OO, DebateSide.CG, DebateSide.CO]

def __init__(self, teams, bracket, room_rank, flags=[], team_flags={}, advancing=[]):
def __init__(self, teams, bracket, room_rank, flags=None, team_flags=None, advancing=None):
super().__init__(teams, bracket, room_rank, 4, flags, team_flags)
self.set_advancing(advancing)

Expand Down
5 changes: 4 additions & 1 deletion tabbycat/draw/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ class PublicDrawTableBuilder(BaseDrawTableBuilder):
def get_sides(self, debates: List['Debate']) -> int:
return max([dt.side for debate in debates for dt in debate.debateteams], default=self.tournament.pref('teams_in_debate') - 1) + 1

def add_debate_team_columns(self, debates, highlight=[]):
def add_debate_team_columns(self, debates, highlight=None):
if highlight is None:
highlight = []

all_sides_confirmed = all(debate.sides_confirmed for debate in debates) # should already be fetched

for side in range(self.get_sides(debates)):
Expand Down
10 changes: 8 additions & 2 deletions tabbycat/draw/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ def get_page_subtitle(self):
else:
return ""

def populate_table(self, debates, table, highlight=[]):
def populate_table(self, debates, table, highlight=None):
if highlight is None:
highlight = []

table.add_debate_venue_columns(debates)
table.add_debate_team_columns(debates, highlight)
table.add_debate_adjudicators_column(debates, show_splits=False)
Expand Down Expand Up @@ -232,7 +235,10 @@ def get_page_subtitle(self):
def get_page_emoji(self):
return None

def populate_table(self, debates, table, highlight=[]):
def populate_table(self, debates, table, highlight=None):
if highlight is None:
highlight = []

table.add_round_column(d.round for d in debates)
super().populate_table(debates, table, highlight=highlight)

Expand Down
5 changes: 4 additions & 1 deletion tabbycat/importer/importers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ def convert_bool(value):
raise ValueError('Invalid boolean value: %s' % (value,))


def make_interpreter(DELETE=[], **kwargs): # noqa: N803
def make_interpreter(DELETE=None, **kwargs): # noqa: N803
"""Convenience function for building an interpreter. The default interpreter
(i.e. the one returned if no arguments are passed to this function) just
removes blank values."""
if DELETE is None:
DELETE = []

def interpreter(lineno, line):
# remove blank and unwanted values
line = {
Expand Down
5 changes: 4 additions & 1 deletion tabbycat/options/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ def get_pref(name, section=section):
return self.cleaned_data


def tournament_preference_form_builder(instance, preferences=[], **kwargs):
def tournament_preference_form_builder(instance, preferences=None, **kwargs):
if preferences is None:
preferences = []

if kwargs.get('section') in [str(s) for s in global_preferences_registry.sections()]:
# Check for global preferences
return preference_form_builder(GlobalPreferenceForm, preferences, **kwargs)
Expand Down
4 changes: 2 additions & 2 deletions tabbycat/results/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,11 @@ class DebateResultWithScoresMixin:
uses_declared_winners = False
uses_speakers = True

def __init__(self, ballotsub, load=True, criteria=[], **kwargs):
def __init__(self, ballotsub, load=True, criteria=None, **kwargs):
super().__init__(ballotsub, load=False, **kwargs)

self.positions = self.tournament.positions
self.criteria = criteria or []
self.criteria = [] if criteria is None else criteria

if load:
if self.ballotsub.id is None:
Expand Down
6 changes: 5 additions & 1 deletion tabbycat/tournaments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def num_available_adjudicators_not_allocated(self):
# Draw retrieval methods
# --------------------------------------------------------------------------

def debate_set_with_prefetches(self, filter_args=[], filter_kwargs={}, ordering=(F('venue__name').asc(nulls_last=True),),
def debate_set_with_prefetches(self, filter_args=None, filter_kwargs=None, ordering=(F('venue__name').asc(nulls_last=True),),
teams=True, adjudicators=True, speakers=True, wins=False,
results=False, venues=True, institutions=False, check_ins=False, iron=False):
"""Returns the debate set, with aff_team and neg_team populated.
Expand All @@ -451,6 +451,10 @@ def debate_set_with_prefetches(self, filter_args=[], filter_kwargs={}, ordering=
from draw.models import DebateTeam
from participants.models import Speaker
from results.prefetch import populate_confirmed_ballots, populate_wins, populate_checkins
if filter_args is None:
filter_args = []
if filter_kwargs is None:
filter_kwargs = {}

debates = self.debate_set.filter(*filter_args, **filter_kwargs)
if results:
Expand Down
6 changes: 5 additions & 1 deletion tabbycat/utils/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ def log_addition(self, request, object, message):
def log_change(self, request, object, message):
return super().log_change(request, object, self.add_ip_to_message(request, message))

def log_deletion(self, request, object, object_repr, message=[]):
def log_deletion(self, request, object, object_repr, message=None):
from django.contrib.admin.models import DELETION, LogEntry

if message is None:
message = []

return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
Expand Down
4 changes: 3 additions & 1 deletion tabbycat/utils/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,12 +803,14 @@ def add_ranking_columns(self, standings):
} for ranking in standing.iterrankings()])
self.add_columns(headers, data)

def add_metric_columns(self, standings, integer_score_columns=[]):
def add_metric_columns(self, standings, integer_score_columns=None):
"""`integer_score_columns`, if given, indicates which metrics to cast to
an int if the metric's value is an integer. For example, if the
tournament preferences are such that the total speaker score should
always be an integer, a list containing the string 'total' or
'speaks_sum' should be passed in via this argument."""
if integer_score_columns is None:
integer_score_columns = []

headers = self._standings_headers(standings.metrics_info())
data = []
Expand Down

0 comments on commit b40c109

Please sign in to comment.