You are not logged in.

#1 2013-06-01 17:09:24

rebootl
Member
Registered: 2012-01-10
Posts: 431
Website

[SOLVED] Python OOP beginner

Hi

I'm trying to get in touch with OOP, but I'm having some troubles...

I managed to write a class and it behaves as I would like it to:

class Satellites:
    Planets=[]

    def populate_planets(self):

        # read file
        # ...

        for line in data_lines:
            if line.startswith("#"): continue
            elif not line.strip(): continue

            values=line.split()

            planet=Satellites()
            
            planet.type="planet"
            planet.name=values[0]   
            planet.mass=values[1]       # in [ *10^24 kg ]
            planet.R=values[2]          # in [ km ]
            planet.rot=values[3]        # in [ d ]
            planet.ded_P=values[4]      # in [ y ]
            planet.ded_mean_dist=values[5]  # in [ *10^10 m ]
            
            self.Planets.append(planet)

It's probably a bit strange to instantiate the class in itself but it behaves as I want it to, and I can use it as follows:

s1 = Satellites()
s1.populate_planets()
print("Planet x mass: ", s1.Planets[x].mass)
# etc..

But now I'd like to group the physical elements in a subgroup so that I could use this way:

s1.Planets[x].phys_data.mass
# adding later more info e.g.
s1.Planets[x].orb_el.a

But I can't figure out how to do this. I think I have to create a class "Phys_Data" and add the elements to it. But how can I integrate it in the other class in a clean way?

How would such a thing be done ? Does it makes sense to do it this way at all ?
I don't want anybody to write me a program I just can't make my head work this OOP way yet...

Thanks, Regards

Edit: I created a class for the phys. elements:

class Phys_Data:
         pass

and populate it with the attributes:

phys_data = Phys_Data()
phys_data.type="planet"
phys_data.name=values[0]
phys_data.mass=values[1]
# etc...

Is there a way to integrate it in the other class so that I can access it's attributes as described above?
e.g.

s1.Planets[x].phys_data.mass

Last edited by rebootl (2013-06-01 20:23:06)


Personal website: reboot.li
GitHub: github.com/rebootl

Offline

#2 2013-06-01 18:37:11

Nylo
Member
From: Montreal, Qc CANADA
Registered: 2013-04-26
Posts: 6

Re: [SOLVED] Python OOP beginner

You are almost there, all you need to do is instantiate a Phys_Data object in your Constructor:

def __init__(self):
    self.phys_data = Phys_Data()

That way you access to it by doing something similar to:

 s1.Planets[x].phys_data.mass = 123 

Or pass it as an argument to your constructor.

def __init__(self, data=None):
    self.phys_data = data

The default value will assign None to your phys_data field if it is not specified but that will allow you to assign already instantiated objects to the field at any moment.

Offline

#3 2013-06-01 19:13:04

McDoenerKing
Member
From: Germany
Registered: 2010-06-21
Posts: 59

Re: [SOLVED] Python OOP beginner

I think you may want to create two classes. One that is the Satellite class and a factory class for that class. Read up what a factory class is. Otherwise I might use a simple data structure and functions for the same effect. It depends on your application and scope.

Edit: By the way you may want to take a look at pep8 and pylint! They are great helpers for coding in python. Especially pylint gives you some pointers for code smell. wink

Last edited by McDoenerKing (2013-06-01 19:18:46)

Offline

#4 2013-06-01 19:38:05

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 20,347

Re: [SOLVED] Python OOP beginner

I don't have much to say here, except that I think you, like all newcomers to OOP, cannot see the forest for the trees.  You will get there; you will know when that happens when you have an "Ah Ha!" moment.

May I suggest an excellent reference in which I have no personal interest; aside from it being a good read?
Look at Dive into Python


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#5 2013-06-01 20:22:29

rebootl
Member
Registered: 2012-01-10
Posts: 431
Website

Re: [SOLVED] Python OOP beginner

@Nylo

all you need to do is instantiate a Phys_Data object in your Constructor:

Ah, finally it works! Thank you so much! And it's so simple and elegant, as it should be in Python!

Btw.: What would be the difference if I write this simply in the class instead of in the constructor:

class Satellites:
    phys_data = Phys_Data()

Edit: It seems to be the same.
I can search upon this by myself, just if you would want to give an explanation...

@McDoenerKing
Well, yes, I don't really have a plan atm, I'm pretty much experimenting around...

... cannot see the forest for the trees.

That's a perfect description of how it feels atm... smile

Thanks for your link and help.

Last edited by rebootl (2013-06-01 20:25:19)


Personal website: reboot.li
GitHub: github.com/rebootl

Offline

#6 2013-06-01 20:29:17

Trent
Member
From: Baltimore, MD (US)
Registered: 2009-04-16
Posts: 990

Re: [SOLVED] Python OOP beginner

This isn't OOP, it's just an unusually convoluted way of making a list of dictionaries. Writing more classes is not going to help from a system architecture standpoint.

What you may want is something more like a class Satellite with a constructor, and a function that returns a list of Satellites. It doesn't make sense to me to have the list or the populate_planets procedure belong to the Satellite(s) class. (If you wanted to make a class for them, like Planet_List or something like that, you could -- but why?) Here's a quick example. Unlike some other languages, Python doesn't require you to put everything in a class, so when it doesn't help you -- don't.

As for your actual question -- "physical attributes" isn't really a "thing" and a poor candidate for objecthood. I mean, sure, you can do it, but it's not really a good use of Python's OOP features. What problem are you trying to solve by doing it that way?

Offline

#7 2013-06-01 21:25:55

rebootl
Member
Registered: 2012-01-10
Posts: 431
Website

Re: [SOLVED] Python OOP beginner

Hi

Thanks for your example. I tried it and I appreciate it !

The idea behind putting populate_planets inside the class was simply, that when working with the class I create an instance first and then work with it:

s1 = Satellites()
s1.populate_planets()

This lead to what you described correctly as:

an unusually convoluted way of making a list of dictionaries

What problem are you trying to solve by doing it that way?

That is a really good question -
Again I think my idea was/is to have a class to work with. The class should contain the attributes (phys. data) necessary to perform calculations with them by using different methods.

Last edited by rebootl (2013-06-01 21:26:37)


Personal website: reboot.li
GitHub: github.com/rebootl

Offline

#8 2013-06-01 21:26:47

Xyne
Administrator/PM
Registered: 2008-08-03
Posts: 6,965
Website

Re: [SOLVED] Python OOP beginner

If you really want to make everything an object, here's one idea for organizing it.

#!/usr/bin/env python3

class PhysicalData(object):
  def from_values(self, values):
    self.mass = values[0]
    self.R = values[1]
    #...



class CelestialBody(object):
  def __init__(self, name, physical_data=None):
    self.name = name

    if physical_data is None:
      physical_data = PhysicalData()
    else:
      self.physical_data = physical_data



class OrbitingBody(CelestialBody):
  def __init__(self, name, around, physical_data=None):
    # Call the parent class's __init__ method.
    CelestialBody.__init__(name, physical_data)
    # You can also use the super() function instead of calling the parent
    # class explicitly when there is only one parent class.
    # Python 3
    # super().__init__(name, physical_data)
    # Python 2
    # super(self.__class__, self).__init__(name, physical_data)

    # The body around which this body orbits.
    self.around = around



class CelestialBodyWithOrbitingBodies(CelestialBody):
  def __init__(self, name, physical_data=None, orbiting_bodies=None):
    CelestialBody.__init__(name, physical_data)
    if orbiting_bodies is None:
      self.orbiting_bodies = list()
    else:
      self.orbiting_bodies = orbiting_bodies

  def new_orbiter(self, name):
    # A method that can be overridden in subclasses.
    # The parent class provides no default implementation, so raise an error.
    raise NotImplementedError

  def populate_orbiting_bodes(self, filepath):
    with open(filepath, 'r') as f:
      for line in f:
        line = line.strip()
        if not line or line[0] == '#':
          continue
        values = line.split()
        name = values[0]
        phys_values = values[1:]
        orbiter = self.new_orbiter(name)
        orbiter.physical_data.from_values(phys_values)
        self.orbiting_bodies.append(orbiter)



class Planet(CelestialBodyWithOrbitingBodies):
  def new_orbiter(self, name):
    # Pass self to satellite as "around" value so satellite can access this
    # instance.
    return Satellite(name, self)



class Satellite(CelestialBody):
  pass

However, I agree with Trent's point. You shouldn't shoehorn everything into a class (Python != Java). Here another approach:

#!/usr/bin/env python3

def load_data(filepath):
  with open(filepath, 'r') as f:
    physical_data = dict()
    for line in f:
      line = line.strip()
      if not line or line[0] == '#':
        continue
      values = line.split()
      name = values[0]
      physical_data['mass'] = values[1]
      physical_data['R'] = values[2]
      # ...
      yield name, physical_data



class CelestialBody(object):
  def __init__(self, name, physical_data=None):
    self.name = name

    if physical_data is None:
      physical_data = dict()
    else:
      self.physical_data = physical_data



class Star(CelestialBody):
  def __init__(self, name, physical_data=None, planets=None):
    CelestialBody.__init__(self, name, physical_data)
    if planets is None:
      self.planets = list()
    else:
      # Use "list()" to ensure that the attribute is a list. This prevents
      # unintented errors with e.g. generators and other iterables such as
      # sets.
      self.planets = list(planets)


class Planet(CelestialBody):
  # Add planet-specific stuff here.
  pass

def planets_from_file(filepath):
  for name, physical_data in load_data(filepath):
    yield Planet(name, physical_data)

# sun = Star("sun", planets=planets_from_file('planets.dat'))
rebootl wrote:

Btw.: What would be the difference if I write this simply in the class instead of in the constructor:

One is a class variable. The other is an instance variable. Maybe the following example will help:

class Foo1(object):
  foo = "foo1"

class Foo2(object):
  def __init__(self):
    self.foo = "foo2"

print("Foo1")
print("----")
foo1a = Foo1()
print(foo1a.foo)
Foo1.foo = "bar"
foo1b = Foo1()
print(foo1b.foo)

print()

print("Foo2")
print("----")
foo2a = Foo2()
print(foo2a.foo)
Foo2.foo = "bar"
foo2b = Foo2()
print(foo2b.foo)

Output:

Foo1
----
foo1
bar

Foo2
----
foo2
foo2

Despite my comment about Python != Java above, everything in Python is an object (internally), including classes, so you can change attributes of the classes.


My Arch Linux StuffForum EtiquetteCommunity Ethos - Arch is not for everyone

Offline

#9 2013-06-01 22:10:45

rebootl
Member
Registered: 2012-01-10
Posts: 431
Website

Re: [SOLVED] Python OOP beginner

One is a class variable. The other is an instance variable.

Yes, I see. They are not the same! I mis-looked it in my program output first.

Look, I made a nice module/class for the date yesterday:

## Imports
from datetime import datetime

## Classes

class JDate:
	
	def __init__(self):
		# set to None values
		self.Y = None
		self.M = None
		self.d = None
		self.h = None
		self.m = None
		self.s = None
		
		self.julian_date = None
	
	def setCurrent(self):
		# set to current date (UTC)
		utc_now=datetime.utcnow()
		
		self.Y = utc_now.year
		self.M = utc_now.month
		self.d = utc_now.day
		self.h = utc_now.hour
		self.m = utc_now.minute
		self.s = utc_now.second
		print("Date set to current (UTC): %i-%02i-%02i %02i:%02i:%02i" % (self.Y, self.M, self.d, self.h, self.m, self.s))
	
	def setDate(self, Year, Month, Day, Hour, Minute, Second):
		# set date manually
		self.Y = Year
		self.M = Month
		self.d = Day
		self.h = Hour
		self.m = Minute
		self.s = Second
		
	
	def calcJulian(self):
		# get the julian date
		
		# check
		if self.Y == None or self.M == None or self.d == None or self.h == None or self.m == None or self.s == None:
			print("Time values not initiated...")
			return
		
		elif self.Y < -4712 or (self.Y == -4712 and self.M == 1 and self.d == 1 and self.h <12):
			print("Date too long ago, only to year -4712 Jan 1 12:00:00possible...")
			return
		
		# --> below assignments is something I really don't like ... optimize ???
		Y = self.Y
		M = self.M
		d = self.d
		h = self.h
		m = self.m
		s = self.s
		
		# calculate fraction of day
		db = float(h)/24 + float(m)/(24*60) + float(s)/(24*60*60)
		df = d + db
		
		Y_calc = Y
		M_calc = M
		
		if M <=2:
			Y_calc-=1
			M_calc+=12
		
		A = int(Y_calc/100)
		# B is the correction between jul. and greg. cal.
		if Y > 1582:
			B = 2 - A + A/4
		elif Y < 1582:
			B = 0
		else:
			if M > 10:
				B = 2 - A + A/4
			elif M < 10:
				B = 0
			else:
				if d >= 15:
					B = 2 - A + A/4
				elif d <= 4:
					B = 0
				else:
					print("The date between the 4.10.1582 and the 15.10.1582 is not existent\n\
due to the change from the julian to the gregorian calendar!")
					return
		
		
		self.julian_date = int(365.25*(Y_calc+4716)) + int(30.6001*(M_calc+1)) + df + B - 1524.5

Its probably not perfect (lot's of assignments, not perfect constructor, evtl. more/better checks could be done, date could be set to current by default (constructor) etc.) and please feel free to comment on it!

But it makes sense to me to make it this way since I can use it now in a convenient way inside my main function (or possibly even in the interpreter):

    today = JDate()
    today.setCurrent()
    today.calcJulian()

    some = JDate()
    some.setDate(-4712, 1, 1, 12, 00, 00)
    some.calcJulian()

The idea is to have something similar for the planets etc. But somehow I got confused on how to load the data since here I need to create the instances automatically!

Thanks for the examples. I'll study them in depth.

Last edited by rebootl (2013-06-01 22:32:09)


Personal website: reboot.li
GitHub: github.com/rebootl

Offline

#10 2013-06-02 00:07:51

Xyne
Administrator/PM
Registered: 2008-08-03
Posts: 6,965
Website

Re: [SOLVED] Python OOP beginner

Whenever you find yourself doing something like this

		self.Y = utc_now.year
		self.M = utc_now.month
		self.d = utc_now.day
		self.h = utc_now.hour
		self.m = utc_now.minute
		self.s = utc_now.second

you should stop and ask yourself if a subclass would make more sense. You are essentially just adding methods to the time struct class, so you may as well subclass it, or create a class that stores it internally, e.g.

class Foo(object):
  __init__(self, t=None):
    if t is None:
      self.t = datetime.utcnow()
    else:
      self.t = t
    self.calcJulian()

and then use e.g. "self.t.year", "self.t.month", etc in the other methods.

Also, anything that you normally do to initialize your objects should be done in the __init__ method. In your example, you are always instantiating an object, passing it some arguments, then calling calcJulian. You could set up your __init__ function to accept optional arguments instead and automatically calculate the Julian date there. I expect that you are just printing the date for testing right now, but later you will likely wish to store the calculated date in an attribute "self.julian".

A better approach would be to create a method that accepts time arguments and updates the internal time object and calculates the Julian date. You can then call that both from the __init__ method and externally to update the time and recalculate the Julian date as needed.


My Arch Linux StuffForum EtiquetteCommunity Ethos - Arch is not for everyone

Offline

#11 2013-06-02 01:55:33

ewaller
Administrator
From: Pasadena, CA
Registered: 2009-07-13
Posts: 20,347

Re: [SOLVED] Python OOP beginner

Xyne wrote:

Whenever you find yourself doing something like this

		self.Y = utc_now.year
		self.M = utc_now.month
		self.d = utc_now.day
		self.h = utc_now.hour
		self.m = utc_now.minute
		self.s = utc_now.second

What is worse, there is mo guarantee that by the time you get down to the minutes or seconds, that the hours, days, month or even year have not rolled over.  Xyne's solution handles that problem.

Last edited by ewaller (2013-06-02 01:55:57)


Nothing is too wonderful to be true, if it be consistent with the laws of nature -- Michael Faraday
Sometimes it is the people no one can imagine anything of who do the things no one can imagine. -- Alan Turing
---
How to Ask Questions the Smart Way

Offline

#12 2013-06-02 17:36:26

rebootl
Member
Registered: 2012-01-10
Posts: 431
Website

Re: [SOLVED] Python OOP beginner

@ewaller
No, I don't think this happens.
Were you kidding ?
When I write in the interpreter (python3), as I have in my code:

utc_snap=datetime.utcnow()
# then
utc_snap.second
54
# later
utc_snap.second
54
# a lot later
utc_snap.second
54

I don't know why (Edit2: I suppose utcnow() get's called only once.) but this is not the same as:

self.Y = datetime.utcnow().year
self.M = datetime.utcnow().month
#..
# etc.

Which is somehow ridiculous, even to me.
Or do I misunderstand something here ?

As for the rest. Yes, lot's of space for improvement. wink
Edit: Still have to study your examples... But I think I can write a new, saner, Planets code now:
I'll try to make one class for Planets.
One System class, containing populatePlanets.
Loading data functions, separate, as functions.

Edit3: A lot better now (to me):

# Classes
#
# System
class System:
    def __init__(self, filename_pl_phys_data, filename_pl_orb_el):
        self.Planets = []
        self.populatePlanets(filename_pl_phys_data, filename_pl_orb_el)

    def populatePlanets(self, filename_pl_phys_data, filename_pl_orb_el):
        load_phys_data(filename_pl_phys_data, self.Planets)
        load_orb_el(filename_pl_orb_el, self.Planets)

    def calcPos(pl_index, date_inst):
         # ..
         # continue here

# Planet
class Planet:
    class Phys_Data:
        pass

    class Orbital_Elements:
        pass

    def __init__(self):
        self.phys_data = self.Phys_Data()
        self.orb_el = self.Orbital_Elements()
    
# Functions
def load_phys_data(filename, Planets=[]):
     # ...
     # ...
def load_orb_el(filename, Planets=[]):
     # ...
     # ...

There's certainly still a lot that could be improved, changed, done otherwise. But as far I'm very happy with this.
Thanks all.

Last edited by rebootl (2013-06-02 19:58:42)


Personal website: reboot.li
GitHub: github.com/rebootl

Offline

Board footer

Powered by FluxBB