Discussion:
[Tutor] how to unittest cli input
Alex Kleider
2015-10-11 00:41:17 UTC
Permalink
"""
I'm trying to follow a test driven development paradigm (using
unittest) but can't figure out how to test functions that collect
info from the command line such as the following.
"""
# collect.py
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

def main():
print(collect_data())

if __name__ == "__main__":
main()

The following works:
$ python3 collect.py < cli_input

# cli_input
Alex
Kleider
415/868-1920

... but I don't know how to make it a unittest.

Thanks in advance for any suggestions.

Alex

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Ben Finney
2015-10-11 01:14:52 UTC
Permalink
Post by Alex Kleider
"""
I'm trying to follow a test driven development paradigm (using
unittest) but can't figure out how to test functions that collect
info from the command line such as the following.
"""
# collect.py
ret = {}
ret['first'] = input("Enter your first name: ")
ret['last'] = input("Enter your last name: ")
ret['phone'] = input("Your mobile phone #: ")
return ret
To collect information from the command line, a program interrogates
‘sys.argv’.

The example you show, rather, gets *interactive* input from the user.
That's not command-line, so it's important to get the terminology right.

You can write unit tests for this kind of function by making a “test
double” for the ‘input’ function: an instrumented replacement (a
“double”) that behaves exactly as you determine in the test case.

One common way is to make a collection of data scenarios, and write test
cases that use each scenario to set up the environment, call the
function, and make one assertion about the result.

import builtins
import unittest
import unittest.mock

import testscenarios

from .. import collect

class collect_data_TestCase(
testscenarios.WithScenarios, unittest.TestCase):
""" Test cases for the `collect_data` function. """

scenarios = [
('simple', {
'input_lines': [
"Lorem",
"Ipsum",
"+61 412 345 678",
],
'expected_result': {
'first': "Lorem",
'last': "Ipsum",
'phone': "+61 412 345 678",
},
}),
('empty', {
'input_lines': [""] * 3,
'expected_result': {
'first': "", 'last': "", 'phone': ""},
}),
]

def setUp(self):
""" Set up test fixtures for the test case. """

# Perform setup from all superclasses. This ensures each
# test case will get attributes assigned from the scenario.
super().setUp()

# Makes a ‘patcher’ to patch the ‘builtins.input’
# function, replacing it with an instrumented test double
# (a MagicMock) that will return the next ‘input_lines’
# value each time it is called.
func_patcher = unittest.mock.patch.object(
builtins, "input",
side_effect=self.input_lines)

# Start the patcher (i.e. replace the real function) for
# this test case.
func_patcher.start()

# Register the patcher's ‘stop’ (i.e. restore the original
# function) as a clean-up action for this test case.
self.addCleanup(func_patcher.stop)

def test_returns_expected_result(self):
""" Should return the result expected for the intputs. """
result = collect.collect_data()
self.assertEqual(self.expected_result, result)

The ‘testscenarios’ third-party library is recommended for data-driven
tests <URL:https://pypi.python.org/pypi/testscenarios/>. It will, at
run-time, create a separate test case for each test case function × each
scenario defined for that function's class.

All the produced test cases will run separately, identified by the test
case method and the scenario name; and each one will fail or pass
separately. This makes it very easy to test a matrix of assertions
versus input/output expectations.

The ‘unittest.mock’ library is part of the Python 3 standard library,
and is good for creating powerful test doubles
<URL:http://xunitpatterns.com/Test%20Double.html>.
--
\ “Airports are ugly. Some are very ugly. Some attain a degree of |
`\ ugliness that can only be the result of a special effort.” |
_o__) —Douglas Adams, _The Long Dark Tea-Time of the Soul_, 1988 |
Ben Finney

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/list
Cameron Simpson
2015-10-11 01:10:54 UTC
Permalink
Post by Alex Kleider
I'm trying to follow a test driven development paradigm (using
unittest) but can't figure out how to test functions that collect
info from the command line such as the following.
Aside: I'd say "the standard input" , not "the command line"; to me the latter
connotes to command line arguments fro sys.argv.

Anyway, ...

I would supply test data either as a string in the unit tests or as a file kept
with the source tree eg:

os.path.join(os.path.dirname(__file__), 'test_data.txt')

and then parameterise the input source in you functions. For example:

def collect_data(src=None):
if src is None:
src = sys.stdin

and supply src.

However, you'r eusing input(), which unconditionally uses stdin and stdout. In
that circumstance I'd consider this:

def collect_data(src=None, out=None):
if src is None:
src = sys.stdin
if out is None:
out = sys.stdout
ostdin = sys.stdin
sys.stdin = src
ostdout = sys.stdout
sys.stdout = out
ret = {}
ret['first'] = input("Enter your first name: ")
... etc ...
sys.stdout = ostdout
sys.stdin = ostdin

Note that this is not thread safe because sys.stdin is a global, but it should
work for testing.

Anyway, perhap that gives you some way forward.

Cheers,
Cameron Simpson <***@zip.com.au>
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-11 16:29:56 UTC
Permalink
On 2015-10-10 18:10, Cameron Simpson wrote:

On 10Oct2015 17:41, Alex Kleider <***@sonic.net> wrote:

I'm tOn 2015-10-10 18:10, Cameron Simpson wrote:

On 10Oct2015 17:41, Alex Kleider <***@sonic.net> wrote:

I'm trying to follow a test driven development paradigm (using
unittest) but can't figure out how to test functions that
collect
info from the command line such as the following.


Aside: I'd say "the standard input" , not "the command line"; to me
the latter connotes to command line arguments fro sys.argv.


Point well taken! stdin it is. Ben suggested I should have used the
term "interactive" which certainly fits well.

Anyway, ...

..................

However, you'r eusing input(), which unconditionally uses stdin and
stdout. In that circumstance I'd consider this:

def collect_data(src=None, out=None):
if src is None:
src = sys.stdin
if out is None:
out = sys.stdout
ostdin = sys.stdin
sys.stdin = src
ostdout = sys.stdout
sys.stdout = out
ret = {}
ret['first'] = input("Enter your first name: ")
... etc ...
sys.stdout = ostdout
sys.stdin = ostdin

Note that this is not thread safe because sys.stdin is a global, but
it should work for testing.

Anyway, perhap that gives you some way forward.


Yes indeed, and thank you for your input.
Here's where I'm going with your suggestion:

# collect.py

test_data = 'test_src.txt'

def data_collection_wrapper(collect, source=None):
"""
"""
if source:
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

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.

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Peter Otten
2015-10-11 18:11:14 UTC
Permalink
Post by Alex Kleider
It'll take more studying on my part before I'll be able to implement
Ben's suggestion.
I find Ben's example instructive, but when you're just starting you might
prefer a simpler approach:

import unittest
from unittest import mock

import collect


class TestCollectData(unittest.TestCase):
def test(self):
with mock.patch(
"builtins.input",
side_effect=["foo", "bar", "baz"]):
self.assertEqual(
collect.collect_data(),
dict(first="foo", last="bar", phone="baz"))


if __name__ == "__main__":
unittest.main()


with mock.patch("builtins.input, side_effect=[...]):
...

temporarily replaces the built-in input() with a function that returns the
first item in the side_effect list on the first call, then the second, and
Post by Alex Kleider
def input_many(): # example function; accumulate strings until ""
... result = []
... while True:
... item = input()
... if item == "": break
... result.append(item)
... return result
...
Post by Alex Kleider
input_many()
foo
bar

['foo', 'bar']

Works ;) Now let's change input() to something that returns "one", then
Post by Alex Kleider
import builtins
_input = builtins.input
... builtins.input = lambda items=iter(["one", "two", ""]): next(items)
... input_many()
... finally:
... builtins.input = _input
...
['one', 'two']


_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Cameron Simpson
2015-10-11 21:52:59 UTC
Permalink
Post by Cameron Simpson
However, you'r eusing input(), which unconditionally uses stdin and
[... temporarily replace stdin and stdout with test data ...]
Yes indeed, and thank you for your input.
[...]
test_data = 'test_src.txt'
"""
"""
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!
Post by Cameron Simpson
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()
src.close()
out.close()
sys.stdin = ostdin
sys.stdout = ostdout
return ret
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.
Post by Cameron Simpson
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))
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 <***@zip.com.au>
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-12 17:37:51 UTC
Permalink
Post by Cameron Simpson
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!
'precise' is good!

Any comments about when/if to use 'if src != None:' vs 'if src is not
None:'?
(or 'if src == None:' vs 'if src is None:')
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Ben Finney
2015-10-12 22:17:44 UTC
Permalink
Post by Alex Kleider
Any comments about when/if to use 'if src != None:' vs 'if src is not
None:'?
Express your intention in the code.

If you want to express “is this value the ‘None’ singleton?”, compare
identity with ‘is’/‘is not’. (This is what you almost always mean when
comparing to ‘None’; I know of no useful exceptions to this.)

If you want to express “is this value possibly different from ‘None’ but
compares as equal?”, compare for equality with ‘==’/‘!=’. (As can be
guessed from my description, I don't know of any good reason this would
be preferred for ‘None’.)

The distinction gets to the issue of object identity, as distinct from
value equality. You don't need to know the details of that quite yet.

Just know that you can choose whether to allow an object to declare it
is equal to some other value, without actually being that same value.
That's what you want in most comparisons (use ‘==’), but there is a
large minority of comparisons where you instead want to know whether an
object *is* some other object (use ‘is’).
--
\ “Pinky, are you pondering what I'm pondering?” “Wuh, I think |
`\ so, Brain, but how will we get three pink flamingos into one |
_o__) pair of Capri pants?” —_Pinky and The Brain_ |
Ben Finney

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/
Steven D'Aprano
2015-10-12 23:19:33 UTC
Permalink
Post by Alex Kleider
Any comments about when/if to use 'if src != None:' vs 'if src is not
None:'?
(or 'if src == None:' vs 'if src is None:')
Short answer: always compare to None using `is` or `is not`.

Long answer:

If you want to check for src being specifically None, then use `src is
None`, since None is the only object that can be identical to None.

If you want to check for some random object that merely compares equal
to None (and why would you do that?) then use `src == None`, since that
will give src a chance to decide whether or not it compares equal to
None.

For built-in types (ints, floats, strings, etc.) there's no practical
difference, but as soon as custom objects may be involved, then you
don't know whether you might get some custom object that for reasons of
its own will compare equal to None:

class Falsey:
def __eq__(self, other):
return (not other)


Also, `is` comparisons are faster than `==` comparisons, not that this
will usually matter.
--
Steve
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Danny Yoo
2015-10-13 19:11:43 UTC
Permalink
Post by Alex Kleider
"""
I'm trying to follow a test driven development paradigm (using
unittest) but can't figure out how to test functions that collect
info from the command line such as the following.
"""
# collect.py
ret = {}
ret['first'] = input("Enter your first name: ")
ret['last'] = input("Enter your last name: ")
ret['phone'] = input("Your mobile phone #: ")
return ret
Hi Alex,


If we look at collect_data in a funny way, we might see that it is a
"function" because it's parameterized by the behavior of the
interactive input() function. That means that we can make it a pure
function by explicitly treating "input" as a parameter.

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


Making it an explicit parameter means that, when we call it, we can
feed it input() as the ask()er:

######################
def main():
print(collect_data(input))
######################


It also means that we can pass in an ask()er that is non-interactive,
which is good for unit tests. For example, here's one we can build up
quickly:

######################
def make_ask(f, l, p):
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################


What does this do? It gives us an ask that knows how to respond to
those questions. For example, at the interactive prompt:

##########################################
Post by Alex Kleider
ask = make_ask('jane', 'doe', '123456789')
ask('Enter your first name: ')
'jane'
Post by Alex Kleider
ask('Enter your last name: ')
'doe'
##########################################


And now this is a tool we can use for unit testing the behavior of
collect_data. That is, we can say:

collect_data(make_ask('jane', 'doe', '12345'))

and know that we expect the dictionary value:

{'first': 'jane', ...}


The key is to look at interaction as an explicit parameter of your
function: once it's a parameter, then you can pass in custom
interactions that are controlled by your unit test.
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-13 21:44:51 UTC
Permalink
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This last line got my attention ("a dict has no such attribute"
but rather "it has a 'get' method")
but then the light went on: you've created a function that
returns a function. So many levels of abstraction!

Thanks for making things fall into place.
cheers,
Alex
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Danny Yoo
2015-10-13 23:06:27 UTC
Permalink
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This last line got my attention ("a dict has no such attribute"
but rather "it has a 'get' method")
but then the light went on: you've created a function that
returns a function. So many levels of abstraction!
Yes. Functions are cool. To use of functions as first-class values
is a lot of fun, and you'll get the hang of it once you see the trick
a few times.
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-14 17:49:47 UTC
Permalink
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
ak
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Danny Yoo
2015-10-14 18:29:42 UTC
Permalink
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
Yes, though I try not to use the word "closure" because it's a
technical distinction that isn't really that useful for beginners
because it focuses on the implementation details. That is, when we
say the word "closure", we're emphasizing the fact that it's a
combination of code and the data that it needs to resolve the code's
free variables. And for this discussion, all I really cared about was
that we needed something that knows how to be "called". Anything that
distracts from that point is something I'm actively trying to avoid,
at least at first.


We could try to make the code look clever by doing something like:

################################################
make_ask = lambda f, l, p: (lambda key: {'Enter your first name: ' :
f, 'Enter your last name: ' : l, 'Your mobile phone #: ' : p}[key])
################################################

But this would be death to the point I was trying to make. :P
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-14 19:20:31 UTC
Permalink
Post by Danny Yoo
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
Yes, though I try not to use the word "closure" because it's a
technical distinction that isn't really that useful for beginners
because it focuses on the implementation details. That is, when we
say the word "closure", we're emphasizing the fact that it's a
combination of code and the data that it needs to resolve the code's
free variables. And for this discussion, all I really cared about was
that we needed something that knows how to be "called". Anything that
distracts from that point is something I'm actively trying to avoid,
at least at first.
################################################
f, 'Enter your last name: ' : l, 'Your mobile phone #: ' : p}[key])
################################################
But this would be death to the point I was trying to make. :P
Thank you for 'making the point' and explaining it so well!
ak
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-15 21:22:39 UTC
Permalink
Post by Danny Yoo
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
Yes, though I try not to use the word "closure" because it's a
technical distinction that isn't really that useful for beginners
because it focuses on the implementation details. That is, when we
say the word "closure", we're emphasizing the fact that it's a
combination of code and the data that it needs to resolve the code's
free variables.
I've been pondering the above and am wondering why the use of
"...and the data that it needs to resolve the code's free variables."
rather than simply "...and the data needed by the code to do its job."
I think what I'm asking is what exactly is meant by 'free variables'
or put another way, what makes a variable 'free' or otherwise?

ak
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor

Peter Otten
2015-10-14 19:27:37 UTC
Permalink
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
It does not make big difference, but I would call the return value "bound
method" rather than "closure". For me closure implies access to the local
namespace of the enclosing function, e. g.

def make_ask(f, l, p):
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
def get(key):
return d.get(key)
return get

Here d is looked up when get() is invoked. Let's make a modification to
... d = {'Enter your first name: ' : f,
... 'Enter your last name: ' : l,
... 'Your mobile phone #: ' : p}
... def get(key):
... return d.get(key)
... def set_d(new_d):
... nonlocal d
... d = new_d
... return get, set_d
...
Post by Alex Kleider
Post by Danny Yoo
get, set_d = make_ask(*"abc")
get("Enter your first name: ")
'a'
... def get(self, key): return "won't tell"
...
Post by Alex Kleider
Post by Danny Yoo
set_d(WontTell())
get("Enter your first name: ")
"won't tell"


_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Alex Kleider
2015-10-15 04:56:35 UTC
Permalink
Post by Peter Otten
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
It does not make big difference, but I would call the return value "bound
method" rather than "closure". For me closure implies access to the local
namespace of the enclosing function, e. g.
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get(key)
return get
Here d is looked up when get() is invoked. Let's make a modification to
... d = {'Enter your first name: ' : f,
... 'Enter your last name: ' : l,
... 'Your mobile phone #: ' : p}
... return d.get(key)
... nonlocal d
... d = new_d
... return get, set_d
...
Post by Alex Kleider
Post by Danny Yoo
get, set_d = make_ask(*"abc")
get("Enter your first name: ")
'a'
... def get(self, key): return "won't tell"
...
Post by Alex Kleider
Post by Danny Yoo
set_d(WontTell())
get("Enter your first name: ")
"won't tell"
Thank you, Peter, for your continued efforts to explain.
It is all getting pretty convoluted for my poor brain!
It took a very long time for me to figure out what the
class WontTell was all about.
I probably should follow Danny Yoo's advice and not concern
myself with this but my curiosity is roused.
ak

_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Peter Otten
2015-10-15 07:52:31 UTC
Permalink
Post by Alex Kleider
Post by Peter Otten
Post by Alex Kleider
Post by Danny Yoo
######################
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get
######################
This is an example of a 'closure' is it not?
It does not make big difference, but I would call the return value "bound
method" rather than "closure". For me closure implies access to the local
namespace of the enclosing function, e. g.
d = {'Enter your first name: ' : f,
'Enter your last name: ' : l,
'Your mobile phone #: ' : p}
return d.get(key)
return get
Here d is looked up when get() is invoked. Let's make a modification to
... d = {'Enter your first name: ' : f,
... 'Enter your last name: ' : l,
... 'Your mobile phone #: ' : p}
... return d.get(key)
... nonlocal d
... d = new_d
... return get, set_d
...
Post by Alex Kleider
Post by Danny Yoo
get, set_d = make_ask(*"abc")
get("Enter your first name: ")
'a'
... def get(self, key): return "won't tell"
...
Post by Alex Kleider
Post by Danny Yoo
set_d(WontTell())
get("Enter your first name: ")
"won't tell"
Thank you, Peter, for your continued efforts to explain.
It is all getting pretty convoluted for my poor brain!
It took a very long time for me to figure out what the
class WontTell was all about.
Sorry about that digression. The example would work with any old dict
Post by Alex Kleider
Post by Peter Otten
Post by Alex Kleider
get, set_d = make_ask("John", "Doe", "123")
get("Enter your last name: ")
'Doe'
Post by Alex Kleider
Post by Peter Otten
Post by Alex Kleider
set_d({"foo": "bar"})
get("Enter your last name: ") is None
True
Post by Alex Kleider
Post by Peter Otten
Post by Alex Kleider
get("foo")
'bar'

but that way the effect of rebinding d (which is what happens) is the same
as replacing the data in the dict initially bound to d.
Post by Alex Kleider
I probably should follow Danny Yoo's advice and not concern
myself with this but my curiosity is roused.
_______________________________________________
Tutor maillist - ***@python.org
To unsubscribe or change subscription options:
https://mail.python.org/mailman/listinfo/tutor
Loading...