Search code examples
pythonlistself

Where did I mess up in this program for tracking faction alliances?


I have a program that models kingdoms and other groups (called 'factions' in my code).

class Faction:
    def __init__(self, name, allies=[]):
        self.name = name
        self.allies = allies

    def is_ally_of(self, other_faction):
        if self in other_faction.allies:
            return True
        else:
            return False

    def become_ally(self, other_faction, both_ally=True):
        """ If both_ally is false, this does *not* also 
            add self to other_faction's ally list """
        if self.is_ally_of(other_faction):
            print("They're already allies!")
        else:
            self.allies.append(other_faction)
            if both_ally == True:
                other_faction.become_ally(self, False)

RezlaGovt = Faction("Kingdom of Rezla")
AzosGovt = Faction("Azos Ascendancy")

I want to be able to call a factions become_ally() method to add factions to the ally lists, like this:

RezlaGovt.become_ally(AzosGovt) # Now AzosGovt should be in RezlaGovt.allies,
                                # and RezlaGovt in AzosGovt.allies

What actually happens is this:

RezlaGovt.become_ally(AzosGovt)
# prints "They're already allies!"
# now AzosGovt is in the allies list of both AzosGovt and RezlaGovt, 
# but RezlaGovt isn't in any allies list at all.

Whenever I try to call become_ally(), the code should check to make sure they aren't already allies. This is the part that isn't working. Every time I call become_ally(), it prints "They're already allies!", regardless of if they actually are.

I also tried to use if self in other_faction.allies:, but that had the same problem.

I strongly suspect that the problem is with my use of self, but I don't know what terms to Google for more information.


Solution

  • You can't use mutable arguments as the default argument to a function.

    def __init__(self, name, allies=[]):
    

    When the default is used, it's the same list each time, so they have the same allies; mutating one changes the other because they're actually the same thing.

    Change to:

    def __init__(self, name, allies=None):
       if allies is None:
           allies = []
    

    Alternatively, copy the allies argument unconditionally (so you're not worried about a reference to it surviving outside the class and getting mutated under the class):

    def __init__(self, name, allies=[]):
        self.allies = list(allies)  # Which also guarantees a tuple argument becomes list
                                    # and non-iterable args are rejected