0

I have a function f that returns two parameters. After this function, I use the first parameter. I need the second one to glue things together with another function g. So my code would look like:

a, b = f()
# do stuff with a
g(b)

This is very repetitive, so I figured I could use something like a RAII approach. But, since I don't know when objects will die and my main goal is to get rid of the repetitive code, I used the with statement:

with F() as a:
    # do stuff with a

That's it. I basically created an object F around this function, supplying __enter__ and __exit__ functions (and obviously __init__ function).

However, I still wonder whether this is the right "Pythonic" way to handle this, since a with statement was meant to handle exceptions. Especially this __exit__ function has three arguments, which I don't use at the moment.

Edit (further explanations): Whenever i call f() i NEED to do something with b. It does not matter what happened in between. I expressed it as g(b). And exactly this i hide away in the with statement. So the programmer doesn't have to type g(b) again and again after every call of f(), which might get very messy and confusing, since #do stuff with a might get lengthy.

hr0m
  • 2,643
  • 5
  • 28
  • 39
  • *Is* it very repetitive? It's not clear what the problem is, or why you thought a context manager was the best solution. If you're using that code a lot, make it a function with parameters `a` and `b`. If the bit in the middle changes, so you think you can't extract it, then add a new parameter that's a function to call with `a` before calling `g` (note that functions are first class in Python). Could you give a less abstract example of what you're trying to achieve? What does `g` *do*? – jonrsharpe Jun 20 '16 at 22:21
  • 1
    Can you explain how it is repetitive exactly? Also, how you are currently using `b` in your with statement? Personally, I prefer using `with` for things that look like resources. The other alternative is using decorators. – UltraInstinct Jun 20 '16 at 22:23
  • What exactly is `g`? Should it still happen if an exception is thrown? If it shouldn't happen, should some other sort of cleanup happen instead? – user2357112 Jun 20 '16 at 22:27
  • I extended my explanations. `g` is necessary, no matter what. Yet currently i have no exceptions AT ALL. – hr0m Jun 20 '16 at 22:31
  • 2
    Well, you're still not explaining what these `f` and `g` are, but from what information you've given us, that sounds like a fairly standard case for a context manager. – user2357112 Jun 20 '16 at 22:35
  • @user2357112 That's what i wanted to hear :) `f` prepare stuff, so i can do things with a, which it returns. `g` cleans up after things were done. Yes that should be a context manager. I just wanted to make sure, since i am still new to python and sick of solutions which somewhat work, but there are better/cleaner/readable ways of doing it in python – hr0m Jun 20 '16 at 22:39

2 Answers2

1

For someone else reading your code (or you in 6 months time), a context manager probably will imply the creation/initialisation and closure/deletion of a resource, and I would be very wary of re-purposing the idiom.

You could use composition, rather than a context manager:

def func(a, b):
    # do stuff with a & optionally b

def new_f(do_stuff):
    a, b = f()
    do_stuff(a, b)
    g(b)


new_f(func)
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Tony Suffolk 66
  • 9,358
  • 3
  • 30
  • 33
  • This is nice but sadly not an option, since i need to be way more general. See it as complicated `do_stuff` which cannot be handled with just one fucntion – hr0m Jun 20 '16 at 22:33
  • you can always pull multiple calls into a single function surely. Well named and well documented functions are better than large blocks of code. – Tony Suffolk 66 Jun 21 '16 at 16:04
1

A slightly more pythonic way of doing it would be to use the contextlib module. You can do away with rolling out your own class with __enter__ and __exit__ functions.

from contextlib import contextmanager

def g(x):
    print "printing g(..):", x

def f():
    return "CORE", "extra"

@contextmanager
def wrap():
    a, b = f()
    try:
        yield a
    except Exception as e:
        print "Exception occurred", e
    finally:
        g(b)
if __name__ == '__main__':
    with wrap() as x:
        print "printing f(..):", x
        raise Exception()

Output:

$ python temp.py
printing f(..): CORE
Exception occurred
printing g(..): extra
UltraInstinct
  • 43,308
  • 12
  • 81
  • 104
  • I think you stuck the `@contextmanager` decorator on the wrong function. – user2357112 Jun 20 '16 at 22:48
  • yep, that's what i wanted. But if i (in future) need to handle exceptions, i need my own fancy class with exit and entry function, right? – hr0m Jun 20 '16 at 22:51
  • Nope. Check my update above. You can surround yield with try-except-finally blocks too :) – UltraInstinct Jun 20 '16 at 22:55
  • Although it makes the code read nice - it is very prone to be misunderstood, since you aren't really creating and then closing a resource. Not wure I would go that way tbh. – Tony Suffolk 66 Jun 21 '16 at 16:29