Skip to main content

Keyword-only argument emulation for Python 2 as a decorator. Python 3 compatible.

Project description

build code quality code health coverage pypi github license: MIT

This library emulates the python3 keyword-only arguments under python2. The resulting code is python3 compatible.

Usage

Installation

pip install kwonly-args

Alternatively you can download the zipped library from https://pypi.python.org/pypi/kwonly-args

Code

With this library you can turn some or all of the default arguments of your function into keyword-only arguments.

  • Decorate your function with kwonly_args.first_kwonly_arg and select one of the default arguments of your function with the name parameter of the decorator. The selected argument along with all default arguments on its right side will be treated as keyword-only arguments.
  • All keyword-only arguments have a default value and they aren’t required args by default. You can make a keyword-only argument required by using kwonly_args.KWONLY_REQUIRED as its default value.

Your new-born keyword-only args are no longer treated as positional arguments and varargs still work if your function has *args or something like that.

from kwonly_args import first_kwonly_arg, KWONLY_REQUIRED


# This turns default1 and default2 into keyword-only arguments.
# They are no longer handled as positional arguments.
@first_kwonly_arg('default1')
def func(arg0, arg1, default0='d0', default1='d1', default2='d2', *args):
    print('arg0={} arg1={} default0={} default1={} default2={} args={}'.format(
          arg0, arg1, default0, default1, default2, args))


func(0, 1, 2, 3, 4)
# Output:
# arg0=0 arg1=1 default0=2 default1=d1 default=d2 args=(3, 4)

# The default1 and default2 args can be passed only as keyword arguments:
func(0, 1, 2, 3, 4, default1='kwonly_param')
# Output:
# arg0=0 arg1=1 default0=2 default1=kwonly_param default=d2 args=(3, 4)


# In this example all three args are keyword-only args and default1 is required.
@first_kwonly_arg('default0')
def func2(default0='d0', default1=KWONLY_REQUIRED, default2='d2'):
    ...

You can also decorate class methods (including both old and new style classes):

from kwonly_args import first_kwonly_arg


class MyClass:
    # turning d1 and d2 into keyword-only arguments
    @first_kwonly_arg('d1')
    def my_instance_method(self, a0, a1, d0='d0', d1='d1', d2='d2', *args):
        ...

    # You have to apply @first_kwonly_arg before @classmethod!
    @classmethod
    @first_kwonly_arg('d1')
    def my_class_method(cls, a0, a1, d0='d0', d1='d1', d2='d2', *args):
        ...

    # You have to apply @first_kwonly_arg before @staticmethod!
    @staticmethod
    @first_kwonly_arg('d1')
    def my_static_method(a0, a1, d0='d0', d1='d1', d2='d2', *args):
        ...

Warning

This library is compatible with python3 but under python3 you can apply the kwonly_args.first_kwonly_arg decorator only to functions that have a python2 compatible signature. E.g.: If your function has python3 keyword-only arguments then applying this decorator fails (because of the used inspect.getargspec() function doesn’t support python2-incompatible signatures).

This library could circumvent this problem by using inspect.getfullargspec() under python3 but why would we emulate keyword-only arguments in python3 when it is natively available and why whould we apply a python2 helper library on a piece of code that doesn’t even compile under python2? On top of this it would provide two different places (before and after the varargs - eumulated and native python3) in your function arg list to specify keyword-only arguments - this is just ugly from a design perspective.

Implementation

Python 2 function signature anatomy

A python2 function argument list consists of the following optional parts. Any optional parts that are present in a function signature appear in the listed order:

  1. Positional arguments
    1. Required arguments (positional arguments without default value)
    2. Default arguments (positional arguments with default value)
    3. Keyword-only arguments (this is available only when you use this library)
  2. VarArgs (*args)
  3. VarKWArgs (**kwargs)

As you see in standard python2 your positional argument list consists of zero or more required arguments followed by zero or more default arguments. This library can turn the last N default arguments (all/some of them) into keyword-only arguments. With the help of this library you can now split the positional argument list of your python2 function signatures into 3 parts instead of the standard 2.

In python3 the keyword-only arguments reside between VarArgs and VarKWArgs but in python2 you can’t put anything between those (it would be a syntax error) so your best bet to emulate keyword-only arguments is turning some of your positional arguments into keyword-only args.

Why does this “library” exist?

The world gives birth to new things in every single moment. This is a key driver behind evolution. But anyway, you are just too naive if you think you can stop code-monkeys with this question. :-D Even a bad reimplementation can give new insights sometimes but in worst case the author learns some new things and learns to appreciate existing implementations for hiding the discovered hell/complexity.

I’ve checked out some other python2 keyword-only argument emulator code snippets and decided to roll my own just for fun and also for the following reasons:

  • Some of those implementations provide you with a decorator with which you have to specify your keyword-only arguments with their (usually zero based) index in the arg list of the function. This is error prone, I never liked the idea of identifying arguments with indexes. The only minor disadvantage of using arg names instead of arg indexes is that using arg names requires direct access to the signature of the original wrapped function. If there are other decorators between our decorator and the original function then under python2 using names isn’t really possible (because functools.update_wrapper() and decorators in general don’t have/support the __wrapped__ attribute to maintain a chain back to the originally wrapped function).
  • Some implementations allow you to pick an arbitrary set of positional arguments by specifying their indexes or names. I don’t like the idea of promoting arbitrary positional arguments into keyword-only arguments by scattering keyword-only args through the remaining positional args. It degrades code readability a lot. This is why I decided to keep positional arguments of the same type (required/default/kwonly) together in a well defined slice of the positional argument list.
  • The implementation of this solution is brief (~40 lines of logic), simple, and well tested.

Project details


Release history Release notifications

History Node

1.0.10

History Node

1.0.9

History Node

1.0.8

History Node

1.0.7

History Node

1.0.6

History Node

1.0.5

This version
History Node

1.0.4

History Node

1.0.3

History Node

1.0.2

History Node

1.0.1

History Node

1.0.0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Filename, size & hash SHA256 hash help File type Python version Upload date
kwonly_args-1.0.4-py3-none-any.whl (11.7 kB) Copy SHA256 hash SHA256 Wheel py3 Apr 8, 2016
kwonly-args-1.0.4.tar.gz (8.2 kB) Copy SHA256 hash SHA256 Source None Apr 8, 2016

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging CloudAMQP CloudAMQP RabbitMQ AWS AWS Cloud computing Fastly Fastly CDN DigiCert DigiCert EV certificate StatusPage StatusPage Status page