On Wed, 27 Mar 2013 02:34:09 -0700, Michael Herrmann wrote: > On Tuesday, March 26, 2013 11:37:23 PM UTC+1, Steven D'Aprano wrote: >> >> Global *variables* are bad, not global functions. You have one global >> variable, "the current window". So long as your API makes it obvious >> when the current window changes, implicitly operating on the current >> window is no more dangerous than Python's implicit operations on the >> current namespace (e.g. "x = 2" binds 2 to x in the current namespace). > > I'm generally wary of everything global, but you're right as long as no > (global) state is involved.
That comment surprises me. Your preferred API: switch_to(notepad) write("Hello World!") press(CTRL + 'a', CTRL + 'c') uses implied global state (the current window). Even if you avoid the use of an actual global for (say) an instance attribute, it's still semantically a global. Surely you realise that? Not trying to be argumentative, I'm just surprised at your comment. >> I recommend you look at the random.py API. You have a Random class, >> that allows the user to generate as many independent random number >> generators as needed. And the module also initialises a private >> instance, and exposes the methods of that instance as top-level >> functions, to cover the 90% simple case where your application only >> cares about a single RNG. > > I looked it up - I think this is a very good approach; to provide easy > access to the functionality used in 90% of cases but still give users > the flexibility to cover the edge cases. > > After everybody's input, I think Design #2 or Design #4 would be the > best fit for us: > > Design #2: > notepad_1 = start("Notepad") > notepad_2 = start("Notepad") > switch_to(notepad_1) > write("Hello World!") > press(CTRL + 'a', CTRL + 'c') > switch_to(notepad_2) > press(CTRL + 'v') This is nice syntax for trivial cases and beginners whose needs are not demanding, but annoying for experts who have more complicated requirements. If this is the only API, experts who need to simultaneously operate in two windows will be forced to write unproductive boilerplate code that does nothing but jump from window to window. Well what do you know, even in the simple case above, you have unproductive code that does nothing but jump from window to window :-) I'm not against this API, I'm just against it as the *only* API. > Design #4: > notepad_1 = start("Notepad") > notepad_2 = start("Notepad") > notepad_1.activate() > write("Hello World!") > press(CTRL + 'a', CTRL + 'c') > notepad_2.activate() > press(CTRL + 'v') This is actually no different from #2 above, except that it uses method call syntax while #2 uses function call syntax. So it has the same limitations as above: it's simple for simple uses, but annoying for complex use. Neither API supports advanced users with complicated needs. A hybrid approach, where you have function call syntax that operates on the implicit current window, plus method call syntax that operates on any window, strikes me as the best of both worlds. With a little forethought in your implementation, you don't have to duplicate code. E.g. something like this: class WindowOps: def __init__(self, theWindow=None): self.theWindow = None def press(self, c): win = self.getWindow() send_keypress_to(win) def getWindow(self): if self.theWindow is None: return gTheTopWindow return self.theWindow _implicit = WindowOps(None) press = _implicit.press # etc. del _implicit This gives you the best of both worlds, for free: a simple API using an implicit top window for simple cases, and a slightly more complex API with an explicit window for advanced users. > Normally, I'd go for Design #4, as it results in one less global, I don't see how this is possible. Both APIs use an implicit "top window". What's the one less global you are referring to? > is > better for autocompletion etc. The thing with our library is that it > tries to make its scripts as similar as possible to giving instructions > to someone looking over their shoulder at a screen. And in this > situation you would just say > > activate(notepad) > > rather than > > notepad.activate(). Depends like Yoda they talk whether or not. Unless you go all the way to writing your own parser that accepts English- like syntax, like Hypertalk: select notepad type hello world I don't think it makes that much difference. Function call syntax is not exactly English-like either. We don't generally speak like this: write bracket quote hello world quote close bracket My personal feeling is that people aren't going to be *too* confused by method call syntax, especially not if they've seen or been introduced to any programming at all. You say tom-a-to, I say tom-ar-to. But I think it is useful to distinguish between the "basic API" using function call syntax and an implied current window, and an "advanced API" using method call syntax with an explicit window: # Basic API is pure function calls, using an implicit window switch_to(notepad) write("Hello World!") press(CTRL + 'a', CTRL + 'c') switch_to(calculator) write('2+3=') press(CTRL + 'a') switch_to(notepad) press(CTRL + 'v') # Advanced API uses an explicit window and method calls: notepad.write("Hello World!") notepad.press(CTRL + 'a', CTRL + 'c') calculator.write('2+3=') calculator.press(CTRL + 'a') notepad.press(CTRL + 'v') # Of course you can mix usage: switch_to(notepad) write("Hello World!") press(CTRL + 'a', CTRL + 'c') calculator.write('2+3=') calculator.press(CTRL + 'a') press(CTRL + 'v') You could avoid method call syntax altogether by giving your functions an optional argument that points to the window to operate on: write("Hello World!") write("Hello World!", notepad) but the difference is mere syntax. There's little or no additional complexity of implementation to allow the user to optionally specify an explicit window. I expect you have something like this: def write(string): window = gTheCurrentWindow # use a global send characters of string to window # magic goes here This can trivially be changed to: def write(string, window=None): if window is None: window = gTheCurrentWindow send characters of string to window # magic goes here See, for example, the decimal module. Most operations take an optional "context" argument that specifies the number of decimal places, rounding mode, etc. If not supplied, the global "current context" is used. This gives the simplicity and convenience of a global, without the disadvantages. (Recent versions of decimal have also added "with context" syntax, for even more power.) -- Steven -- http://mail.python.org/mailman/listinfo/python-list