Link to home
Start Free TrialLog in
Avatar of sara_bellum
sara_bellumFlag for United States of America

asked on

Building a python class and calling it from outside the class

I have a script that uses nested dictionaries to order station headers and links to station files on an html page. The new requirement is to color the links based on whether the links point to empty or populated graphs. I tried writing a loop to dynamically assign link colors to the files, but nested dictionaries do not lend themselves to this (at least I haven't found a way to make it work).

So I thought to build a class instead, where file attributes can be assigned dynamically, I think. But I have little to no experience with classes; rewriting functions into class methods and calling them outside the context of scripted examples looks next to impossible. Maybe it is for the moment, but I'll start with these questions:

a) All the variables used by the class should be defined by the init method, but some return undefined errors - why?
b) How does one call a class or method from outside the class? With lots of trial and error, my attempts fail with attribute or undefined errors.
c) Is it possible to process an ini file in the way I attempt below, i.e., take the paths to station log files and pass them to the class?




[Main]
base_dir = /home/user1/public_html/stations/
www_base = /stations/
template_file = /home/user2/cron/graphs/template.html
destination = /home/user2/public_html/stations/graphs.html
contact_address = user3@website.com
network_url = http://web-stations.org

[Stations]
stn1 = Station 1
stn2 = Station 2
stn3 = Station 3
etc...

[Graphs]
diag = Diagnostics
air = Air Temperature
bar = Barometric Pressure
etc...

Open in new window

import os, sys
import time
from datetime import datetime, date, timedelta
import pytz
import fileinput
import ConfigParser
from configobj import ConfigObj

try:
    cfg = sys.argv[1]
except:
    print 'Error: pls include config file!'
    sys.exit()

parser = ConfigParser.SafeConfigParser()
parser.read(cfg)
destination = parser.get('Main', 'destination')
network_url = parser.get('Main', 'network_url')
source_dir = parser.get('Main', 'source_dir')
target = open(destination, 'w')

station_list = [ 'stn1', 'stn2', 'stn3', 'stn4', 'stn5', \
                 'stn6', 'stn7', 'stn8', 'stn9', 'stn10' ]


class SourceError(Exception): 
    log = "Sorry, no source logs were found"


class Station:

    def __init__(self, source, option):
        self.source = source
        self.option = option
    
        self.current_date = current_date
    
        self.InDiag = InDiag
        self.line_list = line_list
        self.last_line = last_line
        self.last_ts = last_ts
        self.ts = ts
        self.time_diff = time_diff
        
        self.station_list = station_list
        self.period_list = period_list
        self.link_colors = link_colors
        self.result = result

    def procIni(self, source, option):
        
        print 'option', option   
        
        InDiag = open(self.source, 'r')
        line_list = InDiag.readlines()
        last_line = line_list[len(line_list)-1]
        last_ts = last_line[1:20]
        (rYr, rMo, rDay, rHr, rMin, rSec) = time.strptime(last_ts, "%Y-%m-%d %H:%M:%S")[0:6]
        ts = datetime(rYr, rMo, rDay, rHr, rMin, rSec, tzinfo=pytz.timezone('America/Anchorage'))
        time_diff = current_date - ts
        
        print 'time_diff', time_diff
        
        for stn in station_list:
            if option == stn:
                if float(time_diff.days) == -1:
                    delay = 0
                else:
                    delay = float(time_diff.days)
                    delay = int(round(delay, 0)

                return stn, delay
    
    def procStns(stn, delay):
        
        period_list = [ 7, 14, 30, 60, 120, 180, 360 ]
        link_colors = [ 'red', 'blue' ]
        
        for period in period_list:
            result = delay - period
            
         # process station files to figure out which results are 
         # positive and negative, find the highest negative 
         # result for each time increment by station,
         # then assign link colors to the 
         # file links based on the numbers

     
if __name__ == '__main__' :
    
    config = ConfigObj(cfg)
    current_date = datetime.now(pytz.timezone('America/Anchorage'))
    
    for option in config['Stations']:
        
        diag_path = source_dir + option + '/' + option
        if option in [ 'stn1', 'stn2', 'stn3' ]:
            source = diag_path + '_diag.dat'
        elif option == 'stn4':
            source = diag_path + '_diagnotics.dat'
        else:
            source = diag_path + '_hourlydiag.dat'
            
            if source: 
                
                try: 
                    Station(source, option)
                except SourceError:
                    print SourceError.log

Open in new window

Avatar of pepr
pepr

You have missing ) at the line 70.

"a) All the variables used by the class should be defined by the init method, but some return undefined errors - why?"

This is only a recommendation.  The instance variables can be created on-the-fly.  What errors do you mean?  Is there any special reason to use the non-standard configobj module?


"b) How does one call a class or method from outside the class? With lots of trial and error, my attempts fail with attribute or undefined errors."

A class is the description of the data structure and the code that works with the data.  The class definition is static.  When you want to use it, you usually create an instance of that class -- also known as "the object" of the class.  Basically, it contains the data part of the wanted "something" plus reference to the class that defines the code.  It makes no sense to say to "call a class or a method from outside a class".  You are always calling a code.  The code can be part of the class definition.  You can call the code via the class name; however, it is mostly called via the object instance.  If Station is you class, then "calling"

    Station(source, option)

... actually mean creating the object (the instance of the class).  You most probably want to keep the reference to the created object and then you want to use that object.  Using the object often means to call its methods.

    station = Station(source, option)
    station.procIni()     # this should probably be called by the __init__()
    station.procXxx()

"c) Is it possible to process an ini file in the way I attempt below, i.e., take the paths to station log files and pass them to the class?"

There is nothing magical related to the classes. They (roughly said) define what data may be used "from inside" and what code belongs to the class.  The code usually manipulates with the passed arguments and with the variables (usually with the object variables).  


I am planning to write the sequels to the http:A_5354-Python-basics-illustrated-part-1.html article where the things are to be explained.  Until then, ask for details here.  It may be valuable for both you and me ;) (I will use your questions in the article to keep myself in pace with beginners).
Think about the object as about something that simulates the reality.  If Station is the class that describes whatever station, the station instance is one exact instance.  When "personified", you can ask the station various information like "your name", "your time", etc. Or you ask for the data value kept inside, or you ask for task to be done -- i.e. calling the method of the object (also known as a member function of the class or of the object -- all objects of the same class share the same definition of the methods).

Think about objects being the people: one may ask another for the information; the question may be found by another, intermediate (i.e. agent) object from somewhere -- you may not know from where and you do not care.
Avatar of sara_bellum

ASKER

I am in your debt pepr! Pls don't hesitate to call on me for any beginner's sanity check you may require. I'm thinking about the advice you posted but haven't had the time to act on it, since I'm facing some health issues at the moment. Will try to keep up the momentum to make sure I understand before closing this, thanks.
You are not my debtor in any way ;)  Being humans, we should get used to think in other categories than "tit for tat".  Crossing fingers for your health.
Thanks for your good wishes - surgery and chemo have slowed me down. But I thought I'd take another stab at this question.

I had hoped to build a class until I realized that I don't understand the first thing about classes, in spite of what has been (by my standards) considerable reading on the subject.

When writing functions/methods, I can insert print statements to see what the function returns. But I can't print any return values from a class unless they're defined in the init method - none of the other methods do anything at all.

I thought that you could feed data to a class for it to return values based on what you want to do with the data. What am I missing?
class PrintMe:
    
    def __init__(self, speech, number, value):
        self.speech = 'War' 
        self.number = number
        self.value = value
        
    def __words__(self, speech):
        speech = speech.upper(speech + ' and Peace')
        return speech
    
    def __add__(self, number, value):
        my_sum = number + value
        return my_sum
        
x = PrintMe('words', 3, 4)
#print 'x = ', x.my_sum # my sum not defined (not in init method)
#y = PrintMe(speech) # AttributeError: 
#PrintMe instance has no attribute 'speech' and speech is not defined
y = PrintMe('look for quoted text', 3, 4) 
print 'this prints only init value:', y.speech

Open in new window

SOLUTION
Avatar of pepr
pepr

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
In the mean time, have a look at the doc "3.4. Special method names" http://docs.python.org/reference/datamodel.html#special-method-names
ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks very much!!! I have no more questions about the strings now and thank you for cleaning up my syntax, which was breaking things.

But the numbers part still puzzles me: if you pass all the variables as args to the init method, you don't need a function to calculate a sum (or manipulate any numbers). But if you don't pass the sum as an arg to init(), it remains undefined as before. I post my latest below.
class Adder:
        
    def __init__(self, number, value): # when does init() need args?
        self.number = number
        self.value = value
        self.my_sum = my_sum
        
    def add(self, number, value): # method has args
        self.my_sum = number + value
        return my_sum
    
x = Adder(3, 4) # does the class need args?
x.add(3, 4) # the method needs args!
print x.my_sum # NameError: global name 'my_sum' is not defined

Open in new window

I had missed your comment about the optional nature of a return value. I disabled the return value and the add function works, code copied below.
But I'm still confused: under what circumstances should a class method return a value? Why does the return value statement produce a "global name 'my_sum' not defined error?
I use comments to understand the code / mark what I don't understand. Let me know about those, thanks.
class Adder:

    def __init__(self, number, value, my_sum): 
    # does init() always need args to define variables?
        self.number = number
        self.value = value
        self.my_sum = my_sum

    def add(self, number, value): 
        self.my_sum = number + value # why self.my_sum and not my_sum?
        #return my_sum # NameError: global name 'my_sum' is not defined. Why?

x = Adder(1, 2, 3) # the class needs args to serve as placeholders
x.add(3, 4) # the method args are not read unless they're fed to init()
print x.my_sum # prints 7

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Great, thanks again!!

print x.add(3,4) works when the function returns a value (otherwise it prints None), so now I see how a return value can work.

I can't think of any more questions at this point so I'll close :)
For the None.  Every function in Python returns a value.  If you do not use the return command, it is the same as if you used "return None".