2

I'm aware that a+=b and a=a+b do not always give the same result, depending on what they reference (correct me if I'm wrong here). I'm also aware of list aliasing issues in Python. see here: (Yet Another) List Aliasing Conundrum

The problem below seems to be neither of these, so I'm not sure what the issue is.

I have the following program. Pay special attention to the last line of add_clouds() and the last line of add_hosts().

define the globals and classes here

global REQUESTS 
global CLOUDS

REQUESTS = []
CLOUDS = []

class Cloud:

    def __init__(self, ID, coordinate, Hosts):
        self.ID = ID
        self.coordinate = coordinate # coordinate should be a tuple 
        self.Hosts = Hosts # Hosts should be a list of Host objects 

class Host:

    def __init__(self, ID, Resources, Cloud):
        self.ID = ID
        self.Resources = Resources # coordinate should be a tuple 
        self.Cloud = Cloud # Cloud object (NOT the Cloud ID)

This part generates the clouds and hosts

def add_cloud(ID,coordinate,Hosts=[]):
    global CLOUDS
    CLOUDS += [Cloud(ID, coordinate,  Hosts)]

def add_host(Cloud_ID, Resources):
    # search CLOUDS for Cloud_ID
    Cloud = getCloud(Cloud_ID)
    ID = len(Cloud.Hosts)+1
    Cloud.Hosts += [Host(ID,Resources,Cloud)]

def getCloud(ID):
    # returns cloud with ID provided 
    for cloud in CLOUDS:
        if ID == cloud.ID:
            return cloud

add_cloud(1,(10.7,13.5))
add_cloud(2,(1.8,3.0))
add_host(1,128)

Result in shell:

>>> CLOUDS
[<Cloud_Traversal.Cloud instance at 0x027336C0>, <Cloud_Traversal.Cloud instance at 0x02733DF0>]
>>> CLOUDS[1].Hosts
[<Cloud_Traversal.Host instance at 0x027334E0>]
>>> CLOUDS[0].Hosts
[<Cloud_Traversal.Host instance at 0x027334E0>]
>>>   

You can see that the host is somehow added to both clouds, even though I only explicitly add it to one (in the line add_host(1,128)).

I've tried to see if this is an aliasing issue, but I don't think I'm breaking any rules here. Do you know what could be the problem?

4
  • I see what you all are saying about the default argument being an empty list. Thanks. However, when I change the last line of add_host() to the following, it gives the correct result: Cloud.Hosts = Cloud.Hosts + [Host(ID,Resources,Cloud)]. It doesn't seem to me like this relates to the default parameter, so I'm not sure why it solves the problem. Commented Jan 8, 2015 at 20:42
  • ...I should have mentioned this is without changing the default argument to None as suggested. Commented Jan 8, 2015 at 20:43
  • That's because list1 += list2 appends the contents of list2 onto the current list1, while list1 = list1 + list2 creates a new list and assigns it to the variable name list1. So yes, with mutable objects, a += b is not the same as a = a + b. Commented Jan 8, 2015 at 20:45
  • In terms of efficiency, you are better off doing the 'set default argument to None' thing because that way, you don't need to recreate the entire list every time you add a host, instead it will be appending to the existing list that is unique to each cloud. Commented Jan 8, 2015 at 20:48

3 Answers 3

2

See this SO post: "Least Astonishment" and the Mutable Default Argument

When Python executes the following code:

def add_cloud(ID,coordinate,Hosts=[]):
    global CLOUDS
    CLOUDS += [Cloud(ID, coordinate,  Hosts)]

It creates a function object, and stores default parameter values that were specified in a tuple under the attribute func_defaults. See:

>>> print add_cloud.func_defaults
([],)

So basically the default parameter is always referencing the same list. And your clouds will all hold pointers to that same list too, so adding a host to one cloud will affect all others, including any new clouds that you might make in the future.

To prevent this, do the following instead:

def add_cloud(ID,coordinate,Hosts=None):
    if Hosts is None:
        Hosts = []
    global CLOUDS
    CLOUDS += [Cloud(ID, coordinate,  Hosts)]

There is also a great effbot article on this topic.

Sign up to request clarification or add additional context in comments.

Comments

0

You've used empty list syntax [] in a definition (add_cloud). That's a no-no. See this answer: https://stackoverflow.com/a/22515832/3060280

Comments

0

The problem is here:

def add_cloud(ID,coordinate,Hosts=[]):

The default value of the parameter Hosts is [], but Python only evaluates that one time. So, the second time you call add_cloud, Hosts won't be assigned to [] again.

This causes that the list you are passing as argument when you are creating the object

CLOUDS += [Cloud(ID, coordinate,  Hosts)]

is the same on each call to add_cloud.

What can you do?

One thing would be the following validation:

def add_cloud(ID, coordinate, Hosts = None):
    if Hosts is None:
        Hosts = []
    ...

Notes: Remember to follow Python naming conventions.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.