<jupyter_start><jupyter_text>How Param WorksParam seamlessly makes Python attributes have a much richer set of behaviors than they would otherwise, at once both more powerful (with automatic dynamic behavior) and more tightly controlled by the class author. It is natural to wonder how Param achieves this, especially given that it is a normal pure-Python library, not an alternative implementation of Python or a pre-processor. The answer is that Param makes extensive use of Python language features that allow tailoring the behavior of attribute getting and setting in sophisticated ways. You don't need to read any of the material on this page to use Param successfully, but it might help you understand what's going on "under the hood" for debugging or optimizing complex situations or for extending Param. DescriptorsA `Parameter` object is a type of Python "descriptor", i.e., an object that implements custom `__get__` and/or `__set__` behavior. When a descriptor is an attribute of a class, Python will invoke those custom methods instead of simply getting and setting the actual value of the attribute (i.e., the `Parameter` object). The [Python descriptor docs](https://docs.python.org/3/howto/descriptor.html) explain this process in detail, but briefly, let's consider a simple descriptor that returns how many times it has been accessed:<jupyter_code>class Count:
    def __init__(self, start=0):
        self._count=start
    
    def __get__(self, obj, objtype=None):
        self._count += 1
        return self._count
class C:
    x = 5
    y = Count(0)

c = C()

c.x, c.x, c.x, c.y, c.y, c.y<jupyter_output><empty_output><jupyter_text>As you can see, class attributes `x` and `y` here can both be used the same way, but `x` is a normal Python attribute, returning the fixed value `5` that was set on the class, while `y` is a descriptor and returns a computed value when accessed (rather than returning itself as you might think from the syntax), and thus gives a different value each time. Parameters are much more complex than the above example, but this descriptor support provides the underlying mechanism for having full-featured attribute behavior like dynamic values, bounds checking, and so on.  SlotsAs described in the [Parameters docs](Parameters.ipynb), Parameters can store a rich collection of metadata about each parameter. Storing a full object and associated dictionary of metadata for each class and instance attribute could get expensive (i.e., slow and using a lot of memory), so Parameters are implemented using [slots](https://docs.python.org/3/reference/datamodel.htmlslots). A slot is like a normal Python attribute, but instead of being stored in the convenient and flexible but slow `__dict__` attribute of the object, slots are stored in a fixed-size data structure `__slots__` that works like a C `struct`.  `__slots__` reserves just enough space to store these attributes, which can be accessed instantaneously rather than requiring a dictionary lookup (hash table search).Using `__slots__` requires special support for operations to copy and restore Parameters (e.g. for Python persistent storage pickling); see `__getstate__` and `__setstate__`. A Parameter defines at least these slots, with additional slots added for each subclass:```__slots__ = ['name', '_internal_name', 'default', 'doc', 'precedence',              'instantiate', 'constant', 'readonly', 'pickle_default_value',             'allow_None', 'per_instance', 'watchers', 'owner', '_label']```In most cases, you can just treat a Parameter's existing slots like attributes of the Parameter class; they work just the same as regular attributes except for speed and storage space. However, if you add a _new_ attribute to a Parameter class, you have to make sure that you also add it to the `__slots__` defined for that Parameter class, or you'll either get an error or else the Parameter will get an unnecessary full `__dict__` object just to hold the one new attribute.   MetaclassesAnother way `Parameter` and `Parameterized` differ from ordinary Python classes is that they specify a special [metaclass](https://docs.python.org/3/reference/datamodel.htmlmetaclasses) that determines how they behave. Just like you instantiate a Python class to get a Python object, you instantiate a Python metaclass to get a Python class. Most classes are instances of the default metaclass named `type`, but with a custom metaclass, you can define how every Python class with that metaclass will behave, at a fundamental level. The `ParameterMetaclass` is fairly simple, mainly overriding docstrings so that `help(someparam)` gives the declared documentation for the Parameter instance, rather than the less-useful docstring for the underlying class that it would otherwise display. This behavior is convenient, but not essential to the operation of Param. `ParameterizedMetaclass`, on the other hand, defines a lot of the behavior behind Param's features.  In particular, the metaclass implements the behavior for getting and setting parameter values at the class level, similar to how a descriptor controls such behavior at the instance level. Without the metaclass, setting the value of a class attribute to a scalar like `5` would wipe out the `Parameter` object rather than updating the default value. The metaclass thus performs the same role at the class level as descriptors do at the instance level. Descriptors allow setting the value of an instance attribute without overriding the `Parameter` object on that instance, and the metaclass allows setting the value of a class attribute without overridding the `Parameter` object on the class. All told, the ParameterizedMetaclass handles: - allowing Parameter default values to be set at the class level (as just described),- supporting inheriting Parameter objects from superclasses, - instantiating parameter default values into the class's dictionary (if needed)- populating the `name` slot of each Parameter by its attribute name in the class,- reporting whether a class has been declared to be abstract (useful for ignoring it in selectors),- various bookkeeping about dependencies and watchers, - generating docstrings at the class level for each Parameter in the class so that `help(parameterizedclass)` displays not just the class docstring but also information about the Parameters in it (or in superclasses)Thus much of how Param works depends on ParameterizedMetaclass.   Custom attribute accessThe above mechanisms let Param [customize attribute access](https://docs.python.org/3/reference/datamodel.htmlcustomizing-attribute-access) for dynamic behavior and control over user settings. As an example of how this all fits together, consider the following code:<jupyter_code>from param import Parameterized, Parameter

class A(Parameterized):
    p = Parameter(default=1, per_instance=False, instantiate=False)

a1 = A()
a2 = A()<jupyter_output><empty_output><jupyter_text>Here, a1 and a2 share one Parameter object (`A.__dict__['p']`), because `per_instance` is `False`:<jupyter_code>A.__dict__['p'] is a1.param.p is a2.param.p<jupyter_output><empty_output><jupyter_text>The default (class-attribute) value of p is stored in this Parameter object (`A.__dict__['p'].default`), but is accessible as `A.p` due to the Parameter being a descriptor:<jupyter_code>A.__dict__['p'].default
A.p<jupyter_output><empty_output><jupyter_text>If the value of `p` is set on `a1`, `a1`'s value of `p` is stored in the `a1` instance itself, under a specially mangled attribute name. The mangled name is called the `_internal_name` of the parameter, and is constructed from the "attrib name" of the parameter (i.e. `p` in this case) but modified so that it will not be confused with the underlying `Parameter` object `p`:<jupyter_code>a1.p=2
a1.__dict__['_p_param_value']<jupyter_output><empty_output><jupyter_text>When `a1.p` is requested, `a1.__dict__['_p_param_value']` is returned. When `a2.p` is requested, `_p_param_value` is not found in `a2.__dict__`, so `A.__dict__['p'].default` (i.e. `A.p`) is returned instead:<jupyter_code>a2.p<jupyter_output><empty_output><jupyter_text>Because the value for `a2.p` is returned from `A.p`, changing `A.p` will affect `a2.p`, but not `a1.p` since it has its own independent value:<jupyter_code>A.p=3
a2.p, a1.p<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.06823360115121717, 'fraction_numerical': 0.004077227485309989, 'mean_word_length': 5.196136701337296, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '317145', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2151, 'n_tokens_neox': 2113, 'n_words': 1121, 'path': 'examples/user_guide/How_Param_Works.ipynb', 'repo_name': 'ektar/param', 'size': '8339'}