0

I have a simple program that takes a JSON representation of some planets and builds a representation of those planets. It is very straight-forward:

The json input maintains the structure of what celestial bodies(CB) have satellites, what CBs are satellites of what, and various attributes about said CB. That is fed into a simple parser which builds a representation of the system from that input.

The catch here is that each CB can have an n-deep list of orbiting objects. The cleanest/most elegant way to solve this problem is with recursion. Anytime we see there are orbiting bodies, we recurse by use of "get_satellites" until there are no more orbiting objects for the given body.

Below is the json and simplified code, which is runnable, but isn't functioning correctly.

What happens below is that earth, venus, mars, and mercury all end up having 3 satellites each, even though earth should only have one and mars should only have 2.

I have looked at: Python recursive function variable scope

and "Least Astonishment" and the Mutable Default Argument

These may be relevant, but I' not 100% sure because I don't use default arguments in the get_satellites() method call. It seems like it shouldn't apply, but the symptoms that are presenting suggest otherwise.

Code

import sys
import json

"""
Parent class for bodies
"""
class Body(object):

    # name of entity
    name = ""
    type = None

    #type of entity
    class Types:
        Planet, BlackHole, Star, Comet, Asteroid, Satellite = range(6)

        def get_type(t):
            t = t.lower()
            if t == "planet":
                return Body.Types.Planet
            elif t == "star":
                return Body.Types.Star
            elif t == "comet":
                return Body.Types.Comet
            elif t == "asteroid":
                return Body.Types.Asteroid
            elif t == "satellite":
                return Body.Types.Satellite
            elif t == "blackhole":
                return Body.Types.BlackHole
            else:
                return Body.Types.Planet

    def __init__(self, name, t):
        self.name = name
        self.type = t

    def to_string(self):
        return "Name: %s\t Type: %s" % (self.name, self.type)

"""
planet definition
"""
class Planet(Body):

    # point where body is farthest to orbiting body in AU
    aphelion = 0
    # point where body is closest to orbiting body in AU
    perihelion = 0
    # List of orbiting bodies
    satellites = {}
    # orbiting body of this body
    orbiting_body = {}

    def __init__(self, name, t):
        Body.__init__(self, name, t)

    def add_satellites(self, bodies):
        for body in bodies:
            print("PAS: adding: %s to: %s" % (body.name, self.name))
            self.satellites[body.name] = body

    def add_satellite(self, body):
        print("PAS: adding: " + body.name)
        self.satellites[body.name] = body

    def add_orbiting_body(self, body):
        self.orbiting_body[body.name] = body

    def simulate(self):
        print("simulating a planet")

    def to_string(self):
        s = Body.to_string(self)
        s += "Planet: %s has %d satellites\n" % (self.name, len(self.satellites))
        return s

    def __str__(self):
        return self.to_string()

"""
star definition
"""
class Star(Body):

    # weak stellar classification system
    class Stages:
        Nebula, Protostar, RedGiant, RedDwarf, WhiteDwarf, SuperNova, NeutronStar, BlackHole = range(8)
    # which stage is the star in
    stage = Stages.RedGiant
    # light observed on earth
    brightness = 0
    # intrinsic brightness
    absolute_magnitude = 0
    # total amount of energy emitted
    luminosity = 0
    # list of orbiting bodies
    satellites = {}
    #

    def __init__(self, name, t, stage=Stages.RedGiant):
        Body.__init__(self, name, t)
        self.stage = stage

    def add_satellites(self, bodies):
        for body in bodies:
            #print("Star: adding: " + body.name)
            self.satellites[body.name] = body

    def add_satellite(self, body):
        #print("Star: adding: " + body.name)
        self.satellites[body.name] = body

    def simulate(self):
        print("simulating a star")

    def to_string(self):
        s = "%sStage: %s\n" % (Body.to_string(self), self.stage)
        #if self.satellites:
        for satellite in self.satellites:
            s += self.satellites[satellite].to_string()
        return s

    def __str__(self):
        return self.to_string()


"""
Allows us to build the body we need
"""
def build_body(body):
    print(body)
    t = Body.Types.get_type(body['type'])
    if t == Body.Types.Planet:
        b = Planet(body['name'], t)
    elif t == Body.Types.Star:
        b = Star(body['name'], t)
    return b

"""
Get the orbiting bodies recursively
"""
def get_satellites(satellites):
    bodies = []
    x=0
    # iterate mercury, venus, earth....
    if satellites['orbiting_objects']:
        for satellite in satellites['orbiting_objects']:
            print("x is: %s" % x)
            # build the body that satellites orbit
            body = build_body(satellite)
            # if mercury, venus, earth... have satellites
            if satellite['orbiting_objects']:
                #print("%s has satellites!" % body.name)
                # recursively build the satellites
                sats = get_satellites(satellite)
                #for s in sats:
                    #print("on sats: %s"%s)
                # body has satellites, add them
                body.add_satellites(sats)
            else:
                print("%s has no satellites" % body.name)
            #print("%s has %d satellites" % (body.name, len(body.satellites)))
            bodies.append(body)
            #if len(body.satellites) > 0:
                #for b in body.satellites:
                    #print("satellites: %s" % b)
            print("==========")
            x += 1
    else:
        # add a satellite-less body to the system
        bodies.append(build_body(satellites))
    return bodies


def main():
    if len(sys.argv) < 2:
        print("Usage: python main.py path/to/simulation/data.json")
        exit()

    with open(sys.argv[1]) as file:
        data = json.load(file)
        print(data[0])
        b = build_body(data[0])
        sats = get_satellites(data[0])
        b.add_satellites(sats)

        print(b.to_string())

if __name__ == "__main__":
    main()

JSON

  [
                    {
                      "name": "sun",
                      "type": "star",
                      "mass": 1988550000000000000000000000000,
                      "volume": 1410000000000000000,
                      "gravity": 27.94,
                      "aphelion": 0,
                      "perihelion": 0,
                      "radius": 696300,
                      "orbiting_objects": [
                        {
                          "name": "mercury",
                          "type": "planet",
                          "mass": 330110000000000000000000,
                          "volume": 60830000000,
                          "gravity": 0.38,
                          "aphelion": 0.466697,
                          "perihelion": 0.307499,
                          "radius": 2440,
                          "orbiting_objects": []
                        },
                        {
                          "name": "venus",
                          "type": "planet",
                          "mass": 4867500000000000000000000,
                          "volume": 928430000000,
                          "gravity": 0.904,
                          "aphelion": 0.728213,
                          "perihelion": 0.718440,
                          "radius": 6051.8,
                          "orbiting_objects": []
                        },
                        {
                          "name": "earth",
                          "type": "planet",
                          "mass": 5972370000000000000000000,
                          "volume": 1083210000000,
                          "gravity": 9.807,
                          "aphelion": 1.01673,
                          "perihelion": 0.9832687,
                          "radius": 6371.0,
                          "orbiting_objects": [
                            {
                              "name": "moon",
                              "type": "planet",
                              "mass": 73420000000000000000000,
                              "volume": 21958000000,
                              "gravity": 0.1654,
                              "aphelion": 0.00270993162,
                              "perihelion": 0.00242383129,
                              "radius": 1737.1,
                              "orbiting_objects": []
                            }
                          ]
                        },
                        {
                          "name": "mars",
                          "type": "planet",
                          "mass": 641710000000000000000000,
                          "volume": 163180000000,
                          "gravity": 0.376,
                          "aphelion": 1.6660,
                          "perihelion": 1.3814,
                          "radius": 3389.5,
                          "orbiting_objects": [
                            {
                              "name": "phobos",
                              "type": "planet",
                              "mass": 0,
                              "volume": 0,
                              "gravity": 0,
                              "aphelion": 0,
                              "perihelion": 0,
                              "radius": 0,
                              "orbiting_objects": []
                            },
                            {
                              "name": "deimos",
                              "type": "planet",
                              "mass": 0,
                              "volume": 0,
                              "gravity": 0,
                              "aphelion": 0,
                              "perihelion": 0,
                              "radius": 0,
                              "orbiting_objects": []
                            }
                          ]
                        }
    ]}]

Output

{'mass': 1988550000000000000000000000000, 'type': 'star', 'volume': 1410000000000000000, 'orbiting_objects': [{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}, {'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}, {'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}, {'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}], 'perihelion': 0, 'name': 'sun', 'aphelion': 0, 'radius': 696300, 'gravity': 27.94}
{'mass': 1988550000000000000000000000000, 'type': 'star', 'volume': 1410000000000000000, 'orbiting_objects': [{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}, {'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}, {'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}, {'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}], 'perihelion': 0, 'name': 'sun', 'aphelion': 0, 'radius': 696300, 'gravity': 27.94}
x is: 0
{'mass': 330110000000000000000000, 'type': 'planet', 'volume': 60830000000, 'orbiting_objects': [], 'perihelion': 0.307499, 'name': 'mercury', 'aphelion': 0.466697, 'radius': 2440, 'gravity': 0.38}
mercury has no satellites
==========
x is: 1
{'mass': 4867500000000000000000000, 'type': 'planet', 'volume': 928430000000, 'orbiting_objects': [], 'perihelion': 0.71844, 'name': 'venus', 'aphelion': 0.728213, 'radius': 6051.8, 'gravity': 0.904}
venus has no satellites
==========
x is: 2
{'mass': 5972370000000000000000000, 'type': 'planet', 'volume': 1083210000000, 'orbiting_objects': [{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}], 'perihelion': 0.9832687, 'name': 'earth', 'aphelion': 1.01673, 'radius': 6371.0, 'gravity': 9.807}
x is: 0
{'mass': 73420000000000000000000, 'type': 'planet', 'volume': 21958000000, 'orbiting_objects': [], 'perihelion': 0.00242383129, 'name': 'moon', 'aphelion': 0.00270993162, 'radius': 1737.1, 'gravity': 0.1654}
moon has no satellites
==========
PAS: adding: moon to: earth
==========
x is: 3
{'mass': 641710000000000000000000, 'type': 'planet', 'volume': 163180000000, 'orbiting_objects': [{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}, {'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}], 'perihelion': 1.3814, 'name': 'mars', 'aphelion': 1.666, 'radius': 3389.5, 'gravity': 0.376}
x is: 0
{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'phobos', 'aphelion': 0, 'radius': 0, 'gravity': 0}
phobos has no satellites
==========
x is: 1
{'mass': 0, 'type': 'planet', 'volume': 0, 'orbiting_objects': [], 'perihelion': 0, 'name': 'deimos', 'aphelion': 0, 'radius': 0, 'gravity': 0}
deimos has no satellites
==========
PAS: adding: phobos to: mars
PAS: adding: deimos to: mars
==========
Name: sun    Type: 2Stage: 2
Name: earth  Type: 0Planet: earth has 3 satellites
Name: mars   Type: 0Planet: mars has 3 satellites
Name: mercury    Type: 0Planet: mercury has 3 satellites
Name: venus  Type: 0Planet: venus has 3 satellites

I would expect that earth has 1 satellite, mars has 2, and venus and mercury have 0.

Community
  • 1
  • 1
lilott8
  • 1,116
  • 2
  • 17
  • 41
  • 1
    The linked duplicate will explain it pretty well, but the long & short is - you must declare things like `name` within `__init__` (or some other function) or they belong to the class, not to individual instances. – g.d.d.c Feb 25 '16 at 23:30
  • No. Unless I'm misunderstanding your comment, and the post you reference as duplicate, my problem is that the lists of satellites that are class member variables end up sharing the same list of `satellites`: e.g. mars has only 2 satellites, but if you look at the output, you see that mars have 3 satellites. Which shouldn't be. This isn't a scope problem; but it seems like the links I submitted may impact this in some way. – lilott8 Feb 25 '16 at 23:34

0 Answers0