Last updated: Apr 8, 2024
Reading time·5 min
The Python "SyntaxError: non-default argument follows default argument" occurs when you define a function with a positional parameter that follows a default parameter.
To solve the error, make sure to specify all default parameters after the positional parameters of the function.
Here is an example of how the error occurs.
# ⛔️ SyntaxError: non-default argument follows default argument def get_employee(first, last='Doe', salary): return {'first': first, 'last': last, 'salary': salary}
The salary
positional parameter follows the last
default parameter which
causes the error.
To solve the error, make sure to specify the default parameters of the function after the positional ones.
def get_employee(first, salary, last='Doe'): return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100) print(emp_1) # {'first': 'James', 'last': 'Doe', 'salary': 100} emp_2 = get_employee('Alice', 100, 'Smith') print(emp_2) # {'first': 'Alice', 'last': 'Smith', 'salary': 100}
We moved the last
default parameter after the salary
positional one which
solved the error.
# ⛔️ Incorrect syntax def get_employee(first, last='Doe', salary): # ✅ Correct syntax (default parameter after positional) def get_employee(first, salary, last='Doe'):
Default parameter values have the form of parameter = expression
.
When we declare a function with one or more default parameter values, the corresponding arguments can be omitted when the function is invoked.
def example(first, last='Doe'): return first + ' ' + last print(example('James')) # 👉️ James Doe print(example('James', 'Smith')) # 👉️ James Smith
If a value for the default parameter is not provided, the default value is used.
Positional parameters cannot follow a parameter with a default value.
Otherwise, Python has no way of knowing if we passed an argument for the default parameter or for a positional one.
Here is an example that uses multiple default parameters.
def example(first, last='Doe', salary=100): return {'first': first, 'last': last, 'salary': salary} # 👇️ {'first': 'James', 'last': 'Doe', 'salary': 100} print(example('James')) # 👇️ {'first': 'James', 'last': 'Smith', 'salary': 100} print(example('James', 'Smith')) # 👇️ {'first': 'James', 'last': 'Smith', 'salary': 50} print(example('James', 'Smith', 50))
The function can be called with one, two or three arguments.
If you want to use the default value for the last
parameter but need to
specify a default value for salary
, use a None
value when calling the
function.
def example(first, last='Doe', salary=100): return {'first': first, 'last': last, 'salary': salary} # 👇️ {'first': 'James', 'last': None, 'salary': 50} print(example('James', None, 50))
We passed a None
value for the last
default parameter, so the default value
is used.
A function's parameters must be defined in the following order:
*args
- receives excess positional arguments.**kwargs
- receives excess keyword arguments.*args
and **kwargs
insteadYou can specify parameters in the form *identifier
or **identifier
after
default parameters in the function's definition.
def get_employee(first, salary, last='Doe', *args, **kwargs): print(args) # 👉️ ('developer', ) print(kwargs) # 👉️ {'country': 'Austria'} return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100, 'Smith', 'developer', country='Austria') print(emp_1) # 👉️ {'first': 'James', 'last': 'Doe', 'salary': 100}
The form *identifier
is initialized to a tuple that receives any excess
positional arguments.
The form **identifier
is initialized to an ordered mapping that receives any
excess keyword arguments.
However, note that you might get a confusing result if you omit the value for
the last
default parameter.
def get_employee(first, salary, last='Doe', *args, **kwargs): print(args) # 👉️ () print(kwargs) # 👉️ {'country': 'Austria'} return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100, 'developer', country='Austria') print(emp_1) # 👉️ {'first': 'James', 'last': 'developer', 'salary': 100}
Notice that the last
parameter got set to a value of developer
.
The best way to deal with this is to use a keyword argument for the developer
value when calling the function.
def get_employee(first, salary, last='Doe', *args, **kwargs): print(args) # 👉️ () print(kwargs) # 👉️ {'department': 'developer', 'country': 'Austria'} return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100, department='developer', country='Austria') print(emp_1) # 👉️ {'first': 'James', 'last': 'Doe', 'salary': 100}
Keyword arguments use the format of name='value'
.
**kwargs
parameter in the function's definition.You can also use only **kwargs
in the function's definition.
def get_employee(first, salary, last='Doe', **kwargs): print(kwargs) # 👉️ {'country': 'Austria'} return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100, 'Smith', country='Austria') # 👇️ {'first': 'James', 'last': 'Doe', 'salary': 100} print(emp_1)
The form **identifier
is initialized to an ordered mapping that receives any
excess keyword arguments.
The country
argument in the example is an excess keyword argument and gets
added to the **kwargs
dictionary.
Keyword arguments use the format of name='value'
.
You can pass as many keyword arguments as necessary when using **kwargs
.
def get_employee(first, salary, last='Doe', **kwargs): # 👇️ {'country': 'Austria', 'city': 'Example'} print(kwargs) return {'first': first, 'last': last, 'salary': salary} emp_1 = get_employee('James', 100, country='Austria', city='Example') # 👇️ {'first': 'James', 'last': 'Doe', 'salary': 100} print(emp_1)
We passed country
and city
keyword arguments to the function and they both
got added to the kwargs
dictionary.
An important note is to avoid setting default values for non-primitive parameters, e.g. dictionaries and lists.
Here is an example of how this can go wrong.
def get_address(address={}): return address addr1 = get_address() addr2 = get_address() addr1['country'] = 'Germany' print(addr1) # 👉️ {'country': 'Germany'} print(addr2) # 👉️ {'country': 'Germany'}
We
called the get_address()
function 2 times
and stored the results in variables.
Notice that we only set the country
key on one of the dictionaries but both of
them got updated.
They are not evaluated each time the function is called.
When a non-primitive default parameter value, such as a dictionary or a list, is mutated, it is mutated for all function calls.
One way to get around this issue is to set the default parameter value to None
and conditionally update it in the body of the function.
def get_address(address=None): if address is None: address = {} return address addr1 = get_address() addr2 = get_address() addr1['country'] = 'Germany' print(addr1) # 👉️ {'country': 'Germany'} print(addr2) # 👉️ {}
The body of the function is run every time it is invoked, so the issue no longer exists.
You can learn more about the related topics by checking out the following tutorials: