718

If I have a function like this:

def foo(name, opts={}):
  pass

And I want to add type hints to the parameters, how do I do it? The way I assumed gives me a syntax error:

def foo(name: str, opts={}: dict) -> str:
  pass

The following doesn't throw a syntax error but it doesn't seem like the intuitive way to handle this case:

def foo(name: str, opts: dict={}) -> str:
  pass

I can't find anything in the typing documentation or on a Google search.

Edit: I didn't know how default arguments worked in Python, but for the sake of this question, I will keep the examples above. In general it's much better to do the following:

def foo(name: str, opts: dict=None) -> str:
  if not opts:
    opts={}
  pass
3
  • 7
    The last function is the correct way. It's the same way scala language does it too. Commented Aug 2, 2016 at 18:07
  • 54
    you have a mutable default type - that will lead to problems Commented Aug 2, 2016 at 18:15
  • 3
    @noɥʇʎԀʎzɐɹƆ Not unless you're using it for, e.g. memoization. :P Commented Sep 27, 2018 at 17:05

4 Answers 4

748

Your second way is correct.

def foo(opts: dict = {}):
    pass

print(foo.__annotations__)

this outputs

{'opts': <class 'dict'>}

Although it is not explicitly mentioned in PEP 484, type hints are a specific use of function annotations, as outlined in PEP 3107. The syntax section clearly demonstrates that keyword arguments can be annotated in this manner.

I strongly advise against using mutable keyword arguments. More information here.

8
  • 4
    Wow, I didn't know about the mutable default arguments in Python... especially coming from Javascript/Ruby where default arguments work differently. Not gonna rehash what's already been said ad nauseum around SO about it, I'm just glad I found out about this before it bit me. Thanks!
    – josh
    Commented Aug 2, 2016 at 18:42
  • 55
    I was always advised to use None rather than a mutable type like {} or [] or a default object as mutations to that object without a deep-copy will persist between iterations.
    – MrMesees
    Commented Oct 25, 2018 at 10:35
  • 9
    define enough functions with mutable keyword arguments and it is only a matter of time before you find yourself looking back on a 4 hour debugging session questioning your life choices Commented Jun 10, 2019 at 20:50
  • 8
    Shouldn't there be no whitespace around the = in dict = {} like it is convention for non-type-hinted keyword arguments? Commented Mar 24, 2020 at 9:52
  • 5
    @actual_panda Not according to PEP 8, at least.
    – Ron Wolf
    Commented Jan 9, 2023 at 17:36
122

If you're using typing (introduced in Python 3.5) you can use typing.Optional, where Optional[X] is equivalent to Union[X, None]. It is used to signal that the explicit value of None is allowed . From typing.Optional:

def foo(arg: Optional[int] = None) -> None:
    ...

With Python 3.10 and above, as mentioned in joel's comment, this can equivalently be written as:

def foo(arg: int | None = None) -> None:
    ...
4
  • 5
    Shouldn't there be no whitespace around the = in Optional[int] = None like it is convention for non-type-hinted keyword arguments? Commented Mar 24, 2020 at 9:52
  • 20
    @actual_panda the answer is correct. the style is different when there are type hints. there are examples in PEP 484
    – joel
    Commented Jul 3, 2020 at 11:05
  • 1
    @actual_panda Not according to PEP 8. Also, if you're going to post a comment twice on the same page, you should consider asking it as its own question. As noted in the help page for the comment everywhere privilege, “Comments are not recommended for … Secondary discussion”
    – Ron Wolf
    Commented Jan 9, 2023 at 18:13
  • 6
    or def foo(arg: int | None = None) -> None: in the newer syntax
    – joel
    Commented May 18, 2023 at 13:54
47

I recently saw this one-liner:

def foo(name: str, opts: dict=None) -> str:
    opts = {} if not opts else opts
    pass
7
  • 24
    The empty dict passed as a default parameter is the same dict for every call. So if the function mutates it then the default next time will be the mutated value from last time. Making None the default and then checking inside the method avoids this problem by allocating a new dict each time the method is invoked.
    – Ian Goldby
    Commented Jun 21, 2019 at 7:36
  • 2
    Can you update your answer (without "Edit:", "Update:", or similar)? Comments may disappear at any time. Commented Jul 14, 2020 at 15:26
  • 11
    How about opts = opts or {} Commented Sep 19, 2020 at 15:49
  • 8
    One issue with this -- If opts is a mutable parameter that callers will want to see modified values in, it will fail when {} is passed in. Probably safer to stick with the traditional two-liner if opts is None: opts = {}. If you need a one-liner, I'd prefer opts = {} if opts is None else opts. Commented Jul 22, 2021 at 17:22
  • 1
    @GavinS.Yancey This is true when your function is mutating, but unnecessary otherwise. Given opts or if/then/none` is a particularly disorganized construct, and I prefer explicit, functional behavior over mutation, I find value or default clearer 9/10 times. Commented Oct 26, 2022 at 18:22
-1

Correct method

def foo(name: str, opts: dict = {}) -> str:
  pass

Type hinting syntax

<name>: <type> = <default_value>

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.