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).