views:

751

answers:

3

I made a module prototype with the aim of building complex timer schedules in python. The class prototypes emulate Timer objects, each with their waiting times, Repeat objects that group Timer and other Repeat objects, and a Schedule class, just for holding a whole construction or Timers and Repeat instances. The construction can be as complex as needed and needs to be flexible.

Each of these three classes has a .run() method, permitting to go through the whole schedule. Whatever the Class, the .run() method either runs a timer, a repeat group for a certain number of iterations, or a schedule.

Is this polymorphism-oriented approach sound or silly? What are other appropriate approaches I should consider to build such a versatile utility that permits to put all building blocks together in as complex a way as desired with simplicity?

Thanks!

Here is the module code:

#####################
## Importing modules

from time import time, sleep


#####################
## Class definitions

class Timer:
    """
    Timer object with duration.

    """
    def __init__(self, duration):
        self.duration = duration
    def run(self):
        print "Waiting for %i seconds" % self.duration
        wait(self.duration)
        chime()

class Repeat:
    """
    Repeat grouped objects for a certain number of repetitions.

    """
    def __init__(self, objects=[], rep=1):
        self.rep = rep
        self.objects = objects
    def run(self):
        print "Repeating group for %i times" % self.rep
        for i in xrange(self.rep):
            for group in self.objects:
                group.run()

class Schedule:
    """
    Groups of timers and repetitions. Maybe redundant with class Repeat.

    """
    def __init__(self, schedule=[]):
        self.schedule = schedule
    def run(self):
        for group in self.schedule:
            group.run()

########################
## Function definitions

def wait(duration):
    """
    Wait a certain number of seconds.

    """
    time_end = time() + float(duration) #uncoment for minutes# * 60
    time_diff = time_end - time()
    while time_diff > 0:
        sleep(1)
        time_diff = time_end - time()

def chime():
    print "Ding!"
+3  A: 

This is called duck typing and it's used in Python all the time.

Ignacio Vazquez-Abrams
There are many things I can "guess" from your answer. I could guess that 1) since it's used all the time, it is fine; 2) since you italicize "all the time", you question the pythonic way of doing things; 3) That you don't have an opinion on the subject but want to point out that I'm far from being the only one doing it.The fact is, I don't know your opinion :)I would, however, really enjoy to know it. Is it fine? Are there other, more appropriate way of doing things?Cheers!
Morlock
Correct on points 1 and 3. Polymorphism in Python is duck typing as far as the "primary" methods are concerned. I've done both, and I've found no difference. The only time the distinction matters is when you have to call other methods within the same object in order to make small behavioral changes; in that case I've found polymorphism to be superior, since duck typing would require defining the entire "primary" method all over again.
Ignacio Vazquez-Abrams
@Ignacio Vazquez-Abrams: Thanks a lot for the clarifications!
Morlock
+2  A: 

It is indeed used all the time and is perfectly fine. If you want to be really careful, you can use hasattr to make sure the object you expect to have a run method actually has one fairly early on. That helps makes sure exceptions get thrown as close to the point of error as possible.

But otherwise, it's fine and done frequently.

Omnifarious
+4  A: 

The duck-typing-based approach is fine. If you want to be able to check if a given class is supposed to run in your framework, you can use Abstract Base Classes (needs Python 2.6). PEP 3119 states:

[...] there are many different ways to test whether an object conforms to a particular protocol or not. For example, if asking 'is this object a mutable sequence container?', one can look for a base class of 'list', or one can look for a method named 'getitem'. But note that although these tests may seem obvious, neither of them are correct, as one generates false negatives, and the other false positives.[...] This PEP proposes a particular strategy for organizing these tests known as Abstract Base Classes, or ABC. ABCs are simply Python classes that are added into an object's inheritance tree to signal certain features of that object to an external inspector. Tests are done using isinstance(), and the presence of a particular ABC means that the test has passed.

You can implement an ABC and use isinstance or issubclass to test whether classes or instances are written for your framework:

from abc import ABCMeta, abstractmethod

class Runnable(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def run(self):
        raise NotImplementedError

 class Schedule(Runnable):
     ...

The important point is that it is also possible to register other classes (on which you might have no control, because you did not write them) as runnables, and the isinstance and issubclass methods will reflect that:

  >>> issubclass(SomeOtherClass, Runnable)
  False
  >>> Runnable.register(SomeOtherClass)
  >>> issubclass(SomeOtherClass, Runnable)
  True
Torsten Marek
I didn't know this existed. Thank you.
Omnifarious