{ "info": { "author": "Wessel Bruinsma", "author_email": "wessel.p.bruinsma@gmail.com", "bugtrack_url": null, "classifiers": [], "description": "# [Plum: Multiple Dispatch in Python](https://github.com/wesselb/plum)\n\n[![Build](https://travis-ci.org/wesselb/plum.svg?branch=master)](https://travis-ci.org/wesselb/plum)\n[![Coverage Status](https://coveralls.io/repos/github/wesselb/plum/badge.svg?branch=master&service=github)](https://coveralls.io/github/wesselb/plum?branch=master)\n[![Latest Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://wesselb.github.io/plum)\n\nEverybody likes multiple dispatch, just like everybody likes plums.\n\n* [Installation](#installation)\n* [Basic Usage](#basic-usage)\n* [Advanced Features by Example](#advanced-features-by-example)\n - [Dispatch From Type Annotations](#dispatch-from-type-annotations)\n - [Union Types](#union-types)\n - [Parametric Types](#parametric-types)\n - [Variable Arguments](#variable-arguments)\n - [Return Types](#return-types)\n - [Inheritance](#inheritance)\n - [Conversion](#conversion)\n - [Promotion](#promotion)\n - [Method Precedence](#method-precedence)\n - [Parametric Classes](#parametric-classes)\n - [Add Multiple Methods](#add-multiple-methods)\n - [Extend a Function From Another Package](#extend-a-function-from-another-package)\n - [Directly Invoke a Method](#directly-invoke-a-method)\n\n## Installation\n\n```bash\npip install plum-dispatch\n```\n\n## Basic Usage\n\nMultiple dispatch allows you to implement multiple *methods* for the same \n*function*, where the methods specify the types of their arguments:\n\n```python\nfrom plum import dispatch\n\n@dispatch(str)\ndef f(x):\n return 'This is a string!'\n \n\n@dispatch(int)\ndef f(x):\n return 'This is an integer!'\n```\n\n```python\n>>> f('1')\n'This is a string!'\n\n>>> f(1)\n'This is an integer!'\n```\n\nWe haven't implemented a method for `float`s, so in that case an exception \nwill be raised:\n\n```python\n>>> f(1.0)\nNotFoundLookupError: For function \"f\", signature (builtins.float) could not be resolved.\n```\n\nInstead of implementing a method for `float`s, let's implement a method for \nall numbers:\n\n```python\nfrom numbers import Number\n\n\n@dispatch(Number)\ndef f(x):\n return 'This is a number!'\n```\n\nSince a `float` is a `Number`, `f(1.0)` will return `'This is a number!'`.\nBut an `int` is also a `Number`, so `f(1)` can either return \n`'This is an integer!'` or `'This is a number!'`.\nThe rule of multiple dispatch is that the *most specific* method is chosen:\n\n\n```python\n>>> f(1)\n'This is an integer!'\n```\n\nsince an `int` is a `Number`, but a `Number` is not necessarily an `int`.\n\nFor an excellent and way more detailed overview of multiple dispatch, see the\n[manual of the Julia Language](https://docs.julialang.org/en/).\n\n## Features by Example\n\n### Dispatch From Type Annotations\n\n`Dispatcher.annotations` is an experimental feature that can be used to \ndispatch on a function's type annotations:\n\n```python\nfrom plum import dispatch, add_conversion_method\n\nadd_conversion_method(type_from=int, type_to=str, f=str)\n\n\n@dispatch.annotations()\ndef int_to_str(x: int) -> str:\n return x\n \n \n@dispatch.annotations()\ndef int_to_str(x):\n raise ValueError('I only take integers!')\n```\n\n```python\n>>> int_to_str(1.0)\nValueError: I only take integers!\n\n>>> int_to_str(1)\n'1'\n```\n\n### Union Types\n\nSets can be used to instantiate union types:\n\n```python\nfrom plum import dispatch\n\n@dispatch(object)\ndef f(x):\n print('fallback')\n\n\n@dispatch({int, str})\ndef f(x):\n print('int or str')\n```\n\n```\n>>> f(1)\nint or str\n\n>>> f('1')\nint or str\n\n>>> f(1.0)\nfallback\n```\n\n### Parametric Types\n\nThe parametric types `Tuple` and `List` can be used to dispatch on tuples \nand lists with particular types of elements.\nImportantly, the type system is *covariant*, as opposed to Julia's type \nsystem, which is *invariant*.\n\n```python\nfrom plum import dispatch, Tuple, List\n\n@dispatch({tuple, list})\ndef f(x):\n print('tuple or list')\n \n \n@dispatch(Tuple(int))\ndef f(x):\n print('tuple of int')\n \n \n@dispatch(List(int))\ndef f(x):\n print('list of int')\n```\n\n```python\n>>> f([1, 2])\n'list of int'\n\n>>> f([1, '2'])\n'tuple or list'\n\n>>> f((1, 2))\n'tuple of int'\n\n>>> f((1, '2'))\n'tuple or list'\n```\n\n### Variable Arguments\n\nA list can be used to specify variable arguments:\n\n```python\nfrom plum import dispatch\n\n@dispatch(int)\ndef f(x):\n print('single argument')\n \n\n@dispatch(int, [int])\ndef f(x, *xs):\n print('multiple arguments')\n```\n\n```\n>>> f(1)\nsingle argument\n\n>>> f(1, 2)\nmultiple arguments\n\n>>> f(1, 2, 3)\nmultiple arguments\n```\n\n### Return Types\n\nThe keyword argument `return_type` can be set to specify return types:\n\n```python\nfrom plum import dispatch, add_conversion_method\n\n@dispatch({int, str}, return_type=int)\ndef f(x):\n return x\n```\n\n```python\n>>> f(1)\n1\n\n>>> f('1')\nTypeError: Expected return type \"builtins.int\", but got type \"builtins.str\".\n\n>>> add_conversion_method(type_from=str, type_to=int, f=int)\n\n>>> f('1')\n1\n\n```\n\n### Inheritance\n\nSince every class in Python can be subclassed, diagonal dispatch cannot be \nimplemented.\nHowever, inheritance can be used to achieve a form of diagonal dispatch:\n\n```python\nfrom plum import Dispatcher, Referentiable, Self\n\nclass Real(Referentiable):\n dispatch = Dispatcher(in_class=Self)\n\n @dispatch(Self)\n def __add__(self, other):\n return 'real'\n \n\nclass Rational(Real, Referentiable):\n dispatch = Dispatcher(in_class=Self)\n\n @dispatch(Self)\n def __add__(self, other):\n return 'rational'\n \n\nreal = Real()\nrational = Rational()\n```\n\n\n```\n>>> real + real\n'real'\n\n>>> real + rational\n'real'\n\n>>> rational + real\n'real'\n\n>>> rational + rational\n'rational'\n```\n\n### Conversion\n\nThe function `convert` can be used to convert objects of one type to another:\n\n```python\nfrom numbers import Number\n\nfrom plum import convert\n\n\nclass Rational(object):\n def __init__(self, num, denom):\n self.num = num\n self.denom = denom\n```\n\n```python\n>>> convert(0.5, Number)\n0.5\n\n>>> convert(Rational(1, 2), Number)\nTypeError: Cannot convert a \"__main__.Rational\" to a \"numbers.Number\".\n```\n\nThe `TypeError` indicates that `convert` does not know how to convert a \n`Rational` to a `Number`.\nLet us implement that conversion:\n\n```python\nfrom operator import truediv\n\nfrom plum import conversion_method\n \n\n@conversion_method(type_from=Rational, type_to=Number)\ndef rational_to_number(q):\n return truediv(q.num, q.denom)\n```\n\n```python\n>>> convert(Rational(1, 2), Number)\n0.5\n```\n\nInstead of the decorator `conversion_method`, one can also use \n`add_conversion_method`:\n\n\n```python\nfrom plum import add_conversion_method\n\nadd_conversion_method(type_from, type_to, conversion_function)\n```\n\n### Promotion\n\nThe function `promote` can be used to promote objects to a common type:\n\n```python\nfrom plum import dispatch, promote, add_promotion_rule, add_conversion_method\n\n@dispatch(object, object)\ndef add(x, y):\n return add(*promote(x, y))\n \n \n@dispatch(int, int)\ndef add(x, y):\n return x + y\n \n \n@dispatch(float, float)\ndef add(x, y):\n return x + y\n```\n\n```python\n>>> add(1, 2)\n3\n\n>>> add(1.0, 2.0)\n3.0\n\n>>> add(1, 2.0)\nTypeError: No promotion rule for \"builtins.int\" and \"builtins.float\".\n\n>>> add_promotion_rule(int, float, float)\n\n>>> add(1, 2.0)\nTypeError: Cannot convert a \"builtins.int\" to a \"builtins.float\".\n\n>>> add_conversion_method(type_from=int, type_to=float, f=float)\n\n>>> add(1, 2.0)\n3.0\n```\n\n### Method Precedence\n\nThe keyword argument `precedence` can be set to an integer value to specify \nprecedence levels of methods, which are used to break ambiguity:\n\n```python\nfrom plum import dispatch\n\nclass Element(object):\n pass\n\n\nclass ZeroElement(Element):\n pass\n\n\nclass SpecialisedElement(Element):\n pass\n\n\n@dispatch(ZeroElement, Element)\ndef mul_no_precedence(a, b):\n return 'zero'\n\n\n@dispatch(Element, SpecialisedElement)\ndef mul_no_precedence(a, b):\n return 'specialised operation'\n \n\n@dispatch(ZeroElement, Element, precedence=1)\ndef mul(a, b):\n return 'zero'\n\n\n@dispatch(Element, SpecialisedElement)\ndef mul(a, b):\n return 'specialised operation'\n```\n\n```python\n>>> zero = ZeroElement()\n\n>>> specialised_element = SpecialisedElement()\n\n>>> element = Element()\n\n>>> mul(zero, element)\n'zero'\n\n>>> mul(element, specialised_element)\n'specialised operation'\n\n>>> mul_no_precedence(zero, specialised_element)\nAmbiguousLookupError: For function \"mul_no_precedence\", signature (__main__.ZeroElement, __main__.SpecialisedElement) is ambiguous among the following:\n (__main__.ZeroElement, __main__.Element) (precedence: 0)\n (__main__.Element, __main__.SpecialisedElement) (precedence: 0)\n\n>>> mul(zero, specialised_element)\n'zero'\n```\n\n### Parametric Classes\n\nThe decorator `parametric` can be used to create parametric classes:\n\n```python\nfrom plum import dispatch, parametric\n\n@parametric\nclass A(object): # Must be a new-style class!\n pass\n \n \n@dispatch(A)\ndef f(x):\n return 'fallback'\n \n \n@dispatch(A(1))\ndef f(x):\n return '1'\n \n \n@dispatch(A(2))\ndef f(x):\n return '2'\n```\n\n```python\n>>> A\n__main__.A\n\n>>> A(1)\n__main__.A{1}\n\n>>> issubclass(A(1), A)\nTrue\n\n>>> A(1)()\n<__main__.A{1} at 0x10c2bab70>\n\n>>> f(A(1)())\n'1'\n\n>>> f(A(2)())\n'2'\n\n>>> f(A(3)())\n'fallback'\n```\n\n### Add Multiple Methods\n\n`Dispatcher.multi` can be used to implement multiple methods at once:\n\n```python\nfrom plum import dispatch\n\n@dispatch.multi((int, int), (float, float))\ndef add(x, y):\n return x + y\n```\n\n```python\n>>> add(1, 1)\n2\n\n>>> add(1.0, 1.0)\n2.0\n\n>>> add(1, 1.0)\nNotFoundLookupError: For function \"add\", signature (builtins.int, builtins.float) could not be resolved.\n```\n\n### Extend a Function From Another Package\n\n`Function.extend` can be used to extend a particular function:\n\n```python\nfrom package import f\n\n@f.extend(int)\ndef f(x):\n return 'new behaviour'\n```\n\n```python\n>>> f(1.0)\n'old behaviour'\n\n>>> f(1)\n'new behaviour'\n```\n\n### Directly Invoke a Method\n\n`Function.invoke` can be used to invoke a method given types of the arguments:\n\n```python\nfrom plum import dispatch\n\n@dispatch(int)\ndef f(x):\n return 'int'\n \n \n@dispatch(str)\ndef f(x):\n return 'str'\n```\n\n```python\n>>> f(1)\n'int'\n\n>>> f('1')\n'str'\n\n>>> f.invoke(int)('1')\n'int'\n\n>>> f.invoke(str)(1)\n'str'\n```", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/wesselb/plum", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "plum-dispatch", "package_url": "https://pypi.org/project/plum-dispatch/", "platform": "", "project_url": "https://pypi.org/project/plum-dispatch/", "project_urls": { "Homepage": "https://github.com/wesselb/plum" }, "release_url": "https://pypi.org/project/plum-dispatch/0.1.6/", "requires_dist": null, "requires_python": "", "summary": "Multiple dispatch in Python", "version": "0.1.6" }, "last_serial": 5397125, "releases": { "0.1.4": [ { "comment_text": "", "digests": { "md5": "c2fbcf3f1a0bad3199f3523adef6f11d", "sha256": "006beaa1406ae28b54c900909f75672fd055f14d195176f640d7cb1435888587" }, "downloads": -1, "filename": "plum-dispatch-0.1.4.tar.gz", "has_sig": false, "md5_digest": "c2fbcf3f1a0bad3199f3523adef6f11d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25642, "upload_time": "2019-06-12T19:56:21", "url": "https://files.pythonhosted.org/packages/4a/de/e2760eb010135b2c463dd122eb45ce9fab79266cb61f31af65e6e93efbf9/plum-dispatch-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "0b990d541063689ee516790d9525d947", "sha256": "c7c1a0696796a559b3fe131c216d44b23b2b177460fe4be5c22debf00ab0df91" }, "downloads": -1, "filename": "plum-dispatch-0.1.5.tar.gz", "has_sig": false, "md5_digest": "0b990d541063689ee516790d9525d947", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25919, "upload_time": "2019-06-13T17:09:20", "url": "https://files.pythonhosted.org/packages/b2/f1/e39368ee04d0da4d82f426c4a64a90052816f1eaf93e9d74ec4bc39bc4b2/plum-dispatch-0.1.5.tar.gz" } ], "0.1.6": [ { "comment_text": "", "digests": { "md5": "049774e592e952b8f76dd86a470d6315", "sha256": "5be7d122b80772d9ddf12b5b6aa1f7df4547ef09ea469f4b5d4db64be41d14c7" }, "downloads": -1, "filename": "plum-dispatch-0.1.6.tar.gz", "has_sig": false, "md5_digest": "049774e592e952b8f76dd86a470d6315", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25887, "upload_time": "2019-06-13T17:58:43", "url": "https://files.pythonhosted.org/packages/9e/90/14615732b8b4e690173332e0a4484a08334a3d6a99014a35dafe97d034f9/plum-dispatch-0.1.6.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "049774e592e952b8f76dd86a470d6315", "sha256": "5be7d122b80772d9ddf12b5b6aa1f7df4547ef09ea469f4b5d4db64be41d14c7" }, "downloads": -1, "filename": "plum-dispatch-0.1.6.tar.gz", "has_sig": false, "md5_digest": "049774e592e952b8f76dd86a470d6315", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25887, "upload_time": "2019-06-13T17:58:43", "url": "https://files.pythonhosted.org/packages/9e/90/14615732b8b4e690173332e0a4484a08334a3d6a99014a35dafe97d034f9/plum-dispatch-0.1.6.tar.gz" } ] }