1# coding: utf-8 2from __future__ import absolute_import, division, print_function 3 4import inspect 5 6import six 7 8from .compat.contextlib import suppress 9 10__all__ = ['ReprMixinBase', 'ReprMixin'] 11 12 13class ReprMixinBase(object): 14 """Mixin to construct :code:`__repr__` for named arguments **automatically**. 15 16 :code:`_repr_pretty_` for :py:mod:`IPython.lib.pretty` is also constructed. 17 18 :param positional: Mark arguments as positional by number, or a list of 19 argument names. 20 21 .. deprecated:: 1.5.0 22 23 Use the :func:`~represent.core.autorepr` class decorator instead. 24 """ 25 26 def __init__(self, positional=None, *args, **kwargs): 27 cls = self.__class__ 28 # On first init, class variables for repr won't exist. 29 # 30 # Subclasses created after an initialisation of the superclass 31 # will require the repr class variables to be created for the new 32 # class. 33 if (not hasattr(cls, '_repr_clsname') 34 or cls._repr_clsname != cls.__name__): 35 cls._repr_clsname = cls.__name__ 36 cls._repr_positional = positional 37 38 # Support Python 3 and Python 2 argspecs, 39 # including keyword only arguments 40 try: 41 argspec = inspect.getfullargspec(self.__init__) 42 except AttributeError: 43 argspec = inspect.getargspec(self.__init__) 44 45 fun_args = argspec.args[1:] 46 kwonly = set() 47 with suppress(AttributeError): 48 fun_args.extend(argspec.kwonlyargs) 49 kwonly.update(argspec.kwonlyargs) 50 51 # Args can be opted in as positional 52 if positional is None: 53 positional = [] 54 elif isinstance(positional, int): 55 positional = fun_args[:positional] 56 elif isinstance(positional, six.string_types): 57 positional = [positional] 58 59 # Ensure positional args can't follow keyword args. 60 keyword_started = None 61 62 # _repr_pretty_ uses lists for the pretty printer calls 63 cls._repr_pretty_positional_args = list() 64 cls._repr_pretty_keyword_args = list() 65 66 # Construct format string for __repr__ 67 repr_parts = [cls.__name__, '('] 68 for i, arg in enumerate(fun_args): 69 if i: 70 repr_parts.append(', ') 71 72 if arg in positional: 73 repr_parts.append('{{self.{0}!r}}'.format(arg)) 74 cls._repr_pretty_positional_args.append(arg) 75 76 if arg in kwonly: 77 raise ValueError("keyword only argument '{}' cannot be" 78 " positional".format(arg)) 79 if keyword_started: 80 raise ValueError( 81 "positional argument '{}' cannot follow keyword" 82 " argument '{}'".format(arg, keyword_started)) 83 else: 84 keyword_started = arg 85 repr_parts.append('{0}={{self.{0}!r}}'.format(arg)) 86 cls._repr_pretty_keyword_args.append(arg) 87 88 repr_parts.append(')') 89 90 # Store as class variable. 91 cls._repr_formatstr = ''.join(repr_parts) 92 93 # Pass on args for cooperative multiple inheritance. 94 super(ReprMixinBase, self).__init__(*args, **kwargs) 95 96 def __repr__(self): 97 return self.__class__._repr_formatstr.format(self=self) 98 99 def _repr_pretty_(self, p, cycle): 100 """Pretty printer for IPython.lib.pretty""" 101 cls = self.__class__ 102 clsname = cls.__name__ 103 104 if cycle: 105 p.text('{}(...)'.format(clsname)) 106 else: 107 positional_args = cls._repr_pretty_positional_args 108 keyword_args = cls._repr_pretty_keyword_args 109 110 with p.group(len(clsname) + 1, clsname + '(', ')'): 111 for i, positional in enumerate(positional_args): 112 if i: 113 p.text(',') 114 p.breakable() 115 p.pretty(getattr(self, positional)) 116 117 for i, keyword in enumerate(keyword_args, 118 start=len(positional_args)): 119 if i: 120 p.text(',') 121 p.breakable() 122 with p.group(len(keyword) + 1, keyword + '='): 123 p.pretty(getattr(self, keyword)) 124 125 126class ReprMixin(ReprMixinBase): 127 """Mixin to construct :code:`__repr__` for named arguments **automatically**. 128 129 :code:`_repr_pretty_` for :py:mod:`IPython.lib.pretty` is also constructed. 130 131 This class differs from :py:class:`~represent.core.ReprMixinBase` in that it 132 supports unpickling by providing ``__getstate__`` and ``__setstate__``, 133 ensuring :py:class:`~represent.core.ReprMixinBase` is initialised. 134 135 :param positional: Mark arguments as positional by number, or a list of 136 argument names. 137 138 .. versionchanged:: 1.2 139 ``RepresentationMixin`` renamed to ``ReprMixin`` 140 141 .. deprecated:: 1.5.0 142 143 Use the :func:`~represent.core.autorepr` class decorator instead. 144 """ 145 146 # To enable pickle support, we must ensure __init__ gets called. __new__ 147 # could be used instead, but we can only make pickle call __new__ when 148 # using protocol 2 and above. 149 # 150 # Provide default __getstate__ and __setstate__ which calls __init__ 151 # Subclasses that implement these must call ReprMixin.__init__ 152 # in __setstate__. 153 def __getstate__(self): 154 return (self.__class__._repr_positional, self.__dict__) 155 156 def __setstate__(self, d): 157 positional, real_dict = d 158 ReprMixin.__init__(self, positional) 159 self.__dict__.update(real_dict) 160