combomethod 1.0.12
Decorator indicating a method is both a class and an instance method
License: Apache License 2.0
Keywords: method classmethod instance combomethod


| |travisci| |version| |versions| |impls| |wheel| |coverage| |br-coverage|

.. |travisci| image:: https://api.travis-ci.org/jonathaneunice/combomethod.svg
    :target: http://travis-ci.org/jonathaneunice/combomethod

.. |version| image:: http://img.shields.io/pypi/v/combomethod.svg?style=flat
    :alt: PyPI Package latest release
    :target: https://pypi.org/project/combomethod

.. |versions| image:: https://img.shields.io/pypi/pyversions/combomethod.svg
    :alt: Supported versions
    :target: https://pypi.org/project/combomethod

.. |impls| image:: https://img.shields.io/pypi/implementation/combomethod.svg
    :alt: Supported implementations
    :target: https://pypi.org/project/combomethod

.. |wheel| image:: https://img.shields.io/pypi/wheel/combomethod.svg
    :alt: Wheel packaging support
    :target: https://pypi.org/project/combomethod

.. |coverage| image:: https://img.shields.io/badge/test_coverage-100%25-6600CC.svg
    :alt: Test line coverage
    :target: https://pypi.org/project/combomethod

.. |br-coverage| image:: https://img.shields.io/badge/branch_coverage-100%25-6600CC.svg
    :alt: Test branch coverage
    :target: https://pypi.org/project/combomethod

Python has instance methods, class methods (``@classmethod``), and static
methods (``@staticmethod``). But it doesn't have a clear way to invoke a method
on either a class *or* its instances. With ``combomethod``, it does.

::

    from combomethod import combomethod

    class A(object):

        @combomethod
        def either(receiver, x, y):
            return x + y

    a = A()
    assert a.either(1, 3) == 4
    assert A.either(1, 3) == 4

*Voila!* You method now takes either the class or the instance--whichever
one you want to call it with.

Discussion
==========

In some cases, you can fake ``@combomethod`` with ``@classmethod``. In
the code above, for example, there is no real reference to the class
or instance, and ``either`` could have been designated a ``@classmethod``,
since they can be called with either classes or instances. But, there's a
problem: Class methods *always* pass the class to the method, even if they're
called with an instance. With this approach, you can never access the
instance variables. Ouch!

Alternatively, ``either`` could have been designated a ``@staticmethod``,
had its ``receiver`` parameter been removed. But while it would then be
callable from either an instance or a class, in neither case would it pass
the object the method was called from. There'd never be a way to access
either the class or instance variables. Ouch again!

As useful as ``@classmethod`` and ``@staticmethod`` are, they don't handle the
(occasionally important) corner case where you need to call with either the
class or an instance *and* you need genuine access to the object doing the
call. Here's an example that needs this::

    class Above(object):

        base = 10

        def __init__(self, base=100):
            self.base = base

        @combomethod
        def above_base(receiver, x):
            return receiver.base + x

    a = Above()
    assert a.above_base(5) == 105
    assert Above.above_base(5) == 15

    aa = Above(12)
    assert aa.above_base(5) == 17
    assert Above.above_base(5) == 15

When you need to call with either an instance or a class, and you also care
about the object doing the calling, ``@combomethod`` rocks and rolls.

Notes
=====

* This module is primarily a convenient packaging, testing,
  and documentation of insights and code from Mike Axiak's
  `Stack Overflow post <http://stackoverflow.com/questions/2589690/creating-a-method-that-is-simultaneously-an-instance-and-class-method>`_.
  Thank you, Mike!

* Automated multi-version testing managed with
  `pytest <https://pypi.org/project/pytest>`_,
  `pytest-cov <https://pypi.org/project/pytest-cov>`_,
  `coverage <https://pypi.org/project/coverage>`_, and
  `tox <https://pypi.org/project/tox>`_.
  Continuous integration testing
  with `Travis-CI <https://travis-ci.org/jonathaneunice/combomethod>`_.
  Packaging linting with `pyroma <https://pypi.org/project/pyroma>`_.

* Successfully packaged for, and tested against, all late-model versions of
  Python: 2.6, 2.7, 3.3, 3.4, 3.5, 3.6, and 3.7 pre-release as well as the latest
  PyPy and PyPy3 builds.

* See ``CHANGES.yml`` for the complete Change Log.

* The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
  `@jeunice on Twitter <http://twitter.com/jeunice>`_
  welcomes your comments and suggestions.

Installation
============

To install or upgrade to the latest version::

    pip install -U combomethod

You may need to prefix these with ``sudo`` to authorize installation. In
environments without super-user privileges, you may want to use ``pip``'s
``--user`` option, to install only for a single user, rather than system-wide.
You may also need Python-version-sepecific ``pip2`` or ``pip3`` installers,
depending on your system configuration. In cases where ``pip`` isn't
well-configured for a specific Python instance you need, a useful fallback::

    python3.6 -m pip install -U combomethod