Building a python class and calling it from outside the class

sara_bellum
sara_bellum used Ask the Experts™
on
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

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
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.

Author

Commented:
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.
Build an E-Commerce Site with Angular 5

Learn how to build an E-Commerce site with Angular 5, a JavaScript framework used by developers to build web, desktop, and mobile applications.

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.

Author

Commented:
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

A class can define its own data (a.k.a. member variables) and its own methods (a.k.a. member functions).  In Python, when using the class name as if it was a function (here PrintMe('something'...))--and also in other languages--causes creation of the object of the class (a.k.a. the class instance).  The object has its own identity, is of some class (see above), captures inside its own data, and "has" its own methods (i.e. can call them).  

Think about the object method as about a function that can be used only by the objects of that class.

When the object is created (via calling its class name), the first method is called automatically -- the __init__().  Then you get the reference to the object (here the y).  If you want to call another method, you have to call it.  It cannot read your mind, it cannot be called automatically as the __init__() was.  You have to write it explicitly.

One detail, you should not use __thisStyle__ with doubled underscores for your own methods.  It is kind of reserved for a special purpose (for the things that are common to many different classes).  Just give it a normal name.

Return values are just return values.  Use them in the print command if you want to print them.

However, there is more small wrong pieces in your example that may confuse your understanding.  I will dissect the example soon.
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
Try this simplified example and ask further...

class Slogan:
    
    def __init__(self, speech):
        self.speech = speech   # instead of 'War'. You did not use the argument.
        
    def words(self, speech):   # identifier changed -- no underscores
        self.speech = speech.upper() + ' and Peace'
        # Methods are not required to return anything.  They may manipulate with
        # the object data members.  Here the passed argument speech is 
        # upper cased ' and Peace' is added.  The result is stored
        # in the member variable self.speech.  The speech argument is
        # different from the self.speech.
    
x = Slogan('War')
print x.speech        # the x from outside is the self from inside
x.words('Love')       # method called -- member variable changed via it
print x.speech        # print the new value

y = Slogan('Love is in the air')  # another object created -- a different content
print y.speech
y.words('Not the war, just love')
print y.speech

print x.speech        # the first object of the same class still exists

Open in new window


It prints on my screen...

c:\tmp\___python\sara_bellum\Q_27038876>python b.py
War
LOVE and Peace
Love is in the air
NOT THE WAR, JUST LOVE and Peace
LOVE and Peace

Open in new window

Author

Commented:
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

Author

Commented:
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

A member function (a.k.a. method) is almost a normal function.  It is up to you if you want to return anything or not.  Actually, it is often used for its side-effect -- for manipulation with the object data.

Your return in the add() method did not work because you wanted to return the my_sum, but it does not exist.  You may be confused from other languages (C++ and others) where the my_sum would be considered in the cotext of the surrounded code as the member variable of the class.  However, Python requires to say it more explicitly.  The member variable (of the object) is the self.my_sum.  You have to return self.my_sum.

Think about the "self" as about the reference to the object.  If my_sum is the part of the object, then its full name is self.my_sum.  You cannot shorten the name by omiting the self.

Notice also that each method of the class takes the self as the first argument.  You can think about the method as about a normal function that works with the data structure (the object) named explicitly "self".

On the line 13, the x is the reference to the created object.  It contains x.number, x.value, x.my_sum, and it can use the metod x.add() and others.  Instead of writing the x.add(3, 4), you could also write Adder.add(x, 3, 4).  The x seen from outside is the reference to the same data as self from inside the function.  Writing the x.add(3, 4) is just a more usual and more convenient syntax related to object-oriented languages.  

Actually, using the less convenient syntax was the way how object-oriented programs could be written in non-object oriented programming languages.  The first argument is the reference to the object.  The OO language compiler just makes the object method call looking syntactically different by writing "the first argument" in front of the method name.  It emphasizes the fact that the object is the core of the operation.

Python just reveals the implementation of working with objects by forcing you to write the reference to the object (the "self" by convention) explicitly as the first argument in the method definitions.

For the first part, the __init__() is the first method to be called when the object was created.  From that point of view, you do not need to define the __init__() at all.   However, it is the only way in Python how you can pass the arguments from a "constructor" (which is actually not a constructor in say the C++ sense).

The object can create its own data members whenever you decide.  You can write:

class Adder:

    def add(self, number, value): 
        self.my_sum = number + value
        
x = Adder()
x.number = 1
x.value = 2
x.add(3, 4)
print x.my_sum

Open in new window


No explicit __init__() at all.  The .number and .value part were created because they once appeared at the left side of the assignment operator.

However, it is more usual (less confusing) not to create the new member variables on the fly.  It is more difficult to spot them later in the code.  However, it can happen by mistake in Python -- you may create a member variable that you do not want (say self.numbe = 5).

Author

Commented:
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".

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial