Skip to content

Allow default keyword argument in dict.get() method #124675

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
yuriishizawa opened this issue Sep 27, 2024 · 17 comments
Open

Allow default keyword argument in dict.get() method #124675

yuriishizawa opened this issue Sep 27, 2024 · 17 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement

Comments

@yuriishizawa
Copy link

yuriishizawa commented Sep 27, 2024

Feature or enhancement

Proposal:

Background

The dict.get() method is a commonly used feature in Python for retrieving values from dictionaries with a fallback option. Currently, it accepts positional arguments for the key and default value. However, allowing a keyword argument for the default value could enhance code readability and align with Python's philosophy of explicit naming.

Current Behavior

Consider the following code:

my_dict = {"a": 1, "b": 2, "c": 3}
print(my_dict.get("b", "Not found"))

This works as expected. However, when attempting to use a keyword argument:

print(my_dict.get("b", default="Not found"))

It raises a TypeError:

Traceback (most recent call last):
  File "/path/to/script.py", line 3, in <module>
    print(my_dict.get("b", default="Not found"))
TypeError: dict.get() takes no keyword arguments

Proposed Enhancement

Allow the use of the default keyword argument in the dict.get() method. This would make the code more explicit and easier to read, especially in complex expressions or when the default value is a long expression.

Proposed syntax:

my_dict.get("key", default="default_value")

Rationale

  1. Readability: This change aligns with PEP 20 (The Zen of Python), specifically the principle "Explicit is better than implicit." Using a keyword argument makes the intention clearer, especially when the default value is complex or when the method call is part of a larger expression.

  2. Consistency: Many Python built-in functions and methods allow both positional and keyword arguments for optional parameters (e.g., sorted(iterable, key=None, reverse=False)). This change would bring dict.get() in line with this common pattern.

  3. Backwards Compatibility: This change would be backwards compatible, as it only adds functionality without altering existing behavior.

  4. Code Style: It adheres to PEP 8 guidelines for function calls, allowing for more readable code when line length is a concern.

Implementation Considerations

  • The current positional argument for the default value should still be supported for backwards compatibility.
  • The keyword argument should take precedence if both are provided, with a potential deprecation warning for using both simultaneously.

Conclusion

Introducing a default keyword argument to dict.get() would improve code readability and align with Python's design principles. It represents a small but meaningful enhancement to one of Python's most frequently used methods.

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

@yuriishizawa yuriishizawa added the type-feature A feature request or enhancement label Sep 27, 2024
@picnixz
Copy link
Member

picnixz commented Sep 27, 2024

This change aligns with PEP 20 (The Zen of Python), specifically the principle "Explicit is better than implicit." Using a keyword argument makes the intention clearer, especially when the default value is complex or when the method call is part of a larger expression

Yes but there is also "Although practicality beats purity." and "There should be one-- and preferably only one --obvious way to do it.". So, I wouldn't take the Zen as the holy scriptures in this case.

Many Python built-in functions and methods allow both positional and keyword arguments for optional parameters (e.g., sorted(iterable, key=None, reverse=False)). This change would bring dict.get() in line with this common pattern.

Changing dict.get should also warrant a change on next() since this is the same interface for a similar purpose. More generally, I think we need to consider all similar cases instead of changing a specific one (I don't know if there are more).

sorted is a bit different than dict.get because it has two optional arguments, so if one only needs reverse but not key, it is better to only supply reverse without explicitly passing None to key. Other examples that you didn't cite are max and min but this is only because they accept a variadic number of arguments.

This change would be backwards compatible, as it only adds functionality without altering existing behavior.

It would not change the behaviour but it could affect performances. Adding default would slightly slow down dict.get since we would need to also parse the keyword. But this must be benchmarked (that's just my gut feeling).

The keyword argument should take precedence if both are provided, with a potential deprecation warning for using both simultaneously.

Using both simulteneously should be rejected. There shouldn't be any warning IMO.

Code Style: It adheres to PEP 8 guidelines for function calls, allowing for more readable code when line length is a concern.

This actually increases the line length if you use the keyword argument.


Personally, I'm -1 on this one. While it is great to improve readability, knowing that the second argument of dict.get() or next() is for a default value should be something that we do not need to remind directly (IMO, this would not add much value if you're a bit familiar with Python).

This is a minor feature, which does not need previous discussion elsewhere

I don't feel it is a minor feature engouh and I feel we should ask a wider community on Discourse: https://discuss.python.org/c/ideas/6. I will leave the issue open for a few days but I think this one should be closed until a consensus has been reached both among core devs and the community (before posting on Discourse, please check if there aren't existing topics on this matter).

@picnixz picnixz added the pending The issue will be closed if no feedback is provided label Sep 27, 2024
@rruuaanng
Copy link
Contributor

rruuaanng commented Sep 28, 2024

I agree with @picnixz viewpoint. Personally, I believe that methods of built-in types should not rely too heavily on keyword arguments; they should be as simple and intuitive as possible, rather than pursuing readability at the expense of practicality (frankly, I don’t want to pass a bunch of parameters when using methods of built-in types, it would make me feel tired).
Moreover, even if keyword arguments are not supported, it shouldn’t affect its usage, right?

@rruuaanng
Copy link
Contributor

rruuaanng commented Sep 28, 2024

especially in complex expressions or when the default value is a long expression

Regarding the complex expressions you mentioned, I have written a relatively complex one that uses a lot of f-strings (personally, I think it might not get much more complex than this)

dict.get(
    f"{ip.a}.{ip.b}.{ip.c}.{ip.d}",
    f"{ip.a}.{ip.b}.{ip.c}.{ip.d} not found"
    )

It seems that this doesn’t make the code difficult to read and also keeps it neat.

@ZeroIntensity
Copy link
Member

Hi @yuriishizawa! Please don't use ChatGPT or other LLMs to generate feature proposals -- they generally form proposals that are hard to follow and make irrelevant points. With that being said, I've needed this exact feature before, but we don't need a new keyword argument; dict.get("whatever") or default_value works just fine.

If you really do feel that this is something that's needed, bring it up on DPO. Thanks!

@ZeroIntensity ZeroIntensity closed this as not planned Won't fix, can't repro, duplicate, stale Sep 29, 2024
@ZeroIntensity ZeroIntensity removed the pending The issue will be closed if no feedback is provided label Sep 29, 2024
@joooeey
Copy link

joooeey commented Oct 24, 2024

I'd like to throw in another argument for implementing this feature: The current implementation is contrary to the Python documentation which says:

get(key, default=None)
Return the value for key if key is in the dictionary, else default. If default is not given, it defaults to None, so that this method never raises a [KeyError](https://docs.python.org/3/library/exceptions.html#KeyError).

This syntax and wording implies that both key and default can be passed as keyword arguments.

On the other hand, the CPython built-in help correctly includes a slash at the end, implying that no keyword arguments are accepted:

help(dict.get)
Help on method_descriptor:

get(self, key, default=None, /) unbound builtins.dict method
    Return the value for key if key is in the dictionary, else default.

It might make sense to update the Python documentation to match the actual behavior, especially if there is consensus among major Python implementations like CPython, PyPy, IronPython, etc. to not allow default as a keyword argument.

@ZeroIntensity
Copy link
Member

This syntax and wording implies that both key and default can be passed as keyword arguments.

I don't think so. There's nothing there that mentions anything about whether it can be passed as a kwarg or not.

Again, there's enough contention here that this won't fly on GitHub--propose it on DPO, and see if it gets any additional community support, then we can reconsider this.

@joooeey
Copy link

joooeey commented Oct 28, 2024

I don't think so. There's nothing there that mentions anything about whether it can be passed as a kwarg or not.

The lack of a slash in the function signature implies that all parameters can be passed as arguments or keyword arguments.

Hence, the way the Python language is specified in its documentation, dict.get accepts its parameters by name. PyPy, which advertises itself as "a fast, compliant alternative implementation of Python" (emphasis added) implements this feature correctly. Check it out in the online interpreter! CPython on the other hand doesn't fully implement the spec on python.org and incorrectly raises an error when parameters are passed to dict.get by name (see online interpreter).

propose it on DPO, and see if it gets any additional community support, then we can reconsider this.

What's DPO? This Github repo is the official issue tracker for CPython as per the docs.

@ZeroIntensity
Copy link
Member

ZeroIntensity commented Oct 28, 2024

DPO refers to discuss.python.org. Should have clarified, sorry!

All feature discussion is supposed to happen elsewhere, the issue tracker is used for tracking bugs and features that have already been accepted.

@joooeey
Copy link

joooeey commented Oct 28, 2024

All feature discussion is supposed to happen elsewhere, the issue tracker is used for tracking bugs and features that have already been accepted.

This is basically a matter of fully reflecting PEP 457 and PEP 570 in the documentation. I bet the same issue affects many more functions and methods of the standard library. dict.get would only be one example of many. I guess a discussion on DPO on this larger issue would be helpful. In any case, I do not have the time for this discussion.

I do think that ambiguities like this one are in fact serious, as they inhibit the portability of Python Code between CPython, PyPy, IronPython and other implementations.

@ZeroIntensity
Copy link
Member

If there's a docs issue, then that's fixable.

@joooeey
Copy link

joooeey commented Nov 4, 2024

If there's a docs issue, then that's fixable.

Even changing the docs requires a discussion for two reasons:

  1. As far as I can see, for many standard library functions there's ambiguity which parameters can be passed by name. This doesn't only affect dict.get. This issue is discussed in PEP 457 but no specific plan for addressing the documentation issue is proposed there. Hence any effort to change the docs should encompass most of the standard library, not just this one method.
  2. This ambiguity in the docs on python.org has caused divergence between different implementations of Python as I've outlined above (e.g. PyPy generally allows passing parameters by name). How to deal with that when updating the docs is not trivial.
  3. The two reasons above are also arguments for changing the CPython implementation instead of just updating the docs.

@adamtheturtle
Copy link
Contributor

I have created a PR for the docs difference at #128207.

Mentioned in that PR is the fact that not accepting keyword arguments means that dict does not implement collections.abc.Mapping which does allow keyword arguments:

def get(self, key, default=None):

As far as I can see, for many standard library functions there's ambiguity which parameters can be passed by name. This doesn't only affect dict.get. This issue is discussed in PEP 457 but no specific plan for addressing the documentation issue is proposed there. Hence any effort to change the docs should encompass most of the standard library, not just this one method.

The documentation is already inconsistent with regards to this - that is, some function definitions have / and some do not when they could. I think that doing one function at a time is not terrible.

This ambiguity in the docs on python.org has caused divergence between different implementation of Python as I've outlined above (e.g. PyPy generally allows passing parameters by name). How to deal with that when updating the docs is not trivial.

Are you suggesting that maybe the docs.python.org documentation should mention the differences between CPython and PyPy here?

The two reasons above are also arguments for changing the CPython implementation instead of just updating the docs.

I think that the documentation should be updated and then if the implementation is changed, the documentation should be updated again to reflect reality.

@ZeroIntensity
Copy link
Member

I have since changed my mind, after reading this from the PR (gh-128207):

Relatedly, this means that dict does not implement collections.abc.Mapping which does allow keyword arguments:

A dictionary is the de-facto Mapping, it's probably not great to have inconsistencies like this.

cc @JelleZijlstra

@ZeroIntensity ZeroIntensity reopened this Dec 24, 2024
@picnixz
Copy link
Member

picnixz commented Dec 24, 2024

I think the issue is more about the fact that Mapping allows for a keyword argument rather than dict not allowing a keyword argument. In addition, there is still this interrogation:

Adding default would slightly slow down dict.get since we would need to also parse the keyword. But this must be benchmarked (that's just my gut feeling).

I don't know whether it will be faster or slower, but I'd advise checking performances first (even if there is a (small but not insurmountable) inconsistency).

@picnixz picnixz added the interpreter-core (Objects, Python, Grammar, and Parser dirs) label Dec 24, 2024
@JelleZijlstra
Copy link
Member

I'd support allowing default= as a keyword argument; it seems useful. We should benchmark it to make sure it doesn't slow things down though.

@joooeey
Copy link

joooeey commented Jan 7, 2025

Are you suggesting that maybe the docs.python.org documentation should mention the differences between CPython and PyPy here?

Yes. This is either CPython implementation detail or a longstanding bug in CPython, so it should be documented as such. I view docs.python.org as the definition of an abstract language called Python. I don't see a reason why Python (this ideal abstract language) should suddenly stop accepting keyword arguments in dict.get. But that is exactly what your proposed PR implies.

I think that doing one function at a time is not terrible.

Doing one function at a time risks adding even more inconsistencies, as it is not obvious what the proper course of action should be.

@joooeey
Copy link

joooeey commented Jan 7, 2025

I'd support allowing default= as a keyword argument; it seems useful. We should benchmark it to make sure it doesn't slow things down though.

Agree. But what about key=? That one doesn't seem very useful. PyPy supports passing key= by keyword too. So does CPython's collectons.abc.Mapping. So if only default= becomes a positional-and-keyword argument, there would still remain a docs issue to resolve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

7 participants