Question

I am not sure if this is 'good python practice', but would it be possible to, say, define a custom File-object that could do something like:

myfile = myopen('myfile.txt')
with myfile:
    write('Hello World!') #notice we don't put "myfile.write(..)" here!

i.e the File-context creates a function "write()" so that we don't have to type myfile.write(..) etc. It saves typing and makes the purpose clearer in some cases. For instance:

myboard = ChessBoard()
with ChessBoard():
    make_move("e4")
    make_move("e5")
    give_up()

as opposed to

myboard = ChessBoard()
with ChessBoard():
    make_move("e4",board=myboard)
    make_move("e5",board=myboard)
    give_up(board=myboard)

The question is: should I do this? and HOW can I do it? I am guessing I would have to modify the globals()-dict somehow, but that seems like a bad idea..

EDIT: Ok thanks! I got multiple good answers advising me not to do this. So I won't do it :o)

Was it helpful?

Solution

This is not what context managers are for and, as has been remarked, I beats the "explicit is better than implicit" principle. The only way to make it work would have to work around Python's compositional semantics, which are one of its strong points. What you can do to save typing, if there's only a single method to be called multiple times, is:

move = ChessBoard().make_move
move("e4")
move("e5")

Or with multiple such methods:

board = ChessBoard()
move = board.make_move
give_up = board.give_up
# call methods

(In FP terms, this is actually partial application, not currying.)

OTHER TIPS

First off, this is not a good idea at all. Explicit is better than implicit -- by explicitly mentioning the board, the reader (which may be you in a few weeks!) can instantly tell which board is being operated on. Also, there is no need. You can have more convenient syntax in other ways: For example, make the functions methods of the individual objects. And drop the pointless context manager (what's it supposed to do? You already create an object beforehand!).

To do this, you'd need global state, but not globals specifically. Say, a global (module-level to be exact) stack of objects, the context manager pushes and pops the object, and the individual functions look at the top of that stack. It actually wouldn't be too hard to implement if you know your stuff, but as I said before, there is no reason to.

You'd think you'd need to modify globals(). But if you defined the context manager in a different module from the one you're using it in, you'd be using the globals of the module where it was defined. Not the module where it was being used. So you'd need to define the methods in the __builtin__ namespace (where the built-in functions are). This can certainly be done, but it strikes me as an even worse idea, especially if you're wanting to use it with arbitrary objects. Maybe if you mangled the name in some way (e.g., adding a leading underscore). But even then, what if you nested the with statements?

You're looking for Python's with statement to be like Pascal's or Visual Basic's, and it's just not the same thing at all. That said, a Python equivalent to the Pascal/VB with statement would be a lovely thing to have; it just couldn't be called with.

One (at least half-crazy) way of doing that would be something like:

@contextmanager
def export(obj, *atts):
    yield [getattr(obj, a) for a in atts]

and then:

class X:
   def foo...
   def bar...

with export(some_x, 'foo', 'bar') as (foo, bar):
      foo() <-- some_x.foo
      bar() <-- some_x.bar

Of course, like any other similar solution this makes zero sense in real life.

As others have mentioned, this is probably a very bad idea, but I do see some limited cases (perhaps as a make-shift DSL) where it might be workable.

class ChessBoard(object):
    def __init__(self, filename):
        self.filename = filename
    def __enter__(self):
        self.handle = open(self.filename, 'w')
        globals()['move'] = self.move
        globals()['give_up'] = self.give_up
    def __exit__(self, type, value, traceback):
        self.handle.close()
    def move(self, move):
        self.handle.write(move + '\n')
    def give_up(self):
        self.handle.write('resign' + '\n')

with ChessBoard('chess.txt'):
    move('e4')
    move('e5')
    give_up()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top