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?

Community
  • 1
  • 1
makansij
  • 9,303
  • 37
  • 105
  • 183
  • 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. – makansij Jan 08 '15 at 20:42
  • ...I should have mentioned this is **without** changing the default argument to `None` as suggested. – makansij Jan 08 '15 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`. – zehnpaard Jan 08 '15 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. – zehnpaard Jan 08 '15 at 20:48

3 Answers3

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.

Community
  • 1
  • 1
zehnpaard
  • 6,003
  • 2
  • 25
  • 40
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

Community
  • 1
  • 1
James
  • 437
  • 1
  • 6
  • 16
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.

Christian Tapia
  • 33,620
  • 7
  • 56
  • 73