2

I tried this:

@contextmanager
def changed_dir(dirname, msg="nothing to do.."):
    if changed(dirname):
        yield
    else:
        print msg
    return

however when I tried to use it:

with changed_dir("foo/bar"):
    print "something in foo/bar was changed"

I was greeted with:

RuntimeError: generator didn't yield

is there any way to get this to work?

update: many people seem to get stuck on the simplicity of the example. Here is a more complex example illustrating the same point

@contextmanager
def changed_dir(dirname, msg="..."):
    cn = db.get_connection(...)
    try:
        cn.execute("insert ...")
        if changed(dirname):
            cn.execute(...)
            yield
            os.mkdirs(os.path.join('backup', dirname))
            # copy the modified tree.. 
            # etc.
        else:
            cn.execute(...)
            print msg
        cn.commit()
    except:
        cn.rollback()
        # cleanup from mkdirs call...
    finally:
        cn.close()

is in-lining the above still the only solution?

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • 1
    Your question title doesn't seem consistent with the question body. I might see what you mean, but you should clarify. – Cyphase Sep 02 '15 at 00:59
  • 1
    What are you trying to accomplish? – Cyphase Sep 02 '15 at 01:04
  • 2
    Why not just use an if statement? – dano Sep 02 '15 at 01:06
  • @Cyphase I'm trying to enter the with block whenever `changed(dirname)` is true, or print a `msg="nothing to do.."` if `changed(dirname)` is false. – thebjorn Sep 02 '15 at 01:07
  • 3
    That sounds remarkably like an `if/else`. – Cyphase Sep 02 '15 at 01:08
  • @dano assume that `print msg` is a certain amount of book-keeping. The `with` statement is regularly used for similar book-keeping tasks, so why not in this case (or to put it another way, why not just use a try/finally instead of with?) – thebjorn Sep 02 '15 at 01:09
  • 2
    @thebjorn, you should really just use an `if/else`. `if cond: do_stuff();; else: bookkeeping()`. – Cyphase Sep 02 '15 at 01:10
  • 1
    @thebjorn But the book keeping only runs if `changed(dirname) == False`, right? The kind of book keeping handled with a `try`/`finally` or `with` is meant to run to ensure a context is cleaned up, no matter how the `try`/`with` block is exited. In this case, there really isn't any context being created; you just don't want to run the block at all unless a condition is met, and if its not met, you want to run some other code. That describes an `if`/`else`, not a `try`/`finally`. – dano Sep 02 '15 at 01:13
  • @dano I was trying to describe a situation where the context requires the context to be finalized even when the user-code shouldn't be run. That still sounds like a context manager to me (and to Guido based on his reasoning in the PEP..) – thebjorn Sep 02 '15 at 01:29
  • 2
    @thebjorn, I think you should show what you're actually trying to do; that way, either we can eat our hats, or we can convince you that you don't need to do this for it. – Cyphase Sep 02 '15 at 01:47
  • The update is closer to what I'm actually trying to do... – thebjorn Sep 02 '15 at 03:46

2 Answers2

1

Guido's comments in PEP-0343 seems to indicate that this use case is copacetic, and based on the example from the pep I came up with (I looked at the link from @Cyphase's answer, but that seems unnecessarily complicated):

class changed_dir(object):
    class NoChange(ValueError):
        pass

    def __init__(self, dirname, msg="nothing to do"):
        self.dirname = dirname
        self.msg = msg

    def __enter__(self):
        if changed(self.dirname):
            return
        else:
            print self.msg
            raise changed_dir.NoChange()

    def __exit__(self, type, value, traceback):
        return isinstance(value, changed_dir.NoChange)

This seems as simple as most contextmanagers (but not as simple as it would be if I could use the decorator..)

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • 1
    This doesn't silently skip the `with` block. – Cyphase Sep 02 '15 at 02:14
  • 1
    No, unfortunately not. It doesn't execute the with block, but the NoChange exception escapes and `__exit__` is never called :-( Is there any simple way to fix it? – thebjorn Sep 02 '15 at 02:23
0

It's possible, but you probably shouldn't do that. Use if/else. In fact, just use the if/else you have in the context manager.

msg = "nothing to do.."

if changed(dirname):
    print "something in foo/bar was changed"
else:
    print msg

Update:

In response to your update, here's how I would do that:

@contextmanager
def db_context_manager():  # Name this better
    cn = db.get_connection(...)
    try:
        cn.execute("insert ...")
        yield
        cn.commit()
    except:
        cn.rollback()
    finally:
        cn.close()

with db_context_manager():
    if changed(dirname):
        do_stuff()
Cyphase
  • 11,502
  • 2
  • 31
  • 32