Skip to content

NicDom/overloadlib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

244 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Overloadlib

Build status Python Version Dependencies Status Codecov Read The Docs Code style: black Security: bandit Pre-commit Semantic Versions License

A python package to implement overloading of functions in python.

Features

  • Introduces @overload, @override and @<Function>.add decorators, allowing one to overload and override functions. Functions are then called according to their argument types:
@overload
def func(var: str):
    return var

#via @<Function>.add
@func.add
def _(var: int) -> str:
    return str(var * 5)

#via @overload
@overload
def func() -> str:
    return "Functions don't need to have arguments."

#via @override
@override(funcs=[func])
def new(str_1: str, int_1: int):
    return str_1 * int_1

assert func("a") == "a" == new("a")
assert func(1) == "5" == new(1)
assert func() == "Functions don't need to have arguments." == new()
assert new("house", 2) == "househouse"
  • Raises human readable errors, if no callable was determined with the given arguments:
@overload
def some_func(str_1: str, int_1: int):
    return str_1 + str(int_1)

@overload
def some_func(str_1: str):
    return str_1

>>> some_func(str_1=2)
PyOverloadError:
Error when calling:
(__main__.some_func):
         def some_func(str_1: int):
                ...:
        'str_1' needs to be of type (<class 'str'>,) (is type <class 'int'>)

or

>>> some_func(10)
__main__.NoFunctionFoundError: No matching function found.
Following definitions of 'some_func' were found:
(__main__.some_func):
         def some_func(str_1: str, int_1: int):
                ...
(__main__.some_func):
         def some_func(str_1: str):
                ...
The following call was made:
(__main__.some_func):
         def some_func(int_1: int):
                ...
  • Any type of variables is allowed: Build-in ones like str, int, List but also own ones, like classes etc.
  • @overload uses get_type_hints to identify the right function call via type-checking. Hence, it may also be used as a type-checker for functions.
  • Forgot, which overloads of a specific function have been implemented? No worries, you can print them with their typing information using print(func_versions_info(<my_func>)), e.g.
>>> print(func_versions_info(some_func))

Following overloads of 'some_func' exist:
(__main__.some_func):
         def some_func(str_1: str, int_1: int):
                ...
(__main__.some_func):
         def some_func(str_1: str):
                ...

Requirements

Requires Python 3.7+.

Installation

pip install -U overloadlib

or install with Poetry

poetry add overloadlib

Then you can run

overloadlib --help

or with Poetry:

poetry run overloadlib --help
Installing Poetry

To download and install Poetry run (with curl):

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python -

or on windows (without curl):

(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python -

Uninstall:

If you wan to uninstall the package, simply run

pip uninstall overloadlib

or with Poetry:

poetry remove overloadlib

Usage

Import

Imports need to come form overloadlib. Importing via

from overloadlib import *

imports overload, override and <Function>.add decorators, as well as func_versions_info.

Overloading of functions can be done via the @overload, @override or @<Function>.add decorator.

@overload

Overloading of functions:

@overload
def func(var: str):
    return var

@overload
def func(var: int):
    return str(var * 5)

func("a") == "a"  # True
"a: " + func(1)  # "a: 5"

Overloading of methods (or mixtures of both) are also possible using the same decorator:

@dataclass
class Hello:
    text: str = "Hello"

class Some:
    def __init__(self) -> None:
        pass

    @overload
    def func(self, str_1: str, int_1: int) -> str:
        return str_1 + str(int_1)

    @overload
    def func(self, str_1: str) -> str:
        return str_1

    @overload
    def func(self, obj: Hello) -> str:
        return obj.text

@overload
def func(str_1: str) -> str:
    return "yummy " + str_1

Note that own classes, can be given as types to the function. Furthermore, methods and functions ma have the same name. Possible calls could now look like this:

# Giving only *args
some.func("Number: ", 1)  # "Number: 1"

# Giving **kwargs
some.func(str_1="Number: ", int_1=1)  # "Number: 1"

# An object as argument
some.func(Hello())  # "Hello"

# Calling the function not the method
func("yummy")  # "yummy cheese"

@override

You may also 'overload' functions using the @override decorator. This one overrides an list of callables or Function (function wrapper class of overloadlib.py.) via a given new 'parent' function.

def func_str(var: str) -> str:
    return "I am a string"

def func_int(var: int) -> str:
    return "I am an integer"

@overload
def func_both(var_1: int, var_2: str) -> str:
    return var_2 * var_1

@override(funcs=[func_str, func_int, func_both])  # callables and `Function` are given
def new_func(fl: float) -> str:
    return "Float parameter"

Possible calls could now look like this:

new_func(1.0) == "Float parameter"  # True
new_func("a") == func_str("a") == "I am a string"  # True
new_func(1) == func_int(1) == "I am an integer"  # True
new_func(1, "a") == func_both(1, "a") == "a"  # True

Overriding Function's (callables that are decorated with @overload) overrides every version of that Function:

@dataclass
class Some:
    text: str = "Hello"

@overload
def func(str_1: str) -> str:
    return str_1

@func.add
def _(obj: Some) -> str:
    return obj.text

@overload
def func() -> str:
    return "Functions don't need to have arguments."

# adds all previously defined overloads/'version' of `func` to `new`
@override(funcs=[func])
def new(str_1: str, int_1: int) -> str:
    return str_1 * int_1

assert new("a") == "a" == func("a")
assert new(Some()) == "Hello" == func(Some())
assert new() == "Functions don't need to have arguments." == func()
assert new("house", 2) == "househouse"

@<Function>.add

You can always add a new callable to an existing overloaded callable <func> using the @<func>.add decorator:

@overload
def some_func(str_1: str, int_1: int) -> str:
    return str_1 + str(int_1)

@some_func.add
def _(str_1: str) -> str:
    return str_1

@some_func.add
def name_does_not_matter() -> str:
    return "I return some text."

@some_func.add
def _(str_1: str, str_2: str) -> str:
    return str_1 + str_2

assert some_func("This is a number: ", 10) == "This is a number: 10"
assert some_func("cheese") == "cheese"
assert some_func(Some()) == "Hello"

The name of the callable's you are adding don't matter and you can also always use the same name, when adding. However, as using the same name for added functions, clashes with [no-redef] error of mypy_, it is recommended to use different ones (this also increases the readability of the code).1

Usage of @override and @<Function>.add is recommended over usage of @overload only.

func_versions_info

If you want to get all versions of a certain function <myfunc>, use func_versions_info(<myfunc>), e.g.

>>> print(func_versions_info(new_func))

Following overloads of 'new_func' exist:
(__main__.new_func):
         def new_func(var: str):
                ...
(__main__.new_func):
         def new_func(var: int):
                ...
(__main__.new_func):
         def new_func(var_1: int, var_2: str):
                ...
(__main__.new_func):
         def new_func(fl: float):
                ...

Common Mistakes and Limitations

  • Overloading using overload raises problems with mypy. This can be circumvented using @override instead of @overload.

Contributing

Contributions are very welcome. To learn more, see the Contributor Guide.

Set up bots

  • Set up Dependabot to ensure you have the latest dependencies.
  • Set up Stale bot for automatic issue closing.

Poetry

Want to know more about Poetry? Check its documentation.

Details about Poetry

Poetry's commands are very intuitive and easy to learn, like:

  • poetry add numpy@latest
  • poetry run pytest
  • poetry publish --build

etc

Building and releasing your package

Building a new version of the application contains steps:

  • Switch to a branch
  • Bump the version of your package poetry version <version>. You can pass the new version explicitly, or a rule such as major, minor, or patch. For more details, refer to the Semantic Versions standard.
  • Make a commit to GitHub and push it.
  • Open a pull request.
  • Merge the pull request πŸ™‚

Development features

Table of Nox sessions

The following table gives an overview of the available Nox sessions:

Session Description Python Default
coverage Report coverage with Coverage.py 3.9 (βœ“)
docs Build and serve Sphinx documentation 3.9
docs Build Sphinx documentation 3.9 βœ“
mypy Type-check with mypy 3.6 … 3.9 βœ“
pre-commit Lint with pre-commit 3.9 βœ“
safety Scan dependencies with Safety 3.9 βœ“
tests Run tests with pytest 3.[6, 7, 8, 9] … 3.9 βœ“
typeguard Type-check with Typeguard 3.[6, 7, 8, 9] … 3.9 βœ“
xdoctest Run examples with xdoctest 3.[6, 7, 8, 9] … 3.9 βœ“

Deployment features

Open source community features

πŸ“ˆ Releases

You can see the list of available releases on the GitHub Releases page.

We follow Semantic Versions specification.

We use Release Drafter. As pull requests are merged, a draft release is kept up-to-date listing the changes, ready to publish when you’re ready. With the categories option, you can categorize pull requests in release notes using labels.

List of labels and corresponding titles

Pull Request Label Section in Release Notes
breaking πŸ’₯ Breaking Changes
enhancement πŸš€ Features
removal πŸ”₯ Removals and Deprecations
bug 🐞 Fixes
performance 🐎 Performance
testing 🚨 Testing
ci πŸ‘· Continuous Integration
documentation πŸ“š Documentation
refactoring πŸ”¨ Refactoring
style πŸ’„ Style
dependencies πŸ“¦ Dependencies

You can update it in release-drafter.yml.

GitHub creates the bug, enhancement, and documentation labels for you. Dependabot creates the dependencies label. Create the remaining labels when you need them, on the Issues tab of your GitHub repository,

πŸ›‘ License

License

This project is licensed under the terms of the MIT license. See LICENSE for more details.

Issues

If you encounter any problems, please file an issue along with a detailed description.

πŸ“ƒ Citation

@misc{overloadlib,
  author = {Niclas D. Gesing},
  title = {Overloadlib: A python package to implement overloading of functions in python.},
  year = {2021},
  publisher = {GitHub},
  journal = {GitHub repository},
  howpublished = {\url{https://github.com/NicDom/overloadlib}}
}

Credits πŸš€ Your next Python package needs a bleeding-edge project structure.

This project was generated with python-package-template

Footnotes

  1. It should also be stressed, that @<func>.add only works for a with @overload decorated function `. ↩

About

A python package to overload functions.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages