722

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

754

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.

Sign up to request clarification or add additional context in comments.

8 Comments

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!
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.
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
Shouldn't there be no whitespace around the = in dict = {} like it is convention for non-type-hinted keyword arguments?
@actual_panda Not according to PEP 8, at least.
|
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 Comments

Shouldn't there be no whitespace around the = in Optional[int] = None like it is convention for non-type-hinted keyword arguments?
@actual_panda the answer is correct. the style is different when there are type hints. there are examples in PEP 484
@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”
or def foo(arg: int | None = None) -> None: in the newer syntax
47

I recently saw this one-liner:

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

7 Comments

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.
Can you update your answer (without "Edit:", "Update:", or similar)? Comments may disappear at any time.
How about opts = opts or {}
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.
@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.
|
-1

Correct method

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

Type hinting syntax

<name>: <type> = <default_value>

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.