diff --git a/examples/user_guide/Parameters.ipynb b/examples/user_guide/Parameters.ipynb index eb5f9aa57..8d26d194c 100644 --- a/examples/user_guide/Parameters.ipynb +++ b/examples/user_guide/Parameters.ipynb @@ -442,12 +442,22 @@ "\n", "Parameter inheritance like this lets you (a) use a parameter in many subclasses without having to define it more than once, and (b) control the value of that parameter conveniently across the entire set of subclasses and instances, as long as that attribute has not been set on those objects already. Using inheritance in this way is a very convenient mechanism for setting default values and other \"global\" parameters, whether before a program starts executing or during it.\n", "\n", - "`help(b)` or `help(B)` will list all parameters:" + "`help(b)` or `help(B)` will list all parameters. You can also prefix or suffix a Parameterized object with `?` in an IPython console/Notebook to display the help:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd711290-b6f0-4e9e-bbce-c400da27a3c2", + "metadata": {}, + "outputs": [], + "source": [ + "B?" ] }, { "cell_type": "markdown", - "id": "16ad7973", + "id": "54ecbc24-14eb-4c07-8a00-0fb79b6241da", "metadata": {}, "source": [ "" @@ -781,10 +791,11 @@ "source": [ "## Displaying Parameterized objects\n", "\n", - "Most of the important behavior of Parameterized is to do with instantiation, getting, and setting, as described above. Parameterized also provides a few public methods for creating a string representation of the Parameterized object and its parameters:\n", + "Most of the important behavior of Parameterized is to do with instantiation, getting, and setting, as described above. Parameterized also provides a few public methods for creating string representations of the Parameterized object and its parameters:\n", "\n", "- `Parameterized.__str__()`: A concise, non-executable representation of the name and class of this object\n", "- `Parameterized.__repr__()`: A representation of this object and its parameter values as if it were Python code calling the constructor (`classname(parameter1=x,parameter2=y,...)`)\n", + "- `Parameterized._repr_html_()` and `Parameterize.param._repr_html_()`: A rich HTML representation of the object with its parameters listed in a table together with their metadata.\n", "- `Parameterized.param.pprint()`: Customizable, hierarchical pretty-printed representation of this Parameterized and (recursively) any of its parameters that are Parameterized objects. See [Serialization and Persistence](Serialization_and_Persistence.ipynb) for details on customizing `pprint`." ] }, @@ -798,13 +809,13 @@ "import param\n", "\n", "class Q(param.Parameterized):\n", - " a = param.Number(default=39, bounds=(0,50))\n", - " b = param.String(default=\"str\")\n", + " a = param.Number(default=39, bounds=(0,50), doc='Number a')\n", + " b = param.String(default=\"str\", doc='A string')\n", "\n", "class P(Q):\n", - " c = param.ClassSelector(default=Q(), class_=Q)\n", - " e = param.ClassSelector(default=param.Parameterized(), class_=param.Parameterized)\n", - " f = param.Range(default=(0,1))\n", + " c = param.ClassSelector(default=Q(), class_=Q, doc='An instance of Q')\n", + " e = param.ClassSelector(default=param.Parameterized(), class_=param.Parameterized, doc='A Parameterized instance')\n", + " f = param.Range(default=(0,1), doc='A range')\n", "\n", "p = P(f=(2,3), c=P(f=(42,43)), name=\"demo\")" ] @@ -829,6 +840,34 @@ "p.__repr__()" ] }, + { + "cell_type": "markdown", + "id": "799c1eeb-71c2-40a3-ad06-fd3ed8eda501", + "metadata": {}, + "source": [ + "The HTML representation of a `Parameterized` instance is automatically displayed in a Notebook. If the object you are displaying has overriden the `Parameterized._repr_html()` method to implement its own rich display - which is for instance the case for Panel components - call `.param` on your object to see its Param rich display." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c17a53b-3acc-4e6e-ab93-e5d09728e1c5", + "metadata": {}, + "outputs": [], + "source": [ + "p # or p.param" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64628d11-7f2c-4718-bd8a-7a381e282047", + "metadata": {}, + "outputs": [], + "source": [ + "P # or P.param" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/param/parameterized.py b/param/parameterized.py index 88836575c..1403ac5dd 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -24,10 +24,10 @@ except ImportError: serializer = None - from collections import defaultdict, namedtuple, OrderedDict from functools import partial, wraps, reduce -from operator import itemgetter,attrgetter +from html import escape +from operator import itemgetter, attrgetter from threading import get_ident from types import FunctionType, MethodType @@ -324,29 +324,24 @@ def wrapper(cls): return wrapper - -class bothmethod: # pylint: disable-msg=R0903 +class bothmethod: """ 'optional @classmethod' A decorator that allows a method to receive either the class object (if called on the class) or the instance object (if called on the instance) as its first argument. - - Code (but not documentation) copied from: - http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/523033. """ - # pylint: disable-msg=R0903 - - def __init__(self, func): - self.func = func + def __init__(self, method): + self.method = method - # i.e. this is also a non-data descriptor - def __get__(self, obj, type_=None): - if obj is None: - return wraps(self.func)(partial(self.func, type_)) + def __get__(self, instance, owner): + if instance is None: + # Class call + return self.method.__get__(owner) else: - return wraps(self.func)(partial(self.func, obj)) + # Instance call + return self.method.__get__(instance, owner) def _getattrr(obj, attr, *args): @@ -411,6 +406,29 @@ def get_method_owner(method): return method.__self__ +def recursive_repr(fillvalue='...'): + 'Decorator to make a repr function return fillvalue for a recursive call' + # Copy of Python 3.2 reprlib's recursive_repr but allowing extra arguments + + def decorating_function(user_function): + repr_running = set() + + @wraps(user_function) + def wrapper(self, *args, **kwargs): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self, *args, **kwargs) + finally: + repr_running.discard(key) + return result + return wrapper + + return decorating_function + + @accept_arguments def depends(func, *dependencies, watch=False, on_init=False, **kw): """Annotates a function or Parameterized method to express its dependencies. @@ -1948,6 +1966,10 @@ def _watch_group(self_, obj, name, queued, group, attribute=None): return dep_obj.param._watch( mcaller, params, param_dep.what, queued=queued, precedence=-1) + @recursive_repr() + def _repr_html_(self_, open=True): + return _parameterized_repr_html(self_.self_or_cls, open) + # Classmethods # PARAM3_DEPRECATION @@ -3300,9 +3322,9 @@ def type_script_repr(type_,imports,prefix,settings): imports.append('import %s'%module) return module+'.'+type_.__name__ -script_repr_reg[list]=container_script_repr -script_repr_reg[tuple]=container_script_repr -script_repr_reg[FunctionType]=function_script_repr +script_repr_reg[list] = container_script_repr +script_repr_reg[tuple] = container_script_repr +script_repr_reg[FunctionType] = function_script_repr #: If not None, the value of this Parameter will be called (using '()') @@ -3312,26 +3334,106 @@ def type_script_repr(type_,imports,prefix,settings): dbprint_prefix=None -# Copy of Python 3.2 reprlib's recursive_repr but allowing extra arguments -def recursive_repr(fillvalue='...'): - 'Decorator to make a repr function return fillvalue for a recursive call' +def _name_if_set(parameterized): + """Return the name of this Parameterized if explicitly set to other than the default""" + class_name = parameterized.__class__.__name__ + default_name = re.match('^'+class_name+'[0-9]+$', parameterized.name) + return '' if default_name else parameterized.name - def decorating_function(user_function): - repr_running = set() - def wrapper(self, *args, **kwargs): - key = id(self), get_ident() - if key in repr_running: - return fillvalue - repr_running.add(key) - try: - result = user_function(self, *args, **kwargs) - finally: - repr_running.discard(key) - return result - return wrapper +def _get_param_repr(key, val, p, truncate=40): + """HTML representation for a single Parameter object and its value""" + if hasattr(val, "_repr_html_"): + try: + value = val._repr_html_(open=False) + except: + value = val._repr_html_() + else: + rep = repr(val) + value = (rep[:truncate] + '..') if len(rep) > truncate else rep + + modes = [] + if p.constant: + modes.append('constant') + if p.readonly: + modes.append('read-only') + if getattr(p, 'allow_None', False): + modes.append('nullable') + mode = ' | '.join(modes) + if hasattr(p, 'bounds'): + bounds = p.bounds + elif hasattr(p, 'objects') and p.objects: + bounds = ', '.join(list(map(repr, p.objects))) + else: + bounds = '' + tooltip = f' class="param-doc-tooltip" data-tooltip="{escape(p.doc.strip())}"' if p.doc else '' + return ( + f'
Name | Type | {value_field} | Bounds/Objects | Mode |
---|