In Python 3, we can define a function with keyword-only arguments and in Python 3.8, we can further define with positional-only arguments. The syntax is easy, as follows:

Syntax:

def func(x, /, y, z=0, *, a, b):
...


which, the * signifies whatever afterwards are specified as keywords only. And the / signifies whatever before are positional only. This means the function defined above cannot be invoked with:

func(x=3)
func(3, 4, 5, 6, 7)


for the first violated the requirement that argument x must be positional only and the second violated that a and b must be keyword only. These works, however:

func(3, 4, 5, a=6, b=7)
func(3, 4, z=5, a=6, b=7)


because argument z (also y) can be specified either as positional or keyword argument

In this definition, z has a default value. We can legally define a function without default values for a and b because they are keyword only. Otherwise, we must specify their default value for them if we provided one for z since they can be positional.

While we do not provide a default for the two keyword-only arguments, they are not required. That is, we cannot invoke with

func(3, 4, a=5)


but must provide both as

func(3, 4, a=5, b=6)


Why we need them? One example is to make decorators. Consider a decorator that can be used differently, such as functools.lru_cache:

@lru_cache
def my_function():
...

@lru_cache(maxsize=32):
def my_other_function():
...


This decorator can be invoked using @lru_cache as well as @lru_cache(maxsize=32). Recall that a decorator in Python simply means

def my_function():
...
def my_other_function():
...

my_function = lru_cache(my_function)
my_other_function = lru_cache(maxsize=32)(my_other_function)


So in the first example, lru_cache is a function that takes another function as argument, and in the second example, lru_cache(maxsize=32) returns a function that does the same. If we look at the code, it is indeed implemented this way:

def lru_cache(maxsize=128, typed=False):
if isinstance(maxsize, int):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')

def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)

return decorating_function


So some isinstance() and callable() is used to see if lru_cache is used as a decorator or it is used as lru_cache(maxsize=32).

We can indeed write in this way (which we assume we forbid the usage of lru_cache(32) as decorator):

def lru_cache(func=None, /, *, maxsize=128, typed=False):
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)

if func is None:
return decorating_function
else:
return decorating_function(func)


The way it works is as follows: If @lru_cache is used as decorator, it will be invoked with the function to decorate as positional argument. But if @lru_cache(maxsize=32) is used as decorator, we assume a decorator should return as a result of lru_cache(maxsize=32).