On 11Oct2015 09:29, Alex Kleider <aklei...@sonic.net> wrote:
On 2015-10-10 18:10, Cameron Simpson wrote:
   However, you'r eusing input(), which unconditionally uses stdin and
   stdout. In that circumstance I'd consider this:
[... temporarily replace stdin and stdout with test data ...]

Yes indeed, and thank you for your input.
Here's where I'm going with your suggestion:
[...]
test_data = 'test_src.txt'

def data_collection_wrapper(collect, source=None):
   """
   """
   if source:

Minor remark: I would write "if src is not None:". In principle the empty string is also "falsey" like None, making your plain "if src:" slightly unreliable. Be precise!

       ostdin = sys.stdin
       ostdout = sys.stdout
       src = open(source, 'r')
       sys.stdin = src
       out = open('/dev/null', 'w')  # Dump the prompts.
       sys.stdout = out

   ret = collect()

   if source:
       src.close()
       out.close()
       sys.stdin = ostdin
       sys.stdout = ostdout

   return ret


def collect_data():
   ret = {}
   ret['first'] = input("Enter your first name: ")
   ret['last'] = input("Enter your last name: ")
   ret['phone'] = input("Your mobile phone #: ")
   return ret

That looks like what I had in mind.

If you expect to do this with several functions you could write a context manager to push new values for stdin and stdout, call a function and restore.

The "contextlib" stdlib module provides a convenient way to write trivial context managers using the "@contextmanager" decorator, which wraps a generator function which does the before/after steps. Have a read. I'd be inclined to write something like this (untested):

 import sys
 from contextlib import contextmanager

 @contextmanager
 def temp_stdinout(src, dst):
   ostdin = sys.stdin
   ostdout = sys.stdout
   sys.stdin = src
   sys.stdout = dst
   yield None
   sys.stdin = ostdin
   sys.stdout = ostdout

and then in your test code:

 with open(source) as src:
   with open('/dev/null', 'w') as dst:
     with temp_stdinout(src, dst):
       ret = collect()

This has several benefits. Primarily, a context manager's "after" code _always_ runs, even if an exception is raise in the inner section. This means that the files are always closed, and the old stdin and stdout always restored. This is very useful.

You'll notice also that an open file is a context manager which can be used with the "with" statement: it always closes the file.

You also asked (off list) what I meant by parameterisation. I mean that some of your difficult stems from "collect_data" unconditionally using stdin and stdout< and that you can make it more flexible by supplying the input and output as paramaters to the function. Example (not finished):

 def collect_data(src, dst):
     ret = {}
     ret['first'] = input("Enter your first name: ")
     ret['last'] = input("Enter your last name: ")
     ret['phone'] = input("Your mobile phone #: ")
     return ret

Now, the input() builtin always uses stdin and stdout, but it is not hard to write your own:

 def prompt_for(prompt, src, dst):
     dst.write(prompt)
     dst.flush()
     return src.readline()

and use it in collect_data:

 def collect_data(src, dst):
     ret = {}
     ret['first'] = prompt_for("Enter your first name: ", src, dst)
     ret['last'] = prompt_for("Enter your last name: ", src, dst)
     ret['phone'] = prompt_for("Your mobile phone #: ", src, dst)
     return ret

You can also make src and dst optional, falling back to stdin and stdout:

 def collect_data(src=None, dst=None):
     if src is None:
         src = sys.stdin
     if dst is None:
         dst = sys.stdout
     ret = {}
     ret['first'] = prompt_for("Enter your first name: ", src, dst)
     ret['last'] = prompt_for("Enter your last name: ", src, dst)
     ret['phone'] = prompt_for("Your mobile phone #: ", src, dst)
     return ret

Personally I would resist that in this case because the last thing you really want in a function is for it to silently latch onto your input/output if you forget to call it with all its arguments/parameters. Default are better for things that do not have side effects.

def main():
   print(collect_data())  # < check that user input works
   # then check that can test can be automated >
   print(data_collection_wrapper(collect_data,
                                   src=test_data))

if __name__ == "__main__":
   main()

Perhaps data_collection_wrapper could be made into a decorator (about
which I am still pretty naive.)

It'll take more studying on my part before I'll be able to implement Ben's suggestion.

Alex
ps I was tempted to change the "Subject:" to remove 'cli' and replace it with 'interactive' but remember many admonitions to not do that so have left it as is.

Best to leave these things as they are unless the topic totally changes. Then I tend to adjust the Subject: to be:

 Subject: very different topic (was: old topic)

presuming that it is still the same discussion.

Cheers,
Cameron Simpson <c...@zip.com.au>
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Reply via email to