Preface
=======

At some point I was tired of writing & supporting `__repr__` methods, so I've decided to write it once and reuse everywhere in my classes.

Since I'm trying to write classes as simple as possible, most of them ends in something like

```
class MyClass:
     def __init__(self, param, other_param, ...):
         self.param = param
         self.other_param = other_param
         ...

```

i.e. initializer parameters names corresponds to fields names, so we can get initializer arguments by simply `getattr`'ing corresponding fields.

Main ideas are:

1. I want to have informative string which includes parameters involved in instance creation. For simple cases it should be possible to copy string & paste in some place (e.g. REPL session) and have similar object definition with as less work as possible. This helps a lot during debugging sessions, logging, especially in failed test cases with randomly generated data.

Great examples are `Counter` & `OrderedDict` from `collections` standard library:

```
>>> from collections import Counter
>>> Counter(range(2))
Counter({0: 1, 1: 1})
>>> Counter(range(2)) == Counter({0: 1, 1: 1})
True
>>> from collections import OrderedDict
>>> OrderedDict.fromkeys(range(2))
OrderedDict([(0, None), (1, None)])
>>> OrderedDict.fromkeys(range(2)) == OrderedDict([(0, None), (1, None)])
True

```
2. Once signature change, `__repr__` should handle this automatically for simple cases like renaming/removing/changing order of parameters.

Attempt
=======

*Note*: following code requires Python3.5+ due to `yield from` statement

```
import inspect
from collections import (OrderedDict,
                         abc)
from typing import (Any,
                    Callable,
                    Iterable,
                    TypeVar,
                    Union)

Domain = TypeVar('Domain')
Range = TypeVar('Range')
Map = Callable[[Domain], Range]

Constructor = Callable[..., Domain]
Initializer = Callable[..., None]

def generate_repr(constructor_or_initializer: Union[Constructor, Initializer],
                  *,
                  field_seeker: Callable[[Domain, str], Any] = getattr
                  ) -> Map[Domain, str]:
    signature = inspect.signature(constructor_or_initializer)
    parameters = OrderedDict(signature.parameters)
    # remove `self`
    parameters.popitem(0)
    to_positional_argument_string = repr
    to_keyword_argument_string = '{}={!r}'.format

    def __repr__(self: Domain) -> str:
        return (type(self).__qualname__
                + '(' + ', '.join(to_arguments_strings(self)) + ')')

    def to_arguments_strings(object_: Domain) -> Iterable[str]:
        for parameter_name, parameter in parameters.items():
            field = field_seeker(object_, parameter_name)
            if parameter.kind == inspect._VAR_POSITIONAL:
                if isinstance(field, abc.Iterator):
                    yield '...'
                else:
                    yield from map(to_positional_argument_string, field)
            elif parameter.kind == inspect._VAR_KEYWORD:
                yield from map(to_keyword_argument_string,
                               field.keys(), field.values())
            elif parameter.kind in {inspect._POSITIONAL_ONLY,
                                    inspect._POSITIONAL_OR_KEYWORD}:
                yield to_positional_argument_string(field)
            else:
                yield to_keyword_argument_string(parameter_name, field)

    return __repr__

```

Test
====

Let's define our class like

```
class A:
    def __init__(self, positional, *variadic_positional, keyword_only,
                 **variadic_keyword):
        self.positional = positional
        self.variadic_positional = variadic_positional
        self.keyword_only = keyword_only
        self.variadic_keyword = variadic_keyword

    __repr__ = generate_repr(__init__)

```

After that

```
>>> A(1, 2, 3, keyword_only='some', a={'sample': 42}, b={1, 2})
A(1, 2, 3, keyword_only='some', b={1, 2}, a={'sample': 42})

```