Getting around immutable default arguments for recursion

D

dpapathanasiou

I wrote this function to retrieve a list of items from a dictionary.

The first time it was called, it worked properly.

But every subsequent call returned the results of the prior call, plus
the results of the current call.

I was confused until I read in the docs that default arguments are
immutable.

Is there any way around this, to be able to write recursive functions
with default arguments?

Here's the code:

def get_prior_versions (item_id, priors=[]):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
return priors
else:
priors.append(prior_id)
return get_prior_versions(prior_id, priors)
 
M

MRAB

dpapathanasiou said:
I wrote this function to retrieve a list of items from a dictionary.

The first time it was called, it worked properly.

But every subsequent call returned the results of the prior call, plus
the results of the current call.

I was confused until I read in the docs that default arguments are
immutable.

Is there any way around this, to be able to write recursive functions
with default arguments?

Here's the code:

def get_prior_versions (item_id, priors=[]):

The usual solution is:

def get_prior_versions (item_id, priors=None):
if priors is None:
priors = []
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
return priors
else:
priors.append(prior_id)
return get_prior_versions(prior_id, priors)
 
J

James Mills

I wrote this function to retrieve a list of items from a dictionary.

The first time it was called, it worked properly.

But every subsequent call returned the results of the prior call, plus
the results of the current call.

I was confused until I read in the docs that default arguments are
immutable.

Is there any way around this, to be able to write recursive functions
with default arguments?

Here's the code:

def get_prior_versions (item_id, priors=[]):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
return priors
else:
priors.append(prior_id)
return get_prior_versions(prior_id, priors)

How about:

def get_prior_versions (item_id, priors=None):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
return priors
else:
if priors:
priors.append(prior_id)
else:
priors = [prior_id]
return get_prior_versions(prior_id, priors)
 
D

dpapathanasiou

How about:

def get_prior_versions (item_id, priors=None):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
return priors
else:
if priors:
priors.append(prior_id)
else:
priors = [prior_id]
return get_prior_versions(prior_id, priors)

It's not exactly right for what I'm doing, b/c the caller always
expects a list in return.
 
J

James Mills

It's not exactly right for what I'm doing, b/c the caller always
expects a list in return.

How about this then:

def get_prior_versions (item_id, priors=None):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
if priors:
return priors or []
else:
if priors:
priors.append(prior_id)
else:
priors = [prior_id]
return get_prior_versions(prior_id, priors)

By the way, this is a really badly
written function for 2 reasons:

a) a global should and need not be used.
b) this function could be rewritten without recursion.

cheers
James
 
D

dpapathanasiou

You'll continue to be confused if you use that term. Python already
has a specific use of the term “immutable”, and it doesn't apply
here.

I was just following the terminology used in "A Byte of
Python" (which, that particular point aside, is a very good tutorial).
Better to say: default arguments are part of the function definition
statement, and are evaluated when the definition is evaluated.

Yes, well said.
 
D

dpapathanasiou

How about this then:

def get_prior_versions (item_id, priors=None):
"""Return a list of all prior item ids starting with this one"""
global history_db # key = item id, value = prior item id
prior_id = history_db[item_id]
if not prior_id:
if priors:
return priors or []
else:
if priors:
priors.append(prior_id)
else:
priors = [prior_id]
return get_prior_versions(prior_id, priors)

Without the "if priors:" line just above the first return statement (a
typo perhaps?), then yes, it would do what I want.
a) a global should and need not be used.

Passing the entire dictionary to every function that accesses it is
better?
b) this function could be rewritten without recursion.

Right, any recursive function can be written iteratively and vice-
versa. I'm not sure that makes it "bad".
 
J

James Mills

Without the "if priors:" line just above the first return statement (a
typo perhaps?), then yes, it would do what I want.

Yes sorry it was :)
Passing the entire dictionary to every function that accesses it is
better?

Yes :)
Right, any recursive function can be written iteratively and vice-
versa. I'm not sure that makes it "bad".

No it doesn't necessarily make it bad,
but recursion is not necessary here :)

What does history_db look like ? I think
I can write a much simpler function for you.

Also, show me what the results are used for.

cheers
James
 
S

Steven D'Aprano

Passing the entire dictionary to every function that accesses it is
better?

Yes. There is very little overhead when passing objects to functions in
Python. There's no performance penalty to passing objects instead of
relying on globals, and it may in fact be marginally faster:

.... return d
........ return D
........ global D
.... return D
....0.23757314682006836
 
A

alex23

dpapathanasiou said:
Passing the entire dictionary to every function that accesses it is
better?

If there are a large number of functions, you could combine them and
the history_db dict into a single object.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top