3

I have the following code:

import time

class output_letter():

    def output_a(self):
        print('a')
    def output_b(self):
        print('b')
    def output_c(self):
        print('c')
    .............
    def output_z(self):
        print('z')
class wait_time():

    def sleep_2(self):
        time.sleep(2)

out = output_letter()
out.output_a()

I would like to tack the functionality of class wait_time.sleep_2 onto the beginning of class output_letter . As an example if I were to create a hypothetical class "combined" , I could do

c=combined()
c.output_a # would wait 2 seconds and then print 'a'

The problem is that I don't want to rewrite all the functions of 'output_letter', I just want to add the same functionality to all of its functions.

Its not clear to me how to do this , or if this is possible with inheritance or composition.

user1592380
  • 34,265
  • 92
  • 284
  • 515
  • Python supports multiple inheritance so class `combined` can inherit from both classes – Michael Butscher Dec 29 '17 at 02:31
  • class wait_time(output_letter), just use inheritance, Note that u'd better use WaitTime as a class name. – Menglong Li Dec 29 '17 at 02:31
  • I would probably just make a module and import it. That model should implement a decorator that you can use in your original class. The thing, in your example `sleep_2()` method doesn't actually represent any logic about outputting letters and thus shouldn't be mixed. Also this decorator may be handy in many your other classes. – Axalix Dec 29 '17 at 02:34
  • Also this question has already been asked: https://stackoverflow.com/questions/6307761/how-can-i-decorate-all-functions-of-a-class-without-typing-it-over-and-over-for – Axalix Dec 29 '17 at 02:50
  • Yes I think this is heading in the right direction. The key point is that I don't want to rewrite any functions, especially of 'output_letter' – user1592380 Dec 29 '17 at 03:01

5 Answers5

2

I see how you may want to solve 2 separate tasks based on DRY and SOLID principles:

  1. It should be really easy to apply wait_time in future in many other places
  2. Would be cool to keep output_letter as clean and untouchable as possible

So here's my idea:

1. Create a module that will allow you to apply a decorator for all the methods:

def for_all_methods(decorator):
    def decorate(cls):
        for attr in cls.__dict__:
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

2. Isolate wait_time in a module as well:

from time import sleep

class wait_time():
    @staticmethod
    def sleep_decorator(function):
        def wrapper(*args, **kwargs):
            #sleep(2)
            print("sleeping")
            return function(*args, **kwargs)
        return wrapper

3. This is how you can use it

@for_all_methods(wait_time.sleep_decorator)
class output_letter():

    def output_a(self):
        print('a')

    def output_b(self):
        print('b')

    def output_c(self):
        print('c')

    def output_z(self):
        print('z')

So the benefit, we are not actually touching constructors or changing inheritance. All we did, just added a decorator above a class, what is easy to enable or disable when necessary.

If you want to go even further and prevent dealing with an original class directly, you can just create a child combined inherited from the original class and add that decorator signature above its definition.

You can try this DEMO

Axalix
  • 2,831
  • 1
  • 20
  • 37
  • Thanks, this does exactly what I was looking for. I will need to read more on decorators , because I don't understand them very well yet. – user1592380 Dec 29 '17 at 14:05
1

You can either create a class instance of wait_time as an attribute in output_letter or call time.sleep in the method itself:

Option 1:

import time
class wait_time():
   def sleep_2(self):
     time.sleep(2)

class output_letter():
  def __init__(self):
      self.wait = wait_time()
  def output_a(self):
     self.wait.sleep_2()
     print('a')
  def output_b(self):
     self.wait.sleep_2()
     print('b')
  def output_c(self):
     self.wait.sleep_2()
     print('c')

Option 2:

class output_letter():
   def output_a(self):
      time.sleep(2)
      print('a')
    def output_b(self):
      time.sleep(2)
      print('b')
    def output_c(self):
      time.sleep(2)
      print('c')

Edit: regarding your recent comment and edits, you may want to merely create two instances and call each:

a = outout_letter()
t = wait_time()
t.sleep_2()
a.output_a()

Also, it seems that you are trying to output each letter of the output given a method call with the target letter at the end. To shorten your code and implement wait_time as well, you can use __getattr__:

class Output_Letter:
    def __init__(self):
        self.t = wait_time()
    def __getattr__(self, name):
         def wrapper():
             self.t.sleep_2()
             print name.split('_')[-1]
         return wrapper

a = Output_Letter()
a.output_c()

Output:

c
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • After reading your answer I realized I did not explain myself well enough . Can this be done without rewriting all the functions? – user1592380 Dec 29 '17 at 02:47
  • This is better but still requires a rewrite of the functions. – user1592380 Dec 29 '17 at 03:03
  • @user61629 the last option is a shorter, more Pythonic and concise alternative, however, the other edited option does not require a rewrite of the methods since `sleep_2` is called whenever a method is called. – Ajax1234 Dec 29 '17 at 03:05
1

The code below accomplishes what you want. You can set class variables in the combined class to represent output_letter and wait_time. Then inside of the combined class you have access to all attributes and functions of the other classes.

import time
class output_letter():
    def output_a(self):
        print('a')
    def output_b(self):
        print('b')
    def output_c(self):
        print('c')
class wait_time():
    def sleep_2(self):
        time.sleep(2)
class combined():
    ol = output_letter()
    wt = wait_time()
    def output_a(self):
        self.wt.sleep_2()
        self.ol.output_a()

def main():
    c=combined()
    c.output_a()

if __name__ == '__main__':
    main()
mep2664
  • 104
  • 5
1

You can subclass multiple classes. In Python 3:

Given

import time


class Letter():
    "Print a letter."""
    def output_a(self):
        print('a')

    def output_b(self):
        print('b')

    def output_c(self):
        print('c')


class WaitTime():
    """Set a delay."""
    def sleep_2(self):
        time.sleep(2)

Code

class Combined(Letter, WaitTime):
    """Combine methods."""
    def output_a(self):
        super().sleep_2()
        super().output_a()


combined = Combined()
combined.output_a()
# 'a'                                              # delayed output

This allows you to keep your original classes. You simply call them in the new Combined class.


Details

The Combined class mixes other classes together. The super() function makes a call to these superior classes to access their methods according to the class method resolution order:

Combined.__mro__
# (__main__.Combined, __main__.Letter, __main__.WaitTime, object)

In order to work in Python 2, some adjustments are required:

class Letter(object):
    ...

class WaitTime(object):
    ...

class Combined(Letter, WaitTime):

    def output_a(self):
        super(Combined, self).sleep_2()
        super(Combined, self).output_a()

Minor note: for readability, use CamelCase names for classes. Class names are typically nouns; functions are usually verbs.

pylang
  • 40,867
  • 14
  • 129
  • 121
1

None of the classes you've shown in your example are very good OOP design. There's no need for a class when your methods don't refer to any state. if you just want a namespace, usually a module is better.

But that doesn't matter too much if your question is really about how to combine the effects of multiple functions. There are many ways to do that. Functions are first-class objects in Python, so you can put them in lists or dictionaries, or pass them as arguments to other functions if you want. You can also make "function factories", functions that return new functions.

Here's how you might be able to use that to build the delayed letter writing functions you want. I'm storing the function results in a couple of dictionaries, but you could do something different with them if you need to.

import itertools
import time

def letter_writer_factory(letter):
    """Return a function that prints the provided letter when it is called"""
    def func():
        print(letter)
    return func

def delay_factory(seconds):
    """Return a function that when called, waits for the specified time"""
    def delay():
        time.sleep(seconds)
    return delay

def function_sequencer_factory(*functions):
    """Return a function that calls each function argument in turn"""
    def caller():
        for func in functions:
            func()
    return caller

letters = 'abc'
delay_times = [2, 5, 10]
output_funcs = {c: letter_writer_factory(c) for c in letters}
delay_funcs = {t: delay_factory(t) for t in delay_times}
delayed_output_funcs = {(c, t): function_sequencer_factory(df, of)
                        for (c, of), (t, df) in itertools.product(output_funcs.items(),
                                                                  delay_funcs.items())}

#print c after waiting for 10 seconds:
delayed_output_funcs['c', 10]()

This is of course a pretty silly example, but these kinds of functional programming techniques can be used to do some actually useful things in some contexts.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Thank you, your information here gives me a good starting point for improving my understanding of what is possible. – user1592380 Dec 29 '17 at 14:10
  • @Blckknght what do you think in general about your approach in terms of debugging, readability, easy to follow and understand? I saw before this technique many times, but my opinion - it's usually a big pain in really big projects. – Axalix Dec 29 '17 at 17:13
  • 1
    I think it can vary a lot in how easy it is to get your brain wrapped around. Some uses of higher-order functions are easy to follow, like function decorators that are applied using `@foo` syntax. Others are harder. I think any approach can work if you can document it properly. I doubt I'd use this style for the example problem the questioner asked about (I'd just write a single `output_with_delay` function and pass the letter and delay time as arguments), but it might be a good way to implement a solution to some other problem. – Blckknght Dec 29 '17 at 17:26