Re: dict.get(key, default) evaluates default even if key exists
On Mon, Dec 21, 2020 at 12:47 AM <2qdxy4rzwzuui...@potatochowder.com> wrote: > > On 2020-12-19 at 22:29:34 +0100, > Roel Schroeven wrote: > > > What could be useful in some use cases, I think, is a wrapper function > > that evaluates the function lazily: > > > > def dict_get_lazily(d, key, fnc, *args, **kwargs): > > try: > > return d[key] > > except KeyError: > > return fnc(*args, **kwargs) > > > > some_var = dict_get_lazily(d, 'spam', some_function, 31, 11) > > There's no need to pass all those arguments through dict_get_lazily, and > the language already has a way to evaluate an expression lazily: > > def dict_get_lazily(d, key, f): > try: > return d[key] > except KeyError: > return f() > > some_var = dict_get_lazily(d, 'spam', lambda: some_function(31, 11)) > class LazyDict(dict): def __missing__(self, key): # Pretend this is slow print("slow function called") return 31 + 11 d = LazyDict({ 'spam': 0, 'ham': 1, 'parrot': 2, }) print(d['spam']) print(d['eggs']) Guaranteed to be called when and only when the key is missing. This does imply a variant architecture in which the data structure itself is aware of the calculations necessary to generate the data, so it may not be appropriate to all situations, but it's clean, easy, and efficient. If you can't do this, I'd recommend using the try/except method, maybe wrapped up in a function if you want to. In the case where the key is found, the cost is one try block and one lookup - barely more than any other option (for the record, a try block is pretty cheap in CPython, and it's only costly when you hit an exception); and where it isn't found, it's two lookups plus the cost of generating the default (and we've already established that generating the default is expensive, otherwise setdefault would be appropriate). ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-19 at 22:29:34 +0100, Roel Schroeven wrote: > What could be useful in some use cases, I think, is a wrapper function > that evaluates the function lazily: > > def dict_get_lazily(d, key, fnc, *args, **kwargs): > try: > return d[key] > except KeyError: > return fnc(*args, **kwargs) > > some_var = dict_get_lazily(d, 'spam', some_function, 31, 11) There's no need to pass all those arguments through dict_get_lazily, and the language already has a way to evaluate an expression lazily: def dict_get_lazily(d, key, f): try: return d[key] except KeyError: return f() some_var = dict_get_lazily(d, 'spam', lambda: some_function(31, 11)) So, beyond a cache (of which there are many variations and many implementations for many use cases), what are the use cases for an entire dictionary of lazily computed values? And if my application has to know when to access d['key'] directly and when to call dict_get_lazily(d, 'key', f), then whom am I fooling by "hiding" the lazy evaluation? But if I always call dict_get_lazily when I access d, then why do those two or three lines belong in the standard library instead of my application or my private toolbox? -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Ethan Furman schreef op 16/12/2020 om 17:59: Or, if the computationally massively expensive call uses potentially different arguments for each invocation: some_var = d.get('a') or cme(arg1, arg2, ...) I don't like that because it is very fragile: the expression will evaluate to the second part not only when 'a' is not in the dictionary but also whenever the value corresponding to key 'a' is false-y. Example: def slow_function(a, b): # Pretend this is slow, or there is any other reason for this # function to be called only when strictly necessary. print('slow_function called') return a + b d = { 'spam': 0, 'ham': 1, 'parrot': 2, } some_var = d.get('spam') or slow_function(31, 11) print(some_var) Result: slow_function is called, and some_var equals 42 instead of 0. You could make it work reliable by using a unique sentinel value and the walrus operator: SENTINEL = object() some_var = ( r if (r := d.get('spam', SENTINEL)) is not SENTINEL else slow_function(31, 11) ) But I don't like that either because it's way too complicated. What could be useful in some use cases, I think, is a wrapper function that evaluates the function lazily: def dict_get_lazily(d, key, fnc, *args, **kwargs): try: return d[key] except KeyError: return fnc(*args, **kwargs) some_var = dict_get_lazily(d, 'spam', some_function, 31, 11) -- "Honest criticism is hard to take, particularly from a relative, a friend, an acquaintance, or a stranger." -- Franklin P. Jones Roel Schroeven -- https://mail.python.org/mailman/listinfo/python-list
RE: dict.get(key, default) evaluates default even if key exists
Yes. In order to call D.get( ) it needs to pass two arguments. The first is 'a', simple. The second is the result of a call to get_default(). So, that is called. From INSIDE get_default() it prints 'Nobody expects this' but you should expect it, get_default() gets executed. Following that it prints '1', because the default value was NOT USED. If it was used, you would see 'Nobody expects this' followed by 0. --- Joseph S. -Original Message- From: Mark Polesky Sent: Tuesday, December 15, 2020 12:07 PM To: python-list@python.org Subject: dict.get(key, default) evaluates default even if key exists Hi. # Running this script D = {'a':1} def get_default(): print('Nobody expects this') return 0 print(D.get('a', get_default())) # ...generates this output: Nobody expects this 1 ### Since I'm brand new to this community, I thought I'd ask here first... Is this worthy of a bug report? This behavior is definitely unexpected to me, and I accidentally coded an endless loop in a mutual recursion situation because of it. Calling dict.get.__doc__ only gives this short sentence: Return the value for key if key is in the dictionary, else default. Nothing in that docstring suggests that the default value is evaluated even if the key exists, and I can't think of any good reason to do so. Am I missing something? Thanks, Mark -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Il 17/12/2020 13:33, Chris Angelico ha scritto: On Thu, Dec 17, 2020 at 11:16 PM jak wrote: Il 17/12/2020 12:40, Peter J. Holzer ha scritto: On 2020-12-17 12:16:29 +0100, jak wrote: print(_ if d.get('a', None) is not None else get_default()) That doesn't work: print(_ if d.get('a', None) is not None else get_default()) Traceback (most recent call last): File "", line 1, in NameError: name '_' is not defined But this works: print(_ if (_ := d.get('a', None)) is not None else get_default()) 1 (I would prefer ChrisA's solution, though.) hp this one? D['a'] if 'a' in D else get_default() ChrisA This solution search two times same key. Yes, it does, but hash lookups are pretty fast. Unless your key is some sort of custom object with a very expensive __hash__ function, the double lookup isn't going to be too costly. But if that does bother you, you can write it as a try/except instead. ChrisA try/except is a very expensive time. if we have a small dictionary in which you do little research, you definitely have reason. I would be curious to see the differences by taking times with 50 / 60K records by repeating the search 1 or 2 million times with and without try/except and also searching 1 or 2 times too. Cheers -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Il 17/12/2020 12:40, Peter J. Holzer ha scritto: On 2020-12-17 12:16:29 +0100, jak wrote: print(_ if d.get('a', None) is not None else get_default()) That doesn't work: print(_ if d.get('a', None) is not None else get_default()) Traceback (most recent call last): File "", line 1, in NameError: name '_' is not defined But this works: print(_ if (_ := d.get('a', None)) is not None else get_default()) 1 (I would prefer ChrisA's solution, though.) hp I had already corrected my oversight (jak 12:32): > print((_ if d.get('a', None) is not None else get_default())) while your fix works but not correctly because in that way does not collect the return code of the function get_default(). -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Thu, Dec 17, 2020 at 11:16 PM jak wrote: > > Il 17/12/2020 12:40, Peter J. Holzer ha scritto: > > On 2020-12-17 12:16:29 +0100, jak wrote: > >> print(_ if d.get('a', None) is not None else get_default()) > > > > That doesn't work: > > > print(_ if d.get('a', None) is not None else get_default()) > > Traceback (most recent call last): > >File "", line 1, in > > NameError: name '_' is not defined > > > > But this works: > > > print(_ if (_ := d.get('a', None)) is not None else get_default()) > > 1 > > > > (I would prefer ChrisA's solution, though.) > > > > hp > > > this one? > > > > D['a'] if 'a' in D else get_default() > > ChrisA > > > > This solution search two times same key. Yes, it does, but hash lookups are pretty fast. Unless your key is some sort of custom object with a very expensive __hash__ function, the double lookup isn't going to be too costly. But if that does bother you, you can write it as a try/except instead. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Il 17/12/2020 12:40, Peter J. Holzer ha scritto: On 2020-12-17 12:16:29 +0100, jak wrote: print(_ if d.get('a', None) is not None else get_default()) That doesn't work: print(_ if d.get('a', None) is not None else get_default()) Traceback (most recent call last): File "", line 1, in NameError: name '_' is not defined But this works: print(_ if (_ := d.get('a', None)) is not None else get_default()) 1 (I would prefer ChrisA's solution, though.) hp this one? D['a'] if 'a' in D else get_default() ChrisA This solution search two times same key. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-17 12:16:29 +0100, jak wrote: > print(_ if d.get('a', None) is not None else get_default()) That doesn't work: >>> print(_ if d.get('a', None) is not None else get_default()) Traceback (most recent call last): File "", line 1, in NameError: name '_' is not defined But this works: >>> print(_ if (_ := d.get('a', None)) is not None else get_default()) 1 (I would prefer ChrisA's solution, though.) hp -- _ | Peter J. Holzer| Story must make more sense than reality. |_|_) || | | | h...@hjp.at |-- Charles Stross, "Creative writing __/ | http://www.hjp.at/ | challenge!" signature.asc Description: PGP signature -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Il 17/12/2020 12:16, jak ha scritto: Il 15/12/2020 18:07, Mark Polesky ha scritto: Hi. # Running this script D = {'a':1} def get_default(): print('Nobody expects this') return 0 print(D.get('a', get_default())) # ...generates this output: Nobody expects this 1 ### Since I'm brand new to this community, I thought I'd ask here first... Is this worthy of a bug report? This behavior is definitely unexpected to me, and I accidentally coded an endless loop in a mutual recursion situation because of it. Calling dict.get.__doc__ only gives this short sentence: Return the value for key if key is in the dictionary, else default. Nothing in that docstring suggests that the default value is evaluated even if the key exists, and I can't think of any good reason to do so. Am I missing something? Thanks, Mark print(_ if d.get('a', None) is not None else get_default()) ops... print((_ if d.get('a', None) is not None else get_default())) -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Il 15/12/2020 18:07, Mark Polesky ha scritto: Hi. # Running this script D = {'a':1} def get_default(): print('Nobody expects this') return 0 print(D.get('a', get_default())) # ...generates this output: Nobody expects this 1 ### Since I'm brand new to this community, I thought I'd ask here first... Is this worthy of a bug report? This behavior is definitely unexpected to me, and I accidentally coded an endless loop in a mutual recursion situation because of it. Calling dict.get.__doc__ only gives this short sentence: Return the value for key if key is in the dictionary, else default. Nothing in that docstring suggests that the default value is evaluated even if the key exists, and I can't think of any good reason to do so. Am I missing something? Thanks, Mark print(_ if d.get('a', None) is not None else get_default()) -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 17/12/20 8:31 am, Grant Edwards wrote: On 2020-12-16, Dennis Lee Bieber wrote: https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name I vaguely recall some other old language I ran across in a survey course when in college that used it. IIRC it was Algol. Algol 60. Also, Lisp macros effectively let you do something similar. -- Greg -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-16, Dennis Lee Bieber wrote: > On Tue, 15 Dec 2020 20:08:53 + (UTC), Mark Polesky via Python-list > declaimed the following: > >>behavior, and I can't remember any programming language in which it's >>different. > > https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_name Most of us only run across call-by-name when using macro processors (cpp, m4, TeX, various assemblers). I vaguely recall some other old language I ran across in a survey course when in college that used it. IIRC it was Algol. Somebody else already mentioned Haskel. -- Grant -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 12/16/20 3:08 AM, Peter J. Holzer wrote: On 2020-12-15 13:07:25 -0800, Ethan Furman wrote: On 12/15/20 9:07 AM, Mark Polesky via Python-list wrote: D = {'a':1} def get_default(): print('Nobody expects this') return 0 print(D.get('a', get_default())) Python has short-circuiting logical operations, so one way to get the behavior you're looking for is: D.get('a') or get_default() That will call get_default for any falsey value (0, "", an empty list ...), not just for missing values. Which may or may not be what the OP wants. Good point, thanks for clarifying. -- ~Ethan~ -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 12/16/20 1:44 AM, Chris Angelico wrote: On Wed, Dec 16, 2020 at 8:43 PM Loris Bennett wrote: Paul Bryan writes: On Wed, 2020-12-16 at 10:01 +0100, Loris Bennett wrote: OK, I get the point about when the default value is generated and that potentially being surprising, but in the example originally given, the key 'a' exists and has a value of '1', so the default value is not needed. But the function is still called. The get method just doesn't use (or return) the value it generates because the key exists. Nevertheless, you're passing the return value of the get_default function as an argument. Thus, I am still unsurprised when dict.get returns the value of an existing key. As am I. What am I missing? You'll need to tell me at this point. I was just slow and confused, but you helped me get there in the end. I now realise that issue is not about the result of the dict.get(), but rather about the fact that the method which generates the default value is called, whether or not it is needed. I shall remember that next time I think it might be a good idea to define a computationally massively expensive value as a rarely needed default :-) Yep! That's what defaultdict can do - or if you need more flexibility, subclass dict and add a __missing__ method. Or, if the computationally massively expensive call uses potentially different arguments for each invocation: some_var = d.get('a') or cme(arg1, arg2, ...) -- ~Ethan~ -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, Dec 16, 2020 at 10:17 PM Peter J. Holzer wrote: > > On 2020-12-15 13:07:25 -0800, Ethan Furman wrote: > > On 12/15/20 9:07 AM, Mark Polesky via Python-list wrote: > > > > > D = {'a':1} > > > > > > def get_default(): > > > print('Nobody expects this') > > > return 0 > > > > > > print(D.get('a', get_default())) > > > > Python has short-circuiting logical operations, so one way to get the > > behavior you're looking for is: > > > > D.get('a') or get_default() > > That will call get_default for any falsey value (0, "", an empty list ...), > not just for missing values. > > Which may or may not be what the OP wants. > D['a'] if 'a' in D else get_default() ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-15 13:07:25 -0800, Ethan Furman wrote: > On 12/15/20 9:07 AM, Mark Polesky via Python-list wrote: > > > D = {'a':1} > > > > def get_default(): > > print('Nobody expects this') > > return 0 > > > > print(D.get('a', get_default())) > > Python has short-circuiting logical operations, so one way to get the > behavior you're looking for is: > > D.get('a') or get_default() That will call get_default for any falsey value (0, "", an empty list ...), not just for missing values. Which may or may not be what the OP wants. hp -- _ | Peter J. Holzer| Story must make more sense than reality. |_|_) || | | | h...@hjp.at |-- Charles Stross, "Creative writing __/ | http://www.hjp.at/ | challenge!" signature.asc Description: PGP signature -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Paul Bryan writes: > Maybe this will help: > def get(key, default): > ... print("entering get") > ... print(f"{key=} {default=}") > ... print("exiting get") > ... def generate_default(): > ... print("entering generate_default") > ... print("exiting generate_default") > ... return 1 > ... get("a", generate_default()) > entering generate_default > exiting generate_default > entering get > key='a' default=1 > exiting get OK, so it even has nothing to do with dict.get(). It is just about the way function arguments are evaluated. Your example above illustrates nicely that Python does what I would expect, although from the examples others have given, I might have to adjust my expectations when using other languages. Cheers, Loris -- This signature is currently under construction. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, Dec 16, 2020 at 8:43 PM Loris Bennett wrote: > > Paul Bryan writes: > > > On Wed, 2020-12-16 at 10:01 +0100, Loris Bennett wrote: > > > >> OK, I get the point about when the default value is generated and > >> that > >> potentially being surprising, but in the example originally given, > >> the > >> key 'a' exists and has a value of '1', so the default value is not > >> needed. > > > > But the function is still called. The get method just doesn't use (or > > return) the value it generates because the key exists. Nevertheless, > > you're passing the return value of the get_default function as an > > argument. > > > >> Thus, I am still unsurprised when dict.get returns the value of an > >> existing key. > > > > As am I. > > > >> What am I missing? > > > > You'll need to tell me at this point. > > I was just slow and confused, but you helped me get there in the end. I > now realise that issue is not about the result of the dict.get(), but > rather about the fact that the method which generates the default value > is called, whether or not it is needed. > > I shall remember that next time I think it might be a good idea to > define a computationally massively expensive value as a rarely needed > default :-) > Yep! That's what defaultdict can do - or if you need more flexibility, subclass dict and add a __missing__ method. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Paul Bryan writes: > On Wed, 2020-12-16 at 10:01 +0100, Loris Bennett wrote: > >> OK, I get the point about when the default value is generated and >> that >> potentially being surprising, but in the example originally given, >> the >> key 'a' exists and has a value of '1', so the default value is not >> needed. > > But the function is still called. The get method just doesn't use (or > return) the value it generates because the key exists. Nevertheless, > you're passing the return value of the get_default function as an > argument. > >> Thus, I am still unsurprised when dict.get returns the value of an >> existing key. > > As am I. > >> What am I missing? > > You'll need to tell me at this point. I was just slow and confused, but you helped me get there in the end. I now realise that issue is not about the result of the dict.get(), but rather about the fact that the method which generates the default value is called, whether or not it is needed. I shall remember that next time I think it might be a good idea to define a computationally massively expensive value as a rarely needed default :-) Cheers, Loris -- This signature is currently under construction. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Maybe this will help: >>> def get(key, default): ... print("entering get") ... print(f"{key=} {default=}") ... print("exiting get") ... >>> def generate_default(): ... print("entering generate_default") ... print("exiting generate_default") ... return 1 ... >>> get("a", generate_default()) entering generate_default exiting generate_default entering get key='a' default=1 exiting get >>> -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, 2020-12-16 at 10:01 +0100, Loris Bennett wrote: > OK, I get the point about when the default value is generated and > that > potentially being surprising, but in the example originally given, > the > key 'a' exists and has a value of '1', so the default value is not > needed. But the function is still called. The get method just doesn't use (or return) the value it generates because the key exists. Nevertheless, you're passing the return value of the get_default function as an argument. > Thus, I am still unsurprised when dict.get returns the value of an > existing key. As am I. > What am I missing? You'll need to tell me at this point. Paul -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Paul Bryan writes: > On Wed, 2020-12-16 at 08:59 +0100, Loris Bennett wrote: > >> Isn't the second argument to D.get() the value to be return if the >> first >> argument is not a valid key? In that case, why does it make any >> difference here what the second argument of D.get() is since the key >> 'a' >> does exist? >> >> Thus, I would indeed expect the code above to print '1'. I am >> obviously >> missing something here. > > Yes, the second argument is what to return if there is no valid key. > The second argument is evaluated before the call to the get function. > It's return value is being supplied as an argument to the get function. > > Let's write a couple of quick functions to demonstrate... > def get(key, default): > ... print(f"{key=} {default=}") > ... def generate_a_default_value(): > ... return 1 > ... get("a", generate_a_default_value()) > key='a' default=1 > > The generate_a_default_value function was called before the call to > get. It was called so it could produce a value that is actually passed > in as an argument to the get function. OK, I get the point about when the default value is generated and that potentially being surprising, but in the example originally given, the key 'a' exists and has a value of '1', so the default value is not needed. Thus, I am still unsurprised when dict.get returns the value of an existing key. What am I missing? Cheers, Loris -- This signature is currently under construction. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, 2020-12-16 at 08:59 +0100, Loris Bennett wrote: > Isn't the second argument to D.get() the value to be return if the > first > argument is not a valid key? In that case, why does it make any > difference here what the second argument of D.get() is since the key > 'a' > does exist? > > Thus, I would indeed expect the code above to print '1'. I am > obviously > missing something here. Yes, the second argument is what to return if there is no valid key. The second argument is evaluated before the call to the get function. It's return value is being supplied as an argument to the get function. Let's write a couple of quick functions to demonstrate... >>> def get(key, default): ... print(f"{key=} {default=}") ... >>> def generate_a_default_value(): ... return 1 ... >>> get("a", generate_a_default_value()) key='a' default=1 >>> The generate_a_default_value function was called before the call to get. It was called so it could produce a value that is actually passed in as an argument to the get function. Paul -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
Serhiy Storchaka writes: > 15.12.20 19:07, Mark Polesky via Python-list пише: >> # Running this script >> >> D = {'a':1} >> def get_default(): >> print('Nobody expects this') >> return 0 >> print(D.get('a', get_default())) >> >> # ...generates this output: >> >> Nobody expects this >> 1 >> >> ### >> >> Since I'm brand new to this community, I thought I'd ask here first... Is >> this >> worthy of a bug report? This behavior is definitely unexpected to me, and I >> accidentally coded an endless loop in a mutual recursion situation because of >> it. Calling dict.get.__doc__ only gives this short sentence: Return the >> value >> for key if key is in the dictionary, else default. Nothing in that docstring >> suggests that the default value is evaluated even if the key exists, and I >> can't think of any good reason to do so. >> >> Am I missing something? > > You are missed that expressions for function arguments are always > evaluated before passing their values to a function. This is expected > behavior, and I can't remember any programming language in which it's > different. > > So dict.get() returns the value of argument default, which is computed > by calling function get_default() before calling dict.get(). Isn't the second argument to D.get() the value to be return if the first argument is not a valid key? In that case, why does it make any difference here what the second argument of D.get() is since the key 'a' does exist? Thus, I would indeed expect the code above to print '1'. I am obviously missing something here. Cheers, Loris -- This signature is currently under construction. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 15/12/20 15:26, Grant Edwards wrote: > On 2020-12-15, Mark Polesky via Python-list wrote: > >> I see. Perhaps counterintuitive, > I guess that depends on what programming language you normally think > in. Python's handling of function parameters is exactly what I > expected, because all of the previous languages I used did the same > thing. > > Purely out of curiosity, what language had you been using that behaved > otherwise? > R comes to mind; arguments can even be defined as functions of other arguments, and this is evaluated symbolically at call time: """ > f = function(x, y = 2 * x) { y } > f(1) [1] 2 > f(2) [1] 4 > f(y = 3) [1] 3 """ MMR... -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-16 at 12:01:01 +1300, dn via Python-list wrote: > > On Tue, Dec 15, 2020 at 9:57 AM Mark Polesky via Python-list < > > python-list@python.org> wrote: > > > >> Hi. > >> > >> # Running this script > >> > >> D = {'a':1} > >> def get_default(): > >> print('Nobody expects this') > >> return 0 > >> print(D.get('a', get_default())) > >> > >> # ...generates this output: > >> > >> Nobody expects this > >> 1 > >> > >> ### > >> > >> Since I'm brand new to this community, I thought I'd ask here first... Is > >> this worthy of a bug report? This behavior is definitely unexpected to > me, > >> and I accidentally coded an endless loop in a mutual recursion situation > >> because of it. Calling dict.get.__doc__ only gives this short sentence: > >> Return the value for key if key is in the dictionary, else default. > >> Nothing in that docstring suggests that the default value is evaluated > even > >> if the key exists, and I can't think of any good reason to do so. > > > Hey this is fun! A quick search reveals that I've (mea culpa) always thought > of dict.get() as providing a default *value* and not used a function in > there to provide said value. > > That said, the function's name says it all: get_default(). Is it 'getting' a > default value? No, it's (also) reporting-back. Thus a "side-effect". > Undesirable, as you say. > > Were it appropriate to report that a default value was necessary, maybe > something like: > > try: > print( d[ 'a' ] ) > except KeyError: > print( "Nobody expects this" ) > d[ 'a' ] = 0 Be careful: that mutates d in the except clause, whereas calling d.get('a', 0) doesn't, which may or may not be okay. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, Dec 16, 2020 at 10:03 AM dn via Python-list wrote: > > On 16/12/2020 07:52, Dan Stromberg wrote: > ...> BTW, I tend to prefer collections.defaultdict over the two argument > D.get > > or setdefault. > > > Contrarily, dict.get() seems 'better', unless (a) the dict's values are > all to be initialised to the same value, eg all None, int 0, or empty > list []; or (b) my fingers will be tired by the number of dict.get() > calls to be typed. > > This thought partly because it may be some 'distance' between the > definition of the 'dict' as a defaultdict cf the built-in 'original', > and therefore a simple boy (like me) finds it easy to forget or 'lose > track'. > > Also, because it is unusual to come-across a default-factory which > initialises values to the defaultdict other than uniformly. > > Have you seen any application of defaultdict with some more-complex and > cleverly-designed default-factory function? > > Other than personal-preference (which should be respected), and a > uniform default-value, what is the rationale for defaultdict over > dict.get()? It depends on use-case. Using dict.get() is appropriate when you want to know if something's there without prepopulating it; but if your entire purpose is to then stash something into the dict, and you'll ALWAYS be following the same pattern, then it's often more convenient to use __missing__. And if your __missing__ method is, as it commonly will be, constructing a brand new object, then it's easiest to use defaultdict. But a defaultdict for integers might be better written as a Counter instead. There are a lot of similar options, and you take whatever's easiest for this particular job. One VERY common use-case is gathering things together into lists based on some sort of lookup key. This one is perfect for defaultdict: messages = defaultdict(list) for msg in get_messages(): messages[msg["author"]].append(msg) That'll quickly and easily group the messages by their authors. The first time an author is found, a new list is created, and then you append them all. (In digging for examples, I actually found a place where I'd used defaultdict(collections.Counter) to create a nested structure very easily. Worked out well. Pretty unusual thing to want though!) ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 16/12/2020 07:52, Dan Stromberg wrote: ...> BTW, I tend to prefer collections.defaultdict over the two argument D.get or setdefault. Contrarily, dict.get() seems 'better', unless (a) the dict's values are all to be initialised to the same value, eg all None, int 0, or empty list []; or (b) my fingers will be tired by the number of dict.get() calls to be typed. This thought partly because it may be some 'distance' between the definition of the 'dict' as a defaultdict cf the built-in 'original', and therefore a simple boy (like me) finds it easy to forget or 'lose track'. Also, because it is unusual to come-across a default-factory which initialises values to the defaultdict other than uniformly. Have you seen any application of defaultdict with some more-complex and cleverly-designed default-factory function? Other than personal-preference (which should be respected), and a uniform default-value, what is the rationale for defaultdict over dict.get()? -- Regards =dn -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
> On Tue, Dec 15, 2020 at 9:57 AM Mark Polesky via Python-list < > python-list@python.org> wrote: > >> Hi. >> >> # Running this script >> >> D = {'a':1} >> def get_default(): >> print('Nobody expects this') >> return 0 >> print(D.get('a', get_default())) >> >> # ...generates this output: >> >> Nobody expects this >> 1 >> >> ### >> >> Since I'm brand new to this community, I thought I'd ask here first... Is >> this worthy of a bug report? This behavior is definitely unexpected to me, >> and I accidentally coded an endless loop in a mutual recursion situation >> because of it. Calling dict.get.__doc__ only gives this short sentence: >> Return the value for key if key is in the dictionary, else default. >> Nothing in that docstring suggests that the default value is evaluated even >> if the key exists, and I can't think of any good reason to do so. Hey this is fun! A quick search reveals that I've (mea culpa) always thought of dict.get() as providing a default *value* and not used a function in there to provide said value. That said, the function's name says it all: get_default(). Is it 'getting' a default value? No, it's (also) reporting-back. Thus a "side-effect". Undesirable, as you say. Were it appropriate to report that a default value was necessary, maybe something like: try: print( d[ 'a' ] ) except KeyError: print( "Nobody expects this" ) d[ 'a' ] = 0 -- Regards =dn -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Tue, Dec 15, 2020 at 11:05 AM Serhiy Storchaka wrote: > 15.12.20 19:07, Mark Polesky via Python-list пише: > > # Running this script > > > > D = {'a':1} > > def get_default(): > > print('Nobody expects this') > > return 0 > > print(D.get('a', get_default())) > > > > # ...generates this output: > > > > Nobody expects this > > 1 > > > > ### > > > > Since I'm brand new to this community, I thought I'd ask here first... > Is this worthy of a bug report? This behavior is definitely unexpected to > me, and I accidentally coded an endless loop in a mutual recursion > situation because of it. Calling dict.get.__doc__ only gives this short > sentence: Return the value for key if key is in the dictionary, else > default. Nothing in that docstring suggests that the default value is > evaluated even if the key exists, and I can't think of any good reason to > do so. > > > > Am I missing something? > > You are missed that expressions for function arguments are always > evaluated before passing their values to a function. This is expected > behavior, and I can't remember any programming language in which it's > different. > I think Haskell might be different. It's a lazy language. So its '&&' and '||' operators aren't specialcased by the language implementation to be short-circuit - everything is just lazy naturally. It's a neat idea, but it's kind of hard to reason about its performance characteristics. Python has laziness in its 'and' and 'or', as well as in its generators - this gives laziness where you tend to need it the most, without Python's performance becoming hard to reason about (modulo the garbage collector). One of the coolest things about Haskell is if you write a sort function in it, then you can get a "give me the n lowest values" function for free. That's lazy. -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 12/15/20 9:07 AM, Mark Polesky via Python-list wrote: > D = {'a':1} > > def get_default(): > print('Nobody expects this') > return 0 > > print(D.get('a', get_default())) Python has short-circuiting logical operations, so one way to get the behavior you're looking for is: D.get('a') or get_default() -- ~Ethan~ -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On 2020-12-15, Mark Polesky via Python-list wrote: > I see. Perhaps counterintuitive, I guess that depends on what programming language you normally think in. Python's handling of function parameters is exactly what I expected, because all of the previous languages I used did the same thing. Purely out of curiosity, what language had you been using that behaved otherwise? -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
I see. Perhaps counterintuitive, but implemented consistently. Add it to the list of gotchas, I guess. By the way... four helpful responses in under an hour, very impressive. Nice community here. Thanks to all who answered. Mark On Tuesday, December 15, 2020, 11:05:10 AM PST, Serhiy Storchaka wrote: 15.12.20 19:07, Mark Polesky via Python-list пише: > # Running this script > > D = {'a':1} > def get_default(): > print('Nobody expects this') > return 0 > print(D.get('a', get_default())) > > # ...generates this output: > > Nobody expects this > 1 > > ### > > Since I'm brand new to this community, I thought I'd ask here first... Is > this worthy of a bug report? This behavior is definitely unexpected to me, > and I accidentally coded an endless loop in a mutual recursion situation > because of it. Calling dict.get.__doc__ only gives this short sentence: > Return the value for key if key is in the dictionary, else default. Nothing > in that docstring suggests that the default value is evaluated even if the > key exists, and I can't think of any good reason to do so. > > Am I missing something? You are missed that expressions for function arguments are always evaluated before passing their values to a function. This is expected behavior, and I can't remember any programming language in which it's different. So dict.get() returns the value of argument default, which is computed by calling function get_default() before calling dict.get(). -- https://mail.python.org/mailman/listinfo/python-list -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
15.12.20 19:07, Mark Polesky via Python-list пише: > # Running this script > > D = {'a':1} > def get_default(): > print('Nobody expects this') > return 0 > print(D.get('a', get_default())) > > # ...generates this output: > > Nobody expects this > 1 > > ### > > Since I'm brand new to this community, I thought I'd ask here first... Is > this worthy of a bug report? This behavior is definitely unexpected to me, > and I accidentally coded an endless loop in a mutual recursion situation > because of it. Calling dict.get.__doc__ only gives this short sentence: > Return the value for key if key is in the dictionary, else default. Nothing > in that docstring suggests that the default value is evaluated even if the > key exists, and I can't think of any good reason to do so. > > Am I missing something? You are missed that expressions for function arguments are always evaluated before passing their values to a function. This is expected behavior, and I can't remember any programming language in which it's different. So dict.get() returns the value of argument default, which is computed by calling function get_default() before calling dict.get(). -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Wed, Dec 16, 2020 at 4:58 AM Mark Polesky via Python-list wrote: > > Hi. > > # Running this script > > D = {'a':1} > def get_default(): > print('Nobody expects this') > return 0 > print(D.get('a', get_default())) > > # ...generates this output: > > Nobody expects this > 1 > > ### > > Since I'm brand new to this community, I thought I'd ask here first... Is > this worthy of a bug report? This behavior is definitely unexpected to me, > and I accidentally coded an endless loop in a mutual recursion situation > because of it. Calling dict.get.__doc__ only gives this short sentence: > Return the value for key if key is in the dictionary, else default. Nothing > in that docstring suggests that the default value is evaluated even if the > key exists, and I can't think of any good reason to do so. > > Am I missing something? > Seems what you want is the __missing__ method. Look into it - I think it will do what you're expecting here. ChrisA -- https://mail.python.org/mailman/listinfo/python-list
Re: dict.get(key, default) evaluates default even if key exists
On Tue, Dec 15, 2020 at 9:57 AM Mark Polesky via Python-list < python-list@python.org> wrote: > Hi. > > # Running this script > > D = {'a':1} > def get_default(): > print('Nobody expects this') > return 0 > print(D.get('a', get_default())) > > # ...generates this output: > > Nobody expects this > 1 > > ### > > Since I'm brand new to this community, I thought I'd ask here first... Is > this worthy of a bug report? This behavior is definitely unexpected to me, > and I accidentally coded an endless loop in a mutual recursion situation > because of it. Calling dict.get.__doc__ only gives this short sentence: > Return the value for key if key is in the dictionary, else default. > Nothing in that docstring suggests that the default value is evaluated even > if the key exists, and I can't think of any good reason to do so. > Python isn't a lazy language. It's evaluating get_default() unconditionally, prior to executing the D.get(). I don't think it's a Python bug. BTW, I tend to prefer collections.defaultdict over the two argument D.get or setdefault. -- https://mail.python.org/mailman/listinfo/python-list