You are not logged in.
Pages: 1
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
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
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.
Last edited by McDoenerKing (2013-06-01 19:18:46)
Offline
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
@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...
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
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
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
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'))
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 Stuff • Forum Etiquette • Community Ethos - Arch is not for everyone
Offline
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
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 Stuff • Forum Etiquette • Community Ethos - Arch is not for everyone
Offline
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
@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.
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
Pages: 1