newbie help needed factoring methods and params in class defs

Roeland Rengelink r.b.rigilink at cable.a2000.nl
Mon Oct 2 05:48:44 EDT 2000


Hi Jason,

A couple comments on your code, and, hopefully, some helpful
explanation.

Jason Cunliffe wrote:
> 
> I am writing a module 'caldict.py' with classes to manage 'timeline'
> dictionaries. Timestamps (as strings) are lookup keys and the event data
> they fetch is any string.
> I am using Marc-André Lemburg's  mxDateTime package
> http://starship.python.net/~lemburg/mxDateTime.html
> plus 'seqdict' and 'mseqdict' modules by Wolfgang Grafen
> http://home.germany.net/100-366919/Python/Modules/Modules.html
> A great combination - thanks to both authors.
> 
> So far so good.
> I have a class CalDict() which has various methods: init(), append(),
> sort(), findnth(), etc..
> I want to use key arguments for various options for all my functions in a
> consistent manner.
> 
> import caldict
> cd = caldict.CalDict()
> cd.append(year=1955, month=4, day=5, hour=14, minute=30, 'birthday')

I'm surprised whis works. If I try something similar, I get

>>> SyntaxError: non-keyword arg after keyword arg

I would expects the following to work correctly

cd.append('birthday', year=1955, month=4, day=5, hour=14, minute=30)

provided your method definition is something like

def append(event, year=0, month=0, day=0, hour=14, minute)


> cd.append(year= -2500, 'Bronze age')
> cd.append(year = 1789, 'French Revolution')
> cd.append(str(now()), 'here and now')
> #now put everything in order beautifully thanks to seqdict and mxDateTime
> cd.sort()
> #Timeline events can be returned by date...
> cd.getevent('1955-04-05 14:30:00.00')
> #or by index...
> cd.findnth(index='last')
> cd.findnth(index=1)
> cd.findnth(index= -2)  #returns penultimate event ie: 'French Revolution'
> #cool!
> #now I need the option to have other arguments also for example:
> cd.findnth(index='last', display=1)
> #or
> cd.findnth(index='last', showdate=1)
> 
> These will overide defaults and display the result, or show the date as well
> as the event string for whatever the last item in the timeline is.
> 
> At present I do this very simply as follows inside class Caldict:
> 
>    def reverse(self, display = 0):
>         s = self.eventdict
>         if display:
>             self.display()
> 
> But as I look down my list of growing class methods, I see the same code
> repeated again and again.
> There must be a much more elegant pythonic way to factor the "if display"
> condition out. But not sure how..
> I have a method display () in the top of my Class.but when I  try to detect
> there to see if the passed parameter  display =1 it does not work.
> 
> I seem to be not undersrtanding something basic about passing parameters
> and reusing methods within a class definition.
> 
> Another perhaps related problem also concernt best way to factor:
> 
> 1 class Caldict:
> 2   def __init__(self, size=12, months=1, days=0, hours=0):
> 3    #default creates 12 months from the present day and time
> 4        self.eventdict = seqdict.seqdict()
> 5        self.indexdict = {}
> 6        s = self.eventdict

No need to this. You're just telling Python that you want to use s to
refer to self.eventdict, but why not use self.eventdict to refer to 
self.eventdict

> 7        for x in range (size):
> 8            #initialize our delta values
> 9            dmonths = months * x
> 10            ddays = days * x
> 11           dhours = hours * x
> 12           ddate = now()+RelativeDateTime( months=+dmonths, days=+ddays,
> hours=+dhours)
> 13            s.append(str(ddate), "entry" + str(x))
> 14           s.sort()
You can simply do
                self.eventdict.append(str(ddate), "entry" + str(x))
                self.eventdict.sort()

> 15   #showme
> 16   def display(self):
> 17      s = self.eventdict
> 18      if display:
> 19         for x in range(len(s)):
> 20                print x, s.keys()[x]
> 
> Why can do I have to redefine in line 17 's = self.eventdict' when I alrady
> did so in  line 6. Same for all teh followng methods?
> 

Well, the answer is that the s is in the local scope of both __init__()
and display(), which means it is only defined within those functions,
and Python forgets about it at soon as it returns from the function.
But, as I said, you 
don't need s.

>
> This must be Python101..
> I someone can help me get to grips with Python classes better..
> 

OK. 

In Python names are references to objects. What this means is best shown
by example.

>>> class A:                    # define a class object and name it A
...     def do(self):
...         print self
...
>>> A                           # What does A refer to?
<class __main__.A at 80f76e0>
>>> A()	                        # make an instance of A
<__main__.A instance at 80e6668>
>>> a = A()                     # make another instance of A and name it a
>>> a                           # What does a refer to?
<__main__.A instance at 80d91c8>
>>> a.do()			# call a.do
<__main__.A instance at 80d91c8>
>>> A.do(a)			# a.do() == A.do(a)
<__main__.A instance at
80d91c8>                                                
>>> A.do()
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: unbound method must be called with class instance 1st
argument

So, whenever Python encounters a name it has to look up what that name
refers
to. To do that Python looks the name up in a dictionary. At any point in
the program Python has access to two dictionaries, the global and the
local 
dictionary (You can have a look at these using the build-in functions
globals() 
and locals()) Each function has it's own local dictionary that is
created when the function is called, and destroyed when the function
returns. 


>>> class A:
...     def do(self, x):
...         y = 1
...         print locals()
...
>>> a = A()
>>> a.do('Hi')
{'self': <__main__.A instance at 80f6a00>, 'x': 'Hi', 'y': 1}
>>> A.do(a, 'Ho')
{'self': <__main__.A instance at 80f6a00>, 'x': 'Ho', 'y':
1}                   

Note how calling the method do() fills the locals() dictionary with the
names 
self and and x. The rule for the name lookup is simple if the name is
assigned to anywhere in the function look in the locals() dictionary,
else look in the globals() dictionary. 

There are other places where references to objects are stored, in
classes, instances and modules. There is a special syntax to refer to
these dictionaries:
class_name.__dict__, instance_name.__dict__, module_name.__dict__.
Moreover, there is a convenient shorthand notation to refer to the
contents of these dictionaries, namely:

name.attrribute_name is shorthand for:
name.__dict__[attribute_name],

with the special rule that for retrieving references in instances:

if instance_name.__dict__[attribute_name] raises a KeyError, then
instance_name.__class__.__dict__[attribute_name] should be returned.

To illustrate

>>> class A:
...     x = 1               # A.__dict__['x'] = 1
...     def do(self, y):
...             self.y = y  # self.__dict__['y'] = y 
...
>>> a = A()
>>> a.__dict__      
{}
>>> a.do(2)                 # self=a ; self.__dict__['y'] = 2
>>> a.__dict__
{'y': 2}
>>> A.__dict__
{'__doc__': None, '__module__': '__main__', 'x': 1, 'do': <function do
at 80d920
>>> a.__class__.__dict__
{'__doc__': None, '__module__': '__main__', 'x': 1, 'do': <function do
at 80d9208>}
>>> a.x	                    # a.__dict__['x'] gives KeyError, return A.x
1
>>> A.x = 3                 # A.__dict__['x'] = 3
>>> a.x			    # a.__dict__['x'] still gives KeyError
3
>>> a.x = 2                 # a.__dict__['x'] = 2
>>> a.x
2
>>> A.x                     # A.__dict__['x'] still contains 3
3                                                                               

It should be clear now, why we pass a refrence to an instance to class
methods.
To give the methods access to the instance's dictionary. Note that
classes don't know when they are instantiated (sp?), nor does the class
method know which instance called it. In python the method has to be
told explicitly which instance called it, hence the argument 'self'. 
The only convenience Python provides is
that 

instance.method()

is converted automatically in

instance.__dict__['method'](instance)

I hope you now understand why you don't have to do

s = self.eventdict 

within each method.

I also hope you understand why the display() method and the display=1
method-argument are completely unrelated. One is a function object
referred
to in CalDict.__dict__ the other is a name in the locals()- dictionary
of the findnth() method.


> Thanks
> - Jason
> 
> ________________________________________________________________
> Jason CUNLIFFE = NOMADICS.(Interactive Art and Technology).Design Director

Hope this help,

Roeland



More information about the Python-list mailing list