• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PrincetonUniversity / PsyNeuLink / 15917088825

05 Jun 2025 04:18AM UTC coverage: 84.482% (+0.5%) from 84.017%
15917088825

push

github

web-flow
Merge pull request #3271 from PrincetonUniversity/devel

Devel

9909 of 12966 branches covered (76.42%)

Branch coverage included in aggregate %.

1708 of 1908 new or added lines in 54 files covered. (89.52%)

25 existing lines in 14 files now uncovered.

34484 of 39581 relevant lines covered (87.12%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.61
/psyneulink/core/globals/parameters.py
1
"""
2

3
.. _Parameter_Attributes:
4

5
PsyNeuLink `parameters <Parameter>` are objects that represent the user-modifiable parameters of a `Component`.
6
`Parameter`\\ s have names, default values, and other attributes that define how they are used in Compositions.
7
`Parameter` \\s also maintain and provide access to the data used in actual computations - `default values
8
<Parameter_Defaults>`, `current values <Parameter_Statefulness>`, `previous values <Parameter.history>`, and
9
`logged values <Log>`.
10

11

12
.. _Parameter_Defaults:
13

14
Defaults
15
========
16

17
The Defaults class is used to represent the default values for a `Component's parameters <Component_Parameters>`.
18
Parameters have two types of defaults: *instance* defaults and *class* defaults. Class defaults belong to a PsyNeuLink
19
class, and suggest valid types and shapes of Parameter values. Instance defaults belong to an instance of a PsyNeuLink
20
class, and are used to validate compatibility between this instance and other PsyNeuLink objects. For example, given a
21
`TransferMechanism` *t*:
22

23
    - instance defaults are accessible by ``t.defaults`` (e.g., for the `noise <TransferMechanism.noise>` parameter,
24
      ``t.defaults.noise.defaults.noise``)
25

26
    - class defaults are accessible by ``t.class_defaults`` or ``TransferMechanism.defaults`` (e.g.,
27
      ``t.class_defaults.noise`` or `TransferMechanism.defaults.noise`)
28

29
.. note::
30
    ``t.defaults.noise`` is shorthand for ``t.parameters.noise.default_value``, and they both refer to the default
31
    ``noise`` value for *t*
32

33
Default values are sometimes also used when the parameters value has not been specified; for example, a Component's
34
``defaults.variable`` is used as the input to a `Mechanism` if its `execute <Mechanism_Base.execute>` method is called
35
without any input specified, and similarly it is used for the `INPUT <NodeRole.INPUT>` `Nodes <Composition_Nodes>` of
36
a `Composition` which are not specified in the **inputs** argument of its `run <Composition.run>` method.
37

38
.. _Parameter_Statefulness:
39

40
Statefulness of Parameters
41
==========================
42

43
Parameters can have different values in different `execution contexts <Composition_Execution_Context>` in order to
44
ensure correctness of and allow access to `simulation <OptimizationControlMechanism_Execution>` calculations. As a
45
result, to inspect and use the values of a parameter, in general you need to know the execution context in which you
46
are interested. Much of the time, this execution context is likely to be a Composition:::
47

48
        >>> import psyneulink as pnl
49
        >>> c = pnl.Composition()
50
        >>> d = pnl.Composition()
51
        >>> t = pnl.TransferMechanism()
52
        >>> c.add_node(t)
53
        >>> d.add_node(t)
54

55
        >>> c.run({t: 5})
56
        [[array([5.])]]
57
        >>> print(t.value)
58
        [[5.]]
59

60
        >>> d.run({t: 10})
61
        [[array([10.])]]
62
        >>> print(t.value)
63
        [[10.]]
64

65
        >>> print(t.parameters.value.get(c))
66
        [[5.]]
67
        >>> print(t.parameters.value.get(d))
68
        [[10.]]
69

70

71
The TransferMechanism in the above snippet has a different `value <Component.value>` for each Composition it is run in.
72
This holds true for all of its `stateful Parameters <Component_Stateful_Parameters>`, so they can behave differently in
73
different execution contexts and can be modified by modulated `ModulatorySignal_Modulation`.
74

75
.. _Parameter_Dot_Notation:
76

77
.. note::
78
    The "dot notation" version - ``t.value`` - refers to the most recent execution context in which *t* was executed. In
79
    many cases, you can use this to get or set using the execution context you'd expect. However, in complex situations,
80
    or  if there is any doubt, it is best to explicitly specify the execution context using the parameter's `set
81
    <Parameter.set>` method (for a more complete descritpion of the differences between dot notation and the `set
82
    <Parameter.set>` method, see `BasicsAndPrimer_Parameters`.
83

84

85
.. technical_note::
86

87
    Developers must keep in mind state when writing new Components for PsyNeuLink. Any parameters or values that may
88
    change during a `run <Run_Overview>` must become stateful Parameters, or they are at risk of computational
89
    errors like those encountered in parallel programming.
90

91

92
Creating Parameters
93
^^^^^^^^^^^^^^^^^^^
94

95
To create new Parameters, reference this example of a new class *B*
96

97
::
98

99
    class B(A):
100
        class Parameters(A.Parameters):
101
            p = 1.0
102
            q = Parameter()
103

104
        def __init__(p=None, q=1.0):
105
            super(p=p, q=q)
106

107

108
- create an inner class Parameters on the Component, inheriting from the parent Component's Parameters class
109
- an instance of *B*.Parameters will be assigned to the parameters attribute of the class *B* and all instances of *B*
110
- each attribute on *B*.Parameters becomes a parameter (instance of the Parameter class)
111
    - as with *p*, specifying only a value uses default values for the attributes of the Parameter
112
    - as with *q*, specifying an explicit instance of the Parameter class allows you to modify the
113
      `Parameter attributes <Parameter_Attributes_Table>`
114
- default values for the parameters can be specified in the Parameters class body, or in the
115
  arguments for *B*.__init__. If both are specified and the values differ, an exception will be raised
116
- if you want assignments to parameter *p* to be validated, add a method _validate_p(value), \
117
  that returns None if value is a valid assignment, or an error string if value is not a valid assignment
118
    - NOTE: a validation method for *p* may reference other parameters \
119
        only if they are listed in *p*'s \
120
        `dependencies <Parameter.dependencies>`
121
- if you want all values set to *p* to be parsed beforehand, add a method _parse_p(value) that returns the parsed value
122
    - for example, convert to a numpy array or float
123

124
        ::
125

126
            def _parse_p(value):
127
                return np.asarray(value)
128

129
    - NOTE: parsers may not reference other parameters
130

131
- setters and getters (used for more advanced behavior than parsing) should both return the final value to return (getter) or set (setter)
132

133
    For example, `costs <ControlMechanism.costs>` of `ControlMechanism <ControlMechanism>` has a special
134
    getter method, which computes the cost on-the-fly:
135

136
        ::
137

138
            def _modulatory_mechanism_costs_getter(owning_component=None, context=None):
139
                try:
140
                    return [c.compute_costs(c.parameters.variable._get(context), context=context) for c in owning_component.control_signals]
141
                except TypeError:
142
                    return None
143

144
    and `matrix <RecurrentTransferMechanism.matrix>` of `RecurrentTransferMechanism` has a special setter method,
145
    which updates its `auto <RecurrentTransferMechanism.auto>` and `hetero <RecurrentTransferMechanism.hetero>` parameter values accordingly
146

147
        ::
148

149
            def _recurrent_transfer_mechanism_matrix_setter(value, owning_component=None, context=None):
150
                try:
151
                    value = get_matrix(value, owning_component.input_shapes[0], owning_component.input_shapes[0])
152
                except AttributeError:
153
                    pass
154

155
                if value is not None:
156
                    temp_matrix = value.copy()
157
                    owning_component.parameters.auto._set(np.diag(temp_matrix).copy(), context)
158
                    np.fill_diagonal(temp_matrix, 0)
159
                    owning_component.parameters.hetero._set(temp_matrix, context)
160

161
                return value
162

163
.. note::
164
    The specification of Parameters is intended to mirror the PNL class hierarchy. So, it is only necessary for each new class to declare
165
    Parameters that are new, or whose specification has changed from their parent's. Parameters not present in a given class can be inherited
166
    from parents, but will be overridden if necessary, without affecting the parents.
167

168

169
.. _Parameter_Special_Classes:
170

171
.. technical_note::
172
    Special Parameter Classes
173
    -------------------------
174
        `FunctionParameter` and `SharedParameter` are used to provide
175
        simpler access to some parameters of auxiliary components. They
176
        can be passed into the constructor of the owner, and then
177
        automatically passed when constructing the auxiliary component.
178
        The `values <Parameter.values>` of `SharedParameter`\\ s are
179
        shared via getter and  with those of their target `Parameter`.
180

181
        `SharedParameter`\\ s should only be used when there is a
182
        guarantee that their target will exist, given a specific
183
        Component. For example, it is acceptable that
184
        `TransferMechanism.integration_rate` is a `FunctionParameter`
185
        for the `rate` parameter of its
186
        `integrator_function<TransferMechanism.integrator_function>`,
187
        because all `TransferMechanism`\\ s have an integrator
188
        function, and all integrator functions have a `rate` parameter.
189
        It is also acceptable that
190
        `ControlSignal.intensity_cost_function` is a `FunctionParameter`
191
        corresponding to its function's
192
        `intensity_cost_fct <TransferWithCosts>` parameter, because a
193
        ControlSignal's function is always a `TransferWithCosts` and is
194
        not user-specifiable.
195

196
.. technical_note::
197
    _user_specified
198
    ---------------
199
        Currently, default argument values for __init__ methods of
200
        Components are all None to allow distinguishing between a
201
        default value a user actually passed in during construction and
202
        a default automatically assigned. Consider
203

204
            1. `pnl.TransferMechanism(noise=[[0, 0]])`
205
            2. `pnl.TransferMechanism(default_variable=[[0]], noise=[[0, 0]])`
206

207
        In case 1, the default variable is not specified by the user,
208
        and is `flexible <DefaultsFlexibility.FLEXIBLE>` as a result.
209
        Since noise indicates a shape, `[[0, 0]]` is implicitly assigned
210
        as the Mechanism's default variable. This is intended as a
211
        convenience specification. In case 2, the variable and noise are
212
        incompatible, and an error is raised.
213

214
Using Parameters
215
^^^^^^^^^^^^^^^^
216

217
Methods that are called during runtime in general must take *context* as an argument and must pass this *context* along to other
218
PNL methods. The most likely place this will come up would be for the *function* method on a PNL `Function` class, or *_execute* method on other
219
`Components`. Any getting and setting of stateful parameter values must use this *context*, and using standard attributes to store data
220
must be avoided at risk of causing computation errors. You may use standard attributes only when their values will never change during a
221
`Run <TimeScale.RUN>`.
222

223
You should avoid using `dot notation <Parameter_Dot_Notation>` in internal code, as it is ambiguous and can potentially break statefulness.
224

225
.. _Parameter_Attributes_Table:
226

227
`Parameter` **attributes**:
228

229
.. table:: **`Parameter` attributes**
230

231
+------------------+---------------+--------------------------------------------+-----------------------------------------+
232
|  Attribute Name  | Default value |                Description                 |                Dev notes                |
233
|                  |               |                                            |                                         |
234
+------------------+---------------+--------------------------------------------+-----------------------------------------+
235
|  default_value   |     None      |the default value of the Parameter          |                                         |
236
+------------------+---------------+--------------------------------------------+-----------------------------------------+
237
|       name       |     None      |the name of the Parameter                   |                                         |
238
+------------------+---------------+--------------------------------------------+-----------------------------------------+
239
|     stateful     |     True      |whether the parameter has different values  |                                         |
240
|                  |               |based on execution context                  |                                         |
241
+------------------+---------------+--------------------------------------------+-----------------------------------------+
242
|    modulable     |     False     |if True, the parameter can be modulated     |Currently this does not determine what   |
243
|                  |               |(if it belongs to a Mechanism or Projection |gets a ParameterPort, but in the future  |
244
|                  |               | it is assigned a `ParameterPort`)          |it should                                |
245
+------------------+---------------+--------------------------------------------+-----------------------------------------+
246
|    read_only     |     False     |whether the user should be able to set the  |Can be manually set, but will trigger a  |
247
|                  |               |value or not (e.g. variable and value are   |warning unless override=True             |
248
|                  |               |just for informational purposes).           |                                         |
249
+------------------+---------------+--------------------------------------------+-----------------------------------------+
250
|     aliases      |     None      |other names by which the parameter goes     |specify as a list of strings             |
251
|                  |               |(e.g. allocation is the same as variable for|                                         |
252
|                  |               |ControlSignal).                             |                                         |
253
+------------------+---------------+--------------------------------------------+-----------------------------------------+
254
|       user       |     True      |whether the parameter is something the user |                                         |
255
|                  |               |will care about (e.g. NOT context)          |                                         |
256
|                  |               |                                            |                                         |
257
+------------------+---------------+--------------------------------------------+-----------------------------------------+
258
|      values      |     None      |stores the parameter's values under         |                                         |
259
|                  |               |different execution contexts                |                                         |
260
+------------------+---------------+--------------------------------------------+-----------------------------------------+
261
|      getter      |     None      |hook that allows overriding the retrieval of|kwargs self, owning_component, and       |
262
|                  |               |values based on a supplied method           |context will be passed in if your        |
263
|                  |               |(e.g. _output_port_variable_getter)         |method uses them. modulated=True will be |
264
|                  |               |                                            |passed in when modulated values are      |
265
|                  |               |                                            |requested.                               |
266
|                  |               |                                            |self: the Parameter calling the setter   |
267
|                  |               |                                            |owning_component: the Component to which |
268
|                  |               |                                            |    the Parameter belongs                |
269
|                  |               |                                            |context: the context the setter is called|
270
|                  |               |                                            |    with                                 |
271
|                  |               |                                            |Getters must return the resulting value  |
272
+------------------+---------------+--------------------------------------------+-----------------------------------------+
273
|      setter      |     None      |hook that allows overriding the setting of  |should take a positional argument; kwargs|
274
|                  |               |values based on a supplied method (e.g.     |self, owning_component, and context      |
275
|                  |               |_recurrent_transfer_mechanism_matrix_setter)|will be passed in if your method uses    |
276
|                  |               |                                            |them. self - the Parameter calling the   |
277
|                  |               |                                            |setter; owning_component - the Component |
278
|                  |               |                                            |to which the Parameter belongs;          |
279
|                  |               |                                            |context - the context the                |
280
|                  |               |                                            |setter is called with; should return the |
281
|                  |               |                                            |value to be set                          |
282
+------------------+---------------+--------------------------------------------+-----------------------------------------+
283
|     loggable     |     True      |whether the parameter can be logged         |                                         |
284
+------------------+---------------+--------------------------------------------+-----------------------------------------+
285
|       log        |     None      |stores the log of the parameter if          |                                         |
286
|                  |               |applicable                                  |                                         |
287
+------------------+---------------+--------------------------------------------+-----------------------------------------+
288
|  log_condition   |     `OFF`     |the `LogCondition` for which the parameter  |                                         |
289
|                  |               |should be logged                            |                                         |
290
+------------------+---------------+--------------------------------------------+-----------------------------------------+
291
|     history      |     None      |stores the history of the parameter         |                                         |
292
|                  |               |(previous values)                           |                                         |
293
+------------------+---------------+--------------------------------------------+-----------------------------------------+
294
|history_max_length|       1       |the maximum length of the stored history    |                                         |
295
+------------------+---------------+--------------------------------------------+-----------------------------------------+
296
| fallback_value   | ParameterNo   | indicates the behavior of calls to         |                                         |
297
|                  | ValueError    | `Parameter.get` or `Parameter._get` when   |                                         |
298
|                  |               | the Parameter has no value in the          |                                         |
299
|                  |               | requested context. The default behavior is |                                         |
300
|                  |               | to raise a ParameterNoValueError. If set   |                                         |
301
|                  |               | to the keyword DEFAULT='default', the      |                                         |
302
|                  |               | Parameter's default value will be          |                                         |
303
|                  |               | returned. If set to some other value, that |                                         |
304
|                  |               | value will be returned.                    |                                         |
305
+------------------+---------------+--------------------------------------------+-----------------------------------------+
306

307

308

309
Class Reference
310
===============
311

312
"""
313

314
import collections
1✔
315
import copy
1✔
316
import functools
1✔
317
import inspect
1✔
318
import itertools
1✔
319
import logging
1✔
320
import types
1✔
321
import typing
1✔
322
import weakref
1✔
323

324
import toposort
1✔
325

326
from psyneulink.core.globals.context import Context, ContextError, ContextFlags, _get_time, handle_external_context
1✔
327
from psyneulink.core.globals.context import time as time_object
1✔
328
from psyneulink.core.globals.keywords import DEFAULT, SHARED_COMPONENT_TYPES
1✔
329
from psyneulink.core.globals.log import LogCondition, LogEntry, LogError
1✔
330
from psyneulink.core.globals.utilities import (
1✔
331
    call_with_pruned_args,
332
    convert_all_elements_to_np_array,
333
    create_union_set,
334
    get_alias_property_getter,
335
    get_alias_property_setter,
336
    get_deepcopy_with_shared,
337
    get_function_sig_default_value,
338
    is_numeric,
339
    safe_equals,
340
    try_extract_0d_array_item,
341
    unproxy_weakproxy,
342
    update_array_in_place,
343
)
344
from psyneulink.core.rpc.graph_pb2 import Entry, ndArray
1✔
345

346
__all__ = [
1✔
347
    'Defaults', 'get_validator_by_function', 'Parameter', 'ParameterAlias', 'ParameterError',
348
    'ParametersBase', 'parse_context', 'FunctionParameter', 'SharedParameter'
349
]
350

351
logger = logging.getLogger(__name__)
1✔
352

353

354
class ParameterError(Exception):
1✔
355
    pass
1✔
356

357

358
class ParameterNoValueError(ParameterError):
1✔
359
    def __init__(self, param=None, execution_id=None):
1✔
360
        message = "{0} '{1}'{2} has no value for execution_id {3}".format(
1✔
361
            type(param).__name__,
362
            param.name,
363
            param._owner_string,
364
            execution_id if not isinstance(execution_id, str) else f"'{execution_id}'"
365
        )
366
        super().__init__(message)
1✔
367

368

369
class ParameterInvalidSourceError(ParameterError):
1✔
370
    def __init__(self, param=None, detail=None):
1✔
371
        from psyneulink.core.components.component import ComponentsMeta
1✔
372

373
        if detail is None:
1!
374
            try:
1✔
375
                owner = param._owner._owner
1✔
NEW
376
            except AttributeError as e:
×
377
                raise AssertionError() from e
378

379
            attr_name = param.attribute_name
1✔
380
            try:
1✔
381
                attr_val = getattr(owner, attr_name)
1✔
NEW
382
            except AttributeError:
×
NEW
383
                detail = f"has no attribute '{attr_name}'"
×
384
            else:
385
                if attr_val is None:
1✔
386
                    detail = f"'{attr_name}' is None"
1✔
387
                elif not hasattr(attr_val, param.shared_parameter_name):
1✔
388
                    detail = f"'{attr_name}' {attr_val} has no attribute '{param.shared_parameter_name}'"
1✔
389
                elif isinstance(attr_val, ComponentsMeta):
1!
390
                    detail = f"'{attr_name}' {attr_val} is not yet instantiated"
1✔
391
                else:
NEW
392
                    detail = f"'{attr_name}' {attr_val} unspecified error"
×
393
        message = "Invalid source for {0} '{1}'{2}: {3}".format(
1✔
394
            type(param).__name__,
395
            param.name,
396
            param._owner_string,
397
            detail
398
        )
399
        super().__init__(message)
1✔
400

401

402
def _get_prefixed_method(obj, prefix, name, sep=''):
1✔
403
    try:
1✔
404
        return getattr(obj, f'{prefix}{sep}{name}')
1✔
405
    except AttributeError:
1✔
406
        return None
1✔
407

408

409
def get_validator_by_function(function):
1✔
410
    """
411
        Arguments
412
        ---------
413
            function
414
                a function that takes exactly one positional argument and returns `True` if that argument
415
                is a valid assignment, or `False` if that argument is not a valid assignment
416

417
        :return: A validation method for use with Parameters classes that rejects any assignment for which **function** returns False
418
        :rtype: types.FunctionType
419
    """
420
    def validator(self, value):
1✔
421
        if function(value):
1!
422
            return None
1✔
423
        else:
424
            return '{0} returned False'.format(function.__name__)
×
425

426
    return validator
1✔
427

428

429
def parse_context(context):
1✔
430
    """
431
        Arguments
432
        ---------
433
            context
434
                An execution context (context, Composition)
435

436
        :return: the context associated with **context**
437
    """
438
    try:
1✔
439
        return context.default_execution_id
1✔
440
    except AttributeError:
1✔
441
        return context
1✔
442

443

444
def copy_parameter_value(value, shared_types=None, memo=None):
1✔
445
    """
446
        Returns a copy of **value** used as the value or spec of a
447
        Parameter, with exceptions.
448

449
        For example, we assume that if we have a Component in an
450
        iterable, it is meant to be a pointer rather than something
451
        used in computation requiring it to be a "real" instance
452
        (like `Component.function`)
453

454
        e.g. in spec attribute or Parameter `Mechanism.input_ports`
455
    """
456
    if memo is None:
1✔
457
        memo = {}
1✔
458

459
    if SHARED_COMPONENT_TYPES not in memo:
1✔
460
        from psyneulink.core.components.component import Component, ComponentsMeta
1✔
461
        if shared_types is None:
1✔
462
            shared_types = (Component, ComponentsMeta)
1✔
463
        else:
464
            shared_types = tuple(shared_types)
1✔
465
        memo[SHARED_COMPONENT_TYPES] = shared_types
1✔
466

467
    # trying to deepcopy a bound method of a Component will deepcopy the
468
    # Component, but we treat these situations like references.
469
    # ex: GridSearch.search_function = GridSearch._traverse_grid
470
    method_owner = getattr(value, '__self__', None)
1✔
471
    if method_owner:
1✔
472
        memo[id(method_owner)] = method_owner
1✔
473

474
    try:
1✔
475
        return copy.deepcopy(value, memo)
1✔
476
    except TypeError as e:
1✔
477
        if 'pickle' in str(e):
1!
478
            return value
1✔
479
        else:
480
            raise
×
481

482

483
def get_init_signature_default_value(obj, parameter):
1✔
484
    """
485
        Returns:
486
            the default value of the **parameter** argument of
487
            the __init__ method of **obj** if it exists, or inspect._empty
488
    """
489
    # only use the signature if it's on the owner class, not a parent
490
    if '__init__' in obj.__dict__:
1✔
491
        return get_function_sig_default_value(obj.__init__, parameter)
1✔
492
    else:
493
        return inspect._empty
1✔
494

495

496
def check_user_specified(func):
1✔
497
    @functools.wraps(func)
1✔
498
    def check_user_specified_wrapper(self, *args, **kwargs):
1✔
499
        if 'params' in kwargs and kwargs['params'] is not None:
1✔
500
            orig_kwargs = copy.copy(kwargs)
1✔
501
            kwargs = {**kwargs, **kwargs['params']}
1✔
502
            del kwargs['params']
1✔
503
        else:
504
            orig_kwargs = kwargs
1✔
505

506
        # find the corresponding constructor in chained wrappers
507
        constructor = func
1✔
508
        while '__init__' not in constructor.__qualname__:
1!
509
            constructor = constructor.__wrapped__
×
510

511
        try:
1✔
512
            self._user_specified_args
1✔
513
        except AttributeError:
1✔
514
            self._prev_constructor = constructor if '__init__' in type(self).__dict__ else None
1✔
515
            self._user_specified_args = copy.copy(kwargs)
1✔
516
        else:
517
            # add args determined in constructor to user_specifed.
518
            # since some args are set by the values of other
519
            # user_specified args in a constructor, we label these as
520
            # user_specified also (ex. LCAMechanism hetero/competition)
521
            for k, v in kwargs.items():
1✔
522
                # we only know changes in passed parameter values after
523
                # calling the next __init__ in the hierarchy, so can
524
                # only check _prev_constructor
525
                if k not in self._user_specified_args and self._prev_constructor is not None:
1✔
526
                    prev_constructor_default = get_function_sig_default_value(
1✔
527
                        self._prev_constructor, k
528
                    )
529
                    if (
1✔
530
                        # arg value passed through constructor is
531
                        # different than default arg in signature
532
                        (
533
                            type(prev_constructor_default) != type(v)
534
                            or not safe_equals(prev_constructor_default, v)
535
                        )
536
                        # arg value is different than the value given
537
                        # from the previous constructor in the class
538
                        # hierarchy
539
                        and (
540
                            k not in self._prev_kwargs
541
                            or (
542
                                type(self._prev_kwargs[k]) != type(v)
543
                                or not safe_equals(self._prev_kwargs[k], v)
544
                            )
545
                        )
546
                    ):
547
                        # NOTE: this is a good place to identify
548
                        # potentially unnecessary/inconsistent default
549
                        # parameter settings in body of constructors
550
                        self._user_specified_args[k] = v
1✔
551

552
            self._prev_constructor = constructor
1✔
553

554
        self._prev_kwargs = kwargs
1✔
555
        return func(self, *args, **orig_kwargs)
1✔
556

557
    return check_user_specified_wrapper
1✔
558

559

560
def is_array_like(obj: typing.Any) -> bool:
1✔
561
    """
562
    Returns:
563
        bool: True if **obj** is a numpy-array-like object. False
564
        otherwise
565
    """
566
    return hasattr(obj, 'dtype')
1✔
567

568

569
def _owner_string(param_obj):
1✔
570
    # Parameter or ParametersTemplate
571
    try:
1✔
572
        param_owner = param_obj._owner
1✔
NEW
573
    except AttributeError:
×
NEW
574
        return ''
×
575

576
    # Parameter only (bypass its ParametersTemplate _owner)
577
    try:
1✔
578
        param_owner = param_owner._owner
1✔
579
    except AttributeError:
1✔
580
        pass
1✔
581

582
    if isinstance(param_owner, type):
1✔
583
        owner_string = f' of {param_owner}'
1✔
584
    else:
585
        owner_string = f' of {param_owner.name}'
1✔
586

587
        if hasattr(param_owner, 'owner') and param_owner.owner:
1✔
588
            owner_string += f' for {param_owner.owner.name}'
1✔
589
            if hasattr(param_owner.owner, 'owner') and param_owner.owner.owner:
1✔
590
                owner_string += f' of {param_owner.owner.owner.name}'
1✔
591

592
    return owner_string
1✔
593

594

595
# used in Parameter._set_value. Parameter names where a change in
596
# shape/type should cause deletion of corresponding compiled structs
597
# even if the values are not synced
598
addl_unsynced_parameter_names = {'value'}
1✔
599

600

601
class ParametersTemplate:
1✔
602
    _deepcopy_shared_keys = ['_parent', '_params', '_owner_ref', '_children']
1✔
603
    _values_default_excluded_attrs = {'user': False}
1✔
604

605
    def __init__(self, owner, parent=None):
1✔
606
        # using weakref to allow garbage collection of unused objects of this type
607
        self._owner = owner
1✔
608
        self._parent = parent
1✔
609
        if isinstance(self._parent, ParametersTemplate):
1✔
610
            self._parent._children.add(self)
1✔
611

612
        # create list of params currently existing
613
        self._params = set()
1✔
614
        try:
1✔
615
            parent_keys = list(self._parent._params)
1✔
616
        except AttributeError:
1✔
617
            parent_keys = dir(type(self))
1✔
618
        source_keys = dir(self) + parent_keys
1✔
619
        for k in source_keys:
1✔
620
            if self._is_parameter(k):
1✔
621
                self._params.add(k)
1✔
622

623
        self._children = weakref.WeakSet()
1✔
624

625
    def __repr__(self):
626
        return '{0} :\n{1}'.format(super().__repr__(), str(self))
627

628
    def __str__(self):
1✔
629
        return self.show()
1✔
630

631
    def __deepcopy__(self, memo):
1✔
632
        newone = get_deepcopy_with_shared(self._deepcopy_shared_keys)(self, memo)
1✔
633

634
        for name, param in self.values(show_all=True).items():
1✔
635
            if isinstance(param, ParameterAlias):
1✔
636
                source_name = param.source.name
1✔
637
                getattr(newone, name).source = getattr(newone, source_name)
1✔
638

639
        memo[id(self)] = newone
1✔
640
        return newone
1✔
641

642
    def __contains__(self, item):
1✔
643
        if item in self._params:
1✔
644
            return True
1✔
645

646
        for p in self._params:
1✔
647
            try:
1✔
648
                p_attr = getattr(self, p)
1✔
649
            except AttributeError:
1✔
650
                return False
1✔
651
            else:
652
                if item is p_attr:
1!
653
                    return True
×
654

655
        return False
1✔
656

657
    def __iter__(self):
1✔
658
        return iter([getattr(self, k) for k in self.values(show_all=True).keys()])
1✔
659

660
    def _is_parameter(self, param_name):
1✔
661
        if param_name[0] == '_':
1✔
662
            return False
1✔
663
        elif param_name in self._params:
1✔
664
            return True
1✔
665
        else:
666
            try:
1✔
667
                return not isinstance(getattr(self, param_name), (types.MethodType, types.BuiltinMethodType))
1✔
668
            except AttributeError:
1✔
669
                return True
1✔
670

671
    def _register_parameter(self, param_name):
1✔
672
        self._params.add(param_name)
1✔
673
        self._nonexistent_attr_cache.discard(param_name)
1✔
674

675
        for child in self._children:
1✔
676
            child._register_parameter(param_name)
1✔
677

678
    def _invalidate_nonexistent_attr_cache(self, attr):
1✔
679
        self._nonexistent_attr_cache.discard(attr)
1✔
680

681
        for child in self._children:
1✔
682
            child._invalidate_nonexistent_attr_cache(attr)
1✔
683

684
    def values(self, show_all=False):
1✔
685
        """
686
            Arguments
687
            ---------
688
                show_all : False
689
                    if `True`, includes non-`user<Parameter.user` parameters
690

691
            :return: a dictionary with {parameter name: parameter value} key-value pairs for each Parameter
692
        """
693
        result = {}
1✔
694
        for k in self._params:
1✔
695
            val = getattr(self, k)
1✔
696

697
            if show_all:
1✔
698
                result[k] = val
1✔
699
            else:
700
                # exclude any values that have an attribute/value pair listed in ParametersTemplate._values_default_excluded_attrs
701
                for excluded_key, excluded_val in self._values_default_excluded_attrs.items():
1✔
702
                    try:
1✔
703
                        if getattr(val, excluded_key) == excluded_val:
1✔
704
                            break
1✔
705
                    except AttributeError:
×
706
                        pass
×
707
                else:
708
                    result[k] = val
1✔
709

710
        return result
1✔
711

712
    def show(self, show_all=False):
1✔
713
        vals = self.values(show_all=show_all)
1✔
714
        return '(\n\t{0}\n)'.format('\n\t'.join(sorted(['{0} = {1},'.format(k, vals[k]) for k in vals])))
1✔
715

716
    def names(self, show_all=False):
1✔
717
        return sorted([p for p in self.values(show_all)])
1✔
718

719
    @property
1✔
720
    def _owner(self):
1✔
721
        return unproxy_weakproxy(self._owner_ref)
1✔
722

723
    @_owner.setter
1✔
724
    def _owner(self, value):
1✔
725
        try:
1✔
726
            self._owner_ref = weakref.proxy(value)
1✔
727
        except TypeError:
×
728
            self._owner_ref = value
×
729

730
    @property
1✔
731
    def _owner_string(self):
1✔
732
        return _owner_string(self)
1✔
733

734
    def _dependency_order_key(self, names=False):
1✔
735
        """
736
        Args:
737
            names (bool, optional): Whether sorting key is based on
738
            Parameter names or Parameter objects. Defaults to False.
739

740
        Returns:
741
            types.FunctionType: a function that may be passed in as sort
742
            key so that any Parameter is placed before its dependencies
743
        """
744
        parameter_function_ordering = list(toposort.toposort({
1✔
745
            p.name: p.dependencies for p in self if p.dependencies is not None
746
        }))
747
        parameter_function_ordering = list(
1✔
748
            itertools.chain.from_iterable(parameter_function_ordering)
749
        )
750

751
        if names:
1✔
752
            def ordering(p):
1✔
753
                try:
1✔
754
                    return parameter_function_ordering.index(p)
1✔
755
                except ValueError:
1✔
756
                    return -1
1✔
757
        else:
758
            def ordering(p):
1✔
759
                try:
1✔
760
                    return parameter_function_ordering.index(p.name)
1✔
761
                except ValueError:
1✔
762
                    return -1
1✔
763

764
        return ordering
1✔
765

766
    @property
1✔
767
    def _in_dependency_order(self):
1✔
768
        """
769
            Returns:
770
                list[Parameter] - a list of Parameters such that any
771
                Parameter is placed before all of its
772
                `dependencies <Parameter.dependencies>`
773
        """
774
        return sorted(self, key=self._dependency_order_key())
1✔
775

776

777
class Defaults(ParametersTemplate):
1✔
778
    """
779
        A class to simplify display and management of default values associated with the `Parameter`\\ s
780
        in a :class:`Parameters` class.
781

782
        With an instance of the Defaults class, *defaults*, *defaults.<param_name>* may be used to
783
        get or set the default value of the associated :class:`Parameters` object
784

785
        Attributes
786
        ----------
787
            owner
788
                the :class:`Parameters` object associated with this object
789
    """
790
    def __init__(self, owner, **kwargs):
1✔
791
        super().__init__(owner)
1✔
792

793
        try:
1✔
794
            vals = sorted(self.values(show_all=True).items())
1✔
795
            for k, v in vals:
1✔
796
                try:
1✔
797
                    setattr(self, k, kwargs[k])
1✔
798
                except KeyError:
1✔
799
                    pass
1✔
800
        except AttributeError:
1✔
801
            # this may occur if this ends up being assigned to a "base" parameters object
802
            # in this case it's not necessary to support kwargs assignment
803
            pass
1✔
804

805
    def __getattr__(self, attr):
1✔
806
        return getattr(self._owner.parameters, attr).default_value
1✔
807

808
    def __setattr__(self, attr, value):
1✔
809
        if (attr[:1] != '_'):
1✔
810
            param = getattr(self._owner.parameters, attr)
1✔
811
            param.default_value = value
1✔
812
        else:
813
            super().__setattr__(attr, value)
1✔
814

815
    def values(self, show_all=False):
1✔
816
        """
817
            Arguments
818
            ---------
819
                show_all : False
820
                    if `True`, includes non-`user<Parameter.user>` parameters
821

822
            :return: a dictionary with {parameter name: parameter value} key-value pairs corresponding to `owner`
823
        """
824
        return {k: v.default_value for (k, v) in self._owner.parameters.values(show_all=show_all).items()}
1✔
825

826

827
class ParameterBase(types.SimpleNamespace):
1✔
828
    def __lt__(self, other):
1✔
829
        return self.name < other.name
×
830

831
    def __gt__(self, other):
1✔
832
        return self.name > other.name
×
833

834
    def __eq__(self, other):
1✔
835
        return object.__eq__(self, other)
1✔
836

837
    def __hash__(self):
1✔
838
        return object.__hash__(self)
1✔
839

840
    @property
1✔
841
    def _owner_string(self):
1✔
842
        return _owner_string(self)
1✔
843

844

845
class Parameter(ParameterBase):
1✔
846
    """
847
    COMMENT:
848
        KDM 11/30/18: using nonstandard formatting below to ensure developer notes is below type in html
849
    COMMENT
850

851
    Attributes
852
    ----------
853
        default_value
854
            the default value of the Parameter.
855

856
            :default: None
857

858
        name
859
            the name of the Parameter.
860

861
            :default: None
862

863
        stateful
864
            whether the parameter has different values based on execution context.
865

866
            :default: True
867

868
        modulable
869
            if True, the parameter can be modulated; if the Parameter belongs to a `Mechanism <Mechanism>` or
870
            `Projection <Projection>`, it is assigned a `ParameterPort`.
871

872
            :default: False
873

874
            :Developer Notes: Currently this does not determine what gets a ParameterPort, but in the future it should
875

876
        modulation_combination_function
877
            specifies the function used in Port._get_combined_mod_val() to combine values for the parameter if
878
            it receives more than one ModulatoryProjections;  must be either the keyword *MULTIPLICATIVE*,
879
            *PRODUCT*, *ADDITIVE*, *SUM*, or a function that accepts an n dimensional array and retursn an n-1
880
            dimensional array.  If it is None, the an attempt is made to determine it from the an alias for the
881
            Parameter's name (i.e., if that is MULTIPLICATIVE_PARAM or ADDITIVE_PARAM);  otherwise the default
882
            behavior is determined by Port._get_combined_mod_val().
883

884
            :default: None
885

886
        read_only
887
            whether the user should be able to set the value or not
888
            (e.g. variable and value are just for informational purposes).
889

890
            :default: False
891

892
            :Developer Notes: Can be manually set, but will trigger a warning unless override=True
893

894
        function_arg
895
            TBD
896

897
            :default: False
898

899
        pnl_internal
900
            whether the parameter is an idiosyncrasy of PsyNeuLink or it is more intrinsic to the conceptual operation
901
            of the Component on which it resides
902

903
            :default: False
904

905
        aliases
906
            other names by which the parameter goes (e.g. allocation is the same as variable for ControlSignal).
907

908
            :type: list
909
            :default: None
910

911
            :Developer Notes: specify as a list of strings
912

913
        user
914
            whether the parameter is something the user will care about (e.g. NOT context).
915

916
            :default: True
917

918
        values
919
            stores the parameter's values under different execution contexts.
920

921
            :type: dict{execution_id: value}
922
            :default: None
923

924
        getter
925
            hook that allows overriding the retrieval of values based on a supplied method
926
            (e.g. _output_port_variable_getter).
927

928
            :type: types.FunctionType
929
            :default: None
930

931
            :Developer Notes: kwargs self, owning_component, and context will be passed in if your method uses them. self - the Parameter calling the setter; owning_component - the Component to which the Parameter belongs; context - the context the setter is called with; should return the value
932

933
        setter
934
            hook that allows overriding the setting of values based on a supplied method
935
            (e.g.  _recurrent_transfer_mechanism_matrix_setter).
936

937
            :type: types.FunctionType
938
            :default: None
939

940
            :Developer Notes: should take a positional argument; kwargs self, owning_component, and context will be passed in if your method uses them. self - the Parameter calling the setter; owning_component - the Component to which the Parameter belongs; context - the context the setter is called with; should return the value to be set
941

942
        loggable
943
            whether the parameter can be logged.
944

945
            :default: True
946

947
        log
948
            stores the log of the parameter if applicable.
949

950
            :type: dict{execution_id: deque([LogEntry])}
951
            :default: None
952

953
        log_condition
954
            the LogCondition for which the parameter should be logged.
955

956
            :type: `LogCondition`
957
            :default: `OFF <LogCondition.OFF>`
958

959
        delivery_condition
960
            the LogCondition for which the parameter shoud be delivered.
961

962
            :type: `LogCondition`
963
            :default: `OFF <LogCondition.OFF>`
964

965
        history
966
            stores the history of the parameter (previous values). Also see `get_previous`.
967

968
            :type: dict{execution_id: deque([LogEntry])}
969
            :default: None
970

971
        history_max_length
972
            the maximum length of the stored history.
973

974
            :default: 1
975

976
        history_min_length
977
            the minimum length of the stored history. generally this does not need to be
978
            overridden, but is used to indicate if parameter history is necessary to computation.
979

980
            :default: 0
981

982
        fallback_value
983
            indicates the behavior of calls to `Parameter.get` or
984
            `Parameter._get` when the Parameter has no value in the
985
            requested context. The default behavior is to raise a
986
            ParameterNoValueError. If set to the keyword
987
            DEFAULT='default', the Parameter's default value will be
988
            returned. If set to some other value, that value will be
989
            returned.
990

991
            :default: ParameterNoValueError
992

993
        retain_old_simulation_data
994
            if False, the Parameter signals to other PNL objects that any values generated during simulations may be
995
            deleted after they are no longer needed for computation; if True, the values should be saved for later
996
            inspection.
997

998
            :default: False
999

1000
        constructor_argument
1001
            if not None, this indicates the argument in the owning Component's
1002
            constructor that this Parameter corresponds to.
1003

1004
            :default: None
1005

1006
        valid_types
1007
            if not None, this contains a tuple of `type`\\ s that are acceptable
1008
            for values of this Parameter
1009

1010
            :default: None
1011

1012
        reference
1013
            if False, the Parameter is not used in computation for its
1014
            owning Component. Instead, it is just meant to store a value
1015
            that may be used to initialize other Components
1016

1017
            :default: False
1018

1019
            :Developer Notes: Parameters with Function values marked as
1020
            reference will not be automatically instantiated in
1021
            _instantiate_parameter_classes or validated for variable
1022
            shape
1023

1024
        dependencies
1025
            if not None, this contains a set of Parameter
1026
            names corresponding to Parameters whose values must be
1027
            instantiated before that of this Parameter
1028

1029
            :default: None
1030

1031
        initializer
1032
            the name of another Parameter that serves as this
1033
            Parameter's `initializer <StatefulFunction.initializers>`
1034

1035
            :default: None
1036

1037
        port
1038
            stores a reference to the ParameterPort that modulates this
1039
            Parameter, if applicable
1040

1041
            :default: None
1042

1043
        specify_none
1044
            if True, a user-specified value of None for this Parameter
1045
            will set the _user_specified flag to True
1046

1047
            :default: False
1048

1049
    """
1050
    # The values of these attributes will never be inherited from parent Parameters
1051
    # KDM 7/12/18: consider inheriting ONLY default_value?
1052
    _uninherited_attrs = {'name', 'values', 'history', 'log'}
1✔
1053

1054
    # for user convenience - these attributes will be hidden from the repr
1055
    # display if the function is True based on the value of the attribute
1056
    _hidden_if_unset_attrs = {
1✔
1057
        'aliases', 'getter', 'setter', 'constructor_argument', 'spec',
1058
        'modulation_combination_function', 'valid_types', 'initializer'
1059
    }
1060
    _hidden_if_false_attrs = {'read_only', 'modulable', 'fallback_value', 'retain_old_simulation_data'}
1✔
1061
    _hidden_when = {
1✔
1062
        **{k: lambda self, val: val is None for k in _hidden_if_unset_attrs},
1063
        **{k: lambda self, val: val is False for k in _hidden_if_false_attrs},
1064
        **{k: lambda self, val: self.loggable is False or self.log_condition is LogCondition.OFF for k in ['log', 'log_condition']},
1065
        **{k: lambda self, val: self.modulable is False for k in ['modulation_combination_function']},
1066
    }
1067

1068
    # for user convenience - these "properties" (see note below in _set_history_max_length)
1069
    # will be included as "param attrs" - the attributes of a Parameter that may be of interest to/settable by users
1070
    # To add an additional property-like param attribute, add its name here, and a _set_<param_name> method
1071
    # (see _set_history_max_length)
1072
    _additional_param_attr_properties = {
1✔
1073
        'default_value',
1074
        'history_max_length',
1075
        'log_condition',
1076
        'spec',
1077
    }
1078

1079
    def __init__(
1✔
1080
        self,
1081
        default_value=None,
1082
        name=None,
1083
        stateful=True,
1084
        modulable=False,
1085
        structural=False,
1086
        modulation_combination_function=None,
1087
        read_only=False,
1088
        function_arg=True,
1089
        pnl_internal=False,
1090
        aliases=None,
1091
        user=True,
1092
        values=None,
1093
        getter=None,
1094
        setter=None,
1095
        loggable=True,
1096
        log=None,
1097
        log_condition=LogCondition.OFF,
1098
        delivery_condition=LogCondition.OFF,
1099
        history=None,
1100
        history_max_length=1,
1101
        history_min_length=0,
1102
        fallback_value=ParameterNoValueError,
1103
        retain_old_simulation_data=False,
1104
        constructor_argument=None,
1105
        spec=None,
1106
        parse_spec=False,
1107
        valid_types=None,
1108
        reference=False,
1109
        dependencies=None,
1110
        initializer=None,
1111
        port=None,  # if modulated, set to the ParameterPort
1112
        mdf_name=None,
1113
        specify_none=False,
1114
        _owner=None,
1115
        _inherited=False,
1116
        # this stores a reference to the Parameter object that is the
1117
        # closest non-inherited parent. This parent is where the
1118
        # attributes will be taken from
1119
        _inherited_source=None,
1120
        _user_specified=False,
1121
        _scalar_converted=False,
1122
        _tracking_compiled_struct=False,
1123
        **kwargs
1124
    ):
1125
        if isinstance(aliases, str):
1✔
1126
            aliases = [aliases]
1✔
1127

1128
        if values is None:
1✔
1129
            values = {}
1✔
1130

1131
        if history is None:
1✔
1132
            history = {}
1✔
1133

1134
        if loggable and log is None:
1✔
1135
            log = {}
1✔
1136

1137
        if valid_types is not None:
1✔
1138
            if isinstance(valid_types, (list, tuple)):
1✔
1139
                valid_types = tuple(valid_types)
1✔
1140
            else:
1141
                valid_types = (valid_types, )
1✔
1142

1143
        if dependencies is not None:
1✔
1144
            dependencies = create_union_set(dependencies)
1✔
1145

1146
        super().__init__(
1✔
1147
            default_value=default_value,
1148
            name=name,
1149
            stateful=stateful,
1150
            modulable=modulable,
1151
            structural=structural,
1152
            modulation_combination_function=modulation_combination_function,
1153
            read_only=read_only,
1154
            function_arg=function_arg,
1155
            pnl_internal=pnl_internal,
1156
            aliases=aliases,
1157
            user=user,
1158
            values=values,
1159
            getter=getter,
1160
            setter=setter,
1161
            loggable=loggable,
1162
            log=log,
1163
            log_condition=log_condition,
1164
            delivery_condition=delivery_condition,
1165
            history=history,
1166
            history_max_length=history_max_length,
1167
            history_min_length=history_min_length,
1168
            fallback_value=fallback_value,
1169
            retain_old_simulation_data=retain_old_simulation_data,
1170
            constructor_argument=constructor_argument,
1171
            spec=spec,
1172
            parse_spec=parse_spec,
1173
            valid_types=valid_types,
1174
            reference=reference,
1175
            dependencies=dependencies,
1176
            initializer=initializer,
1177
            port=port,
1178
            mdf_name=mdf_name,
1179
            specify_none=specify_none,
1180
            _inherited=_inherited,
1181
            _inherited_source=_inherited_source,
1182
            _user_specified=_user_specified,
1183
            _temp_uninherited=set(),
1184
            _scalar_converted=_scalar_converted,
1185
            _tracking_compiled_struct=_tracking_compiled_struct,
1186
            **kwargs
1187
        )
1188

1189
        self._owner = _owner
1✔
1190
        self._param_attrs = [k for k in self.__dict__ if k[0] != '_'] \
1✔
1191
            + [k for k in self.__class__.__dict__ if k in self._additional_param_attr_properties]
1192

1193
        self._is_invalid_source = False
1✔
1194
        self._inherited_attrs_cache = {}
1✔
1195
        self.__inherited = False
1✔
1196
        self._inherited = _inherited
1✔
1197

1198
    def __repr__(self):
1199
        return '{0} :\n{1}'.format(super(types.SimpleNamespace, self).__repr__(), str(self))
1200

1201
    def __str__(self):
1✔
1202
        # modified from types.SimpleNamespace to exclude _-prefixed attrs
1203
        try:
1✔
1204
            items = (
1✔
1205
                "{}={!r}".format(k, getattr(self, k)) for k in sorted(self._param_attrs)
1206
                if k not in self._hidden_when or not self._hidden_when[k](self, getattr(self, k))
1207
            )
1208

1209
            return "{}(\n\t\t{}\n\t)".format(type(self).__name__, "\n\t\t".join(items))
1✔
1210
        except AttributeError:
×
1211
            return super().__str__()
×
1212

1213
    def __deepcopy__(self, memo):
1✔
1214
        result = type(self)(
1✔
1215
            **{
1216
                k: copy_parameter_value(getattr(self, k), memo=memo)
1217
                for k in self._param_attrs
1218
            },
1219
            _owner=self._owner,
1220
            _inherited=self._inherited,
1221
            _user_specified=self._user_specified,
1222
            _scalar_converted=self._scalar_converted,
1223
        )
1224

1225
        # make sure default values are always deepcopied
1226
        if (
1✔
1227
            not self._inherited
1228
            and id(self.default_value) in memo
1229
            and memo[id(self.default_value)] is self.default_value
1230
        ):
1231
            del memo[id(self.default_value)]
1✔
1232
            result._set_default_value(
1✔
1233
                copy_parameter_value(self.default_value, memo), directly=True
1234
            )
1235

1236
        memo[id(self)] = result
1✔
1237

1238
        return result
1✔
1239

1240
    def __getattr__(self, attr):
1✔
1241
        # runs when the object doesn't have an attr attribute itself
1242
        # attempt to get from its parent, which is also a Parameter
1243

1244
        # this is only called when self._inherited is True. We know
1245
        # there must be a source if attr exists at all. So, find it this
1246
        # time and only recompute lazily when the current source becomes
1247
        # inherited itself, or a closer parent becomes uninherited. Both
1248
        # will be indicated by the following conditional
1249
        try:
1✔
1250
            inherited_source = self._inherited_source()
1✔
1251
        except TypeError:
1✔
1252
            inherited_source = None
1✔
1253

1254
        if (
1✔
1255
            inherited_source is None
1256
            # this condition indicates the cache was invalidated
1257
            # since it was set
1258
            or inherited_source._is_invalid_source
1259
        ):
1260
            next_parent = self._parent
1✔
1261
            while next_parent is not None:
1✔
1262
                if not next_parent._is_invalid_source:
1✔
1263
                    self._inherit_from(next_parent)
1✔
1264
                    inherited_source = next_parent
1✔
1265
                    break
1✔
1266
                next_parent = next_parent._parent
1✔
1267

1268
        if inherited_source is None:
1✔
1269
            # will fail, use default behavior
1270
            return self.__getattribute__(attr)
1✔
1271
        else:
1272
            return inherited_source.__getattribute__(attr)
1✔
1273

1274
    def __setattr__(self, attr, value):
1✔
1275
        if attr in self._additional_param_attr_properties:
1✔
1276
            self._temp_uninherited.add(attr)
1✔
1277
            self._inherited = False
1✔
1278

1279
            try:
1✔
1280
                getattr(self, '_set_{0}'.format(attr))(value)
1✔
1281
            except AttributeError:
1✔
1282
                super().__setattr__(attr, value)
×
1283

1284
            self._temp_uninherited.remove(attr)
1✔
1285
        else:
1286
            super().__setattr__(attr, value)
1✔
1287

1288
    def reset(self):
1✔
1289
        """
1290
            Resets *default_value* to the value specified in its `Parameters` class declaration, or
1291
            inherits from parent `Parameters` classes if it is not explicitly specified.
1292
        """
1293
        # check for default in Parameters class
1294
        cls_param_value = inspect._empty
1✔
1295
        if self._owner._param_is_specified_in_class(self.name):
1✔
1296
            try:
1✔
1297
                cls_param_value = self._owner.__class__.__dict__[self.name]
1✔
1298
            except KeyError:
×
1299
                pass
×
1300
            else:
1301
                try:
1✔
1302
                    cls_param_value = cls_param_value.default_value
1✔
1303
                except AttributeError:
1✔
1304
                    pass
1✔
1305

1306
        # check for default in __init__ signature
1307
        value = self._owner._reconcile_value_with_init_default(self.name, cls_param_value)
1✔
1308
        if value is not inspect._empty:
1✔
1309
            self.default_value = value
1✔
1310
            return
1✔
1311

1312
        # no default specified, must be inherited or invalid
1313
        if self._parent is not None:
1✔
1314
            self._inherited = True
1✔
1315
            return
1✔
1316
        else:
1317
            raise ParameterError(
1318
                'Parameter {0} cannot be reset, as it does not have a default specification '
1319
                'or a parent. This may occur if it was added dynamically rather than in an'
1320
                'explict Parameters inner class on a Component'
1321
            )
1322

1323
    def _register_alias(self, name):
1✔
1324
        if self.aliases is None:
1!
1325
            self.aliases = [name]
×
1326
        elif name not in self.aliases:
1!
1327
            self.aliases.append(name)
×
1328

1329
    @property
1✔
1330
    def _inherited(self):
1✔
1331
        return self.__inherited
1✔
1332

1333
    @_inherited.setter
1✔
1334
    def _inherited(self, value):
1✔
1335
        if value is not self._inherited:
1✔
1336
            # invalid if set to inherited
1337
            self._is_invalid_source = value
1✔
1338
            self.__inherited = value
1✔
1339

1340
            if value:
1✔
1341
                self._cache_inherited_attrs()
1✔
1342
            else:
1343
                # This is a rare operation, so we can just immediately
1344
                # trickle down sources without performance issues.
1345
                children = [*self._owner._children]
1✔
1346
                while len(children) > 0:
1✔
1347
                    next_child = children.pop()
1✔
1348
                    next_child = getattr(next_child, self.name)
1✔
1349

1350
                    if next_child._inherited:
1✔
1351
                        next_child._inherit_from(self)
1✔
1352
                        children.extend(next_child._owner._children)
1✔
1353

1354
                self._restore_inherited_attrs()
1✔
1355

1356
    def _inherit_from(self, parent):
1✔
1357
        self._inherited_source = weakref.ref(parent)
1✔
1358

1359
    def _cache_inherited_attrs(self, exclusions=None):
1✔
1360
        if exclusions is None:
1✔
1361
            exclusions = set()
1✔
1362

1363
        exclusions = self._uninherited_attrs.union(self._temp_uninherited).union(exclusions)
1✔
1364

1365
        for attr in self._param_attrs:
1✔
1366
            if attr not in exclusions:
1✔
1367
                self._inherited_attrs_cache[attr] = getattr(self, attr)
1✔
1368
                delattr(self, attr)
1✔
1369

1370
    def _restore_inherited_attrs(self, exclusions=None):
1✔
1371
        if exclusions is None:
1✔
1372
            exclusions = set()
1✔
1373

1374
        exclusions = self._uninherited_attrs.union(self._temp_uninherited).union(exclusions)
1✔
1375

1376
        for attr in self._param_attrs:
1✔
1377
            if (
1✔
1378
                attr not in exclusions
1379
                and getattr(self, attr) is getattr(self._parent, attr)
1380
            ):
1381
                super().__setattr__(attr, self._inherited_attrs_cache[attr])
1✔
1382

1383
    @property
1✔
1384
    def _parent(self):
1✔
1385
        try:
1✔
1386
            return getattr(self._owner._parent, self.name)
1✔
1387
        except AttributeError:
1✔
1388
            return None
1✔
1389

1390
    def _validate(self, value):
1✔
1391
        return self._owner._validate(self.name, value)
1✔
1392

1393
    def _parse(self, value, check_scalar=False):
1✔
1394
        if is_numeric(value):
1✔
1395
            orig_value = value
1✔
1396
            value = convert_all_elements_to_np_array(value)
1✔
1397
            if check_scalar:
1✔
1398
                self._scalar_converted = orig_value is not value and value.ndim == 0
1✔
1399

1400
        return self._owner._parse(self.name, value)
1✔
1401

1402
    @property
1✔
1403
    def _default_getter_kwargs(self):
1✔
1404
        # self._owner: the Parameters object it belongs to
1405
        # self._owner._owner: the Component the Parameters object belongs to
1406
        # self._owner._owner.owner: that Component's owner if it exists
1407
        kwargs = {
1✔
1408
            'self': self,
1409
            'owning_component': self._owner._owner
1410
        }
1411
        try:
1✔
1412
            kwargs['owner'] = self._owner._owner.owner
1✔
1413
        except AttributeError:
1✔
1414
            pass
1✔
1415

1416
        return kwargs
1✔
1417

1418
    @property
1✔
1419
    def _default_setter_kwargs(self):
1✔
1420
        return self._default_getter_kwargs
1✔
1421

1422
    def _call_getter(self, context, **kwargs):
1✔
1423
        kwargs = {**self._default_getter_kwargs, **kwargs}
1✔
1424
        return call_with_pruned_args(self.getter, context=context, **kwargs)
1✔
1425

1426
    def _call_setter(self, value, context, compilation_sync, **kwargs):
1✔
1427
        kwargs = {
1✔
1428
            **self._default_setter_kwargs,
1429
            **kwargs,
1430
            'compilation_sync': compilation_sync,
1431
        }
1432
        return call_with_pruned_args(self.setter, value, context=context, **kwargs)
1✔
1433

1434
    @handle_external_context()
1✔
1435
    def get(self, context=None, fallback_value=ParameterNoValueError, **kwargs):
1✔
1436
        """
1437
            Gets the value of this `Parameter` in the context of **context**
1438
            If no context is specified, attributes on the associated `Component` will be used
1439

1440
            Arguments
1441
            ---------
1442

1443
                context : Context, execution_id, Composition
1444
                    the context for which the value is stored; if a Composition, uses **context**.default_execution_id
1445

1446
                fallback_value:
1447
                    overrides `Parameter.fallback_value` for this call
1448

1449
                kwargs
1450
                    any additional arguments to be passed to this `Parameter`'s `getter` if it exists
1451
        """
1452
        base_val = self._get(context, fallback_value, **kwargs)
1✔
1453
        if self._scalar_converted:
1✔
1454
            base_val = try_extract_0d_array_item(base_val)
1✔
1455
        if is_array_like(base_val):
1✔
1456
            base_val = copy_parameter_value(base_val)
1✔
1457
        return base_val
1✔
1458

1459
    def _get(self, context=None, fallback_value=ParameterNoValueError, **kwargs):
1✔
1460
        if not self.stateful:
1✔
1461
            execution_id = None
1✔
1462
        else:
1463
            try:
1✔
1464
                execution_id = context.execution_id
1✔
1465
            except AttributeError as e:
×
1466
                raise ParameterError(
1467
                    '_get must pass in a Context object as the context '
1468
                    'argument. To get parameter values using only an '
1469
                    'execution id, use get.'
1470
                ) from e
1471

1472
        if self.getter is not None:
1✔
1473
            value = self._call_getter(context, **kwargs)
1✔
1474
            if self.stateful:
1✔
1475
                self._set_value(value, execution_id=execution_id, context=context)
1✔
1476
            return value
1✔
1477
        else:
1478
            try:
1✔
1479
                return self.values[execution_id]
1✔
1480
            except KeyError as e:
1✔
1481
                if fallback_value is ParameterNoValueError:
1✔
1482
                    fallback_value = self.fallback_value
1✔
1483

1484
                if fallback_value is ParameterNoValueError:
1✔
1485
                    raise ParameterNoValueError(self, execution_id) from e
1486
                elif fallback_value == DEFAULT:
1✔
1487
                    return self.default_value
1✔
1488
                else:
1489
                    return fallback_value
1✔
1490

1491
    @handle_external_context()
1✔
1492
    def get_previous(
1✔
1493
        self,
1494
        context=None,
1495
        index: int = 1,
1496
        range_start: int = None,
1497
        range_end: int = None,
1498
    ):
1499
        """
1500
            Gets the value set before the current value of this
1501
            `Parameter` in the context of **context**. To return a range
1502
            of values, use `range_start` and `range_end`. Range takes
1503
            precedence over `index`. All history values can be accessed
1504
            directly as a list in `Parameter.history`.
1505

1506
            Args:
1507
                context : Context, execution_id, Composition
1508
                    the context for which the value is stored; if a
1509
                    Composition, uses **context**.default_execution_id
1510

1511
                index
1512
                    how far back to look into the history. An index of
1513
                    1 means the value this Parameter had just before
1514
                    its current value. An index of 2 means the value
1515
                    before that
1516

1517
                range_start
1518
                    Inclusive. The index of the oldest history value to
1519
                    return. If ``None``, the range begins at the oldest
1520
                    value stored in history
1521

1522
                range_end
1523
                    Inclusive. The index of the newest history value to
1524
                    return. If ``None``, the range ends at the newest
1525
                    value stored in history (does not include current
1526
                    value in `Parameter.values`)
1527

1528
            Returns:
1529
                the stored value or list of values in Parameter history
1530

1531
        """
1532
        def parse_index(x, arg_name=None):
1✔
1533
            try:
1✔
1534
                if x < 0:
1✔
1535
                    raise ValueError(f'{arg_name} cannot be negative')
1536
                return -x
1✔
1537
            except TypeError:
1✔
1538
                return x
1✔
1539

1540
        # inverted because the values represent "___ from the end of history"
1541
        index = parse_index(index, arg_name='index')
1✔
1542
        range_start = parse_index(range_start, arg_name='range_start')
1✔
1543

1544
        # override index with ranges
1545
        if range_start == range_end and range_start is not None:
1!
1546
            index = range_start
×
1547
            range_start = range_end = None
×
1548

1549
        # fix 0 to "-0" / None
1550
        if range_end == 0:
1✔
1551
            range_end = None
1✔
1552
        elif range_end is not None:
1✔
1553
            # range_end + 1 for inclusive range
1554
            range_end = range_end + 1
1✔
1555

1556
        if range_start is not None or range_end is not None:
1✔
1557
            try:
1✔
1558
                return list(self.history[context.execution_id])[range_start:range_end]
1✔
1559
            except (KeyError, IndexError):
×
1560
                return None
×
1561
        else:
1562
            try:
1✔
1563
                return self.history[context.execution_id][index]
1✔
1564
            except (KeyError, IndexError):
1✔
1565
                return None
1✔
1566

1567
    @handle_external_context()
1✔
1568
    def get_delta(self, context=None):
1✔
1569
        """
1570
            Gets the difference between the current value and previous value of `Parameter` in the context of **context**
1571

1572
            Arguments
1573
            ---------
1574

1575
                context : Context, execution_id, Composition
1576
                    the context for which the value is stored; if a Composition, uses **context**.default_execution_id
1577
        """
1578
        try:
1✔
1579
            return self.get(context) - self.get_previous(context)
1✔
1580
        except TypeError as e:
1✔
1581
            raise TypeError(
1582
                "Parameter '{0}' value mismatch between current ({1}) and previous ({2}) values".format(
1583
                    self.name,
1584
                    self.get(context),
1585
                    self.get_previous(context)
1586
                )
1587
            ) from e
1588

1589
    @handle_external_context()
1✔
1590
    def set(self, value, context=None, override=False, skip_history=False, skip_log=False, **kwargs):
1✔
1591
        """
1592
            Sets the value of this `Parameter` in the context of **context**
1593
            If no context is specified, attributes on the associated `Component` will be used
1594

1595
            Arguments
1596
            ---------
1597

1598
                context : Context, execution_id, Composition
1599
                    the context for which the value is stored; if a Composition, uses **context**.default_execution_id
1600
                override : False
1601
                    if True, ignores a warning when attempting to set a *read-only* Parameter
1602
                skip_history : False
1603
                    if True, does not modify the Parameter's *history*
1604
                skip_log : False
1605
                    if True, does not modify the Parameter's *log*
1606
                kwargs
1607
                    any additional arguments to be passed to this `Parameter`'s `setter` if it exists
1608
        """
1609
        from psyneulink.core.components.component import Component
1✔
1610

1611
        if not override and self.read_only:
1✔
1612
            raise ParameterError('Parameter \'{0}\' is read-only. Set at your own risk. Pass override=True to force set.'.format(self.name))
1613

1614
        value = self._parse(value, check_scalar=True)
1✔
1615
        value = self._set(value, context, skip_history, skip_log, **kwargs)
1✔
1616

1617
        try:
1✔
1618
            if isinstance(value.__self__, Component):
1!
1619
                value = value.__self__
1✔
1620
        except AttributeError:
1✔
1621
            pass
1✔
1622

1623
        if isinstance(value, Component):
1✔
1624
            owner = self._owner._owner
1✔
1625
            if value not in owner._parameter_components:
1!
1626
                if owner.initialization_status == ContextFlags.INITIALIZED:
1✔
1627
                    value._initialize_from_context(context)
1✔
1628
                    owner._parameter_components.add(value)
1✔
1629

1630
                    try:
1✔
1631
                        value._update_default_variable(owner._get_parsed_variable(self, context=context), context)
1✔
1632
                    except TypeError as e:
1✔
1633
                        if (
1!
1634
                            f'unsupported for {value.__class__.__name__}' not in str(e)
1635
                            and f'unsupported for {owner.__class__.__name__}' not in str(e)
1636
                        ):
1637
                            raise
×
1638

1639
        return value
1✔
1640

1641
    def _set(
1✔
1642
        self,
1643
        value,
1644
        context,
1645
        skip_history=False,
1646
        skip_log=False,
1647
        skip_delivery=False,
1648
        compilation_sync=False,
1649
        **kwargs,
1650
    ):
1651
        if not self.stateful:
1✔
1652
            execution_id = None
1✔
1653
        else:
1654
            try:
1✔
1655
                execution_id = context.execution_id
1✔
1656
            except AttributeError as e:
×
1657
                raise ParameterError(
1658
                    '_set must pass in a Context object as the context '
1659
                    'argument. To set parameter values using only an '
1660
                    'execution id, use set.'
1661
                ) from e
1662

1663
        if self.setter is not None:
1✔
1664
            value = self._call_setter(value, context, compilation_sync, **kwargs)
1✔
1665

1666
        self._set_value(
1✔
1667
            value,
1668
            execution_id=execution_id,
1669
            context=context,
1670
            skip_history=skip_history,
1671
            skip_log=skip_log,
1672
            skip_delivery=skip_delivery,
1673
            compilation_sync=compilation_sync,
1674
        )
1675

1676
        assert 'DEBUGGING BREAKPOINT: PARAMETER SETTING'
1✔
1677

1678
        return value
1✔
1679

1680
    def _set_value(
1✔
1681
        self,
1682
        value,
1683
        execution_id=None,
1684
        context=None,
1685
        skip_history=False,
1686
        skip_log=False,
1687
        skip_delivery=False,
1688
        compilation_sync=False,
1689
    ):
1690
        value_is_array_like = is_array_like(value)
1✔
1691
        # store history
1692
        if not skip_history:
1✔
1693
            if execution_id in self.values:
1✔
1694
                value_for_history = self.values[execution_id]
1✔
1695
                if value_is_array_like:
1✔
1696
                    value_for_history = copy_parameter_value(value_for_history)
1✔
1697

1698
                try:
1✔
1699
                    self.history[execution_id].append(value_for_history)
1✔
1700
                except KeyError:
1✔
1701
                    self.history[execution_id] = collections.deque(
1✔
1702
                        [value_for_history],
1703
                        maxlen=self.history_max_length,
1704
                    )
1705

1706
        if self.loggable:
1✔
1707
            value_for_log = value
1✔
1708
            if value_is_array_like:
1✔
1709
                value_for_log = copy_parameter_value(value)
1✔
1710
            # log value
1711
            if not skip_log:
1✔
1712
                self._log_value(value_for_log, context)
1✔
1713
            # Deliver value to external application
1714
            if not skip_delivery:
1!
1715
                self._deliver_value(value_for_log, context)
1✔
1716

1717
        value_updated = False
1✔
1718
        if not compilation_sync:
1✔
1719
            try:
1✔
1720
                update_array_in_place(self.values[execution_id], value)
1✔
1721
            except (KeyError, TypeError, ValueError):
1✔
1722
                # no self.values for execution_id
1723
                # failure during attempted update
1724
                pass
1✔
1725
            except RuntimeError:
×
1726
                # torch tensor
1727
                pass
×
1728
            else:
1729
                value_updated = True
1✔
1730

1731
        if not value_updated:
1✔
1732
            self.values[execution_id] = value
1✔
1733

1734
            if compilation_sync:
1✔
1735
                self._tracking_compiled_struct = True
1✔
1736
            elif (
1✔
1737
                value_is_array_like
1738
                and (
1739
                    self._tracking_compiled_struct
1740
                    or self.name in addl_unsynced_parameter_names
1741
                )
1742
            ):
1743
                # recompilation is needed for arrays that could not be
1744
                # updated in place
1745
                try:
1✔
1746
                    owner_comps = self._owner._owner.compositions
1✔
1747
                except AttributeError:
1✔
1748
                    pass
1✔
1749
                else:
1750
                    for comp in owner_comps:
1✔
1751
                        comp._delete_compilation_data(context, self)
1✔
1752
                self._tracking_compiled_struct = False
1✔
1753

1754
    @handle_external_context()
1✔
1755
    def delete(self, context=None):
1✔
1756
        try:
1✔
1757
            del self.values[context.execution_id]
1✔
1758
        except KeyError:
1✔
1759
            pass
1✔
1760

1761
        try:
1✔
1762
            del self.history[context.execution_id]
1✔
1763
        except KeyError:
1✔
1764
            pass
1✔
1765

1766
        self.clear_log(context.execution_id)
1✔
1767

1768
    def _has_value(self, context: Context):
1✔
1769
        return context.execution_id in self.values
1✔
1770

1771
    def _log_value(self, value, context=None):
1✔
1772
        # manual logging
1773
        if context is not None and context.source is ContextFlags.COMMAND_LINE:
1✔
1774
            try:
1✔
1775
                time = _get_time(self._owner._owner, context)
1✔
1776
            except (AttributeError, ContextError):
1✔
1777
                time = time_object(None, None, None, None)
1✔
1778

1779
            # this branch only ran previously when context was ContextFlags.COMMAND_LINE
1780
            context_str = ContextFlags._get_context_string(ContextFlags.COMMAND_LINE)
1✔
1781
            log_condition_satisfied = True
1✔
1782

1783
        # standard logging
1784
        else:
1785
            if self.log_condition is None or self.log_condition is LogCondition.OFF:
1✔
1786
                return
1✔
1787

1788
            if context is None:
1!
1789
                context = self._owner._owner.most_recent_context
×
1790

1791
            time = _get_time(self._owner._owner, context)
1✔
1792
            context_str = ContextFlags._get_context_string(context.flags)
1✔
1793
            log_condition_satisfied = self.log_condition & context.flags
1✔
1794

1795
        if (
1✔
1796
            not log_condition_satisfied
1797
            and self.log_condition & LogCondition.INITIALIZATION
1798
            and self._owner._owner.initialization_status is ContextFlags.INITIALIZING
1799
        ):
1800
            log_condition_satisfied = True
1✔
1801

1802
        if log_condition_satisfied:
1✔
1803
            if not self.stateful:
1✔
1804
                execution_id = None
1✔
1805
            else:
1806
                execution_id = context.execution_id
1✔
1807

1808
            if execution_id not in self.log:
1✔
1809
                self.log[execution_id] = collections.deque([])
1✔
1810

1811
            self.log[execution_id].append(
1✔
1812
                LogEntry(time, context_str, value)
1813
            )
1814

1815
    def _deliver_value(self, value, context=None):
1✔
1816
        # if a context is attached and a pipeline is attached to the context
1817
        if context and context.rpc_pipeline:
1✔
1818
            # manual delivery
1819
            if context.source is ContextFlags.COMMAND_LINE:
1✔
1820
                try:
1✔
1821
                    time = _get_time(self._owner._owner, context)
1✔
1822
                except (AttributeError, ContextError):
×
1823
                    time = time_object(None, None, None, None)
×
1824
                delivery_condition_satisfied = True
1✔
1825

1826
            # standard logging
1827
            else:
1828
                if self.delivery_condition is None or self.delivery_condition is LogCondition.OFF:
1✔
1829
                    return
1✔
1830

1831
                time = _get_time(self._owner._owner, context)
1✔
1832
                delivery_condition_satisfied = self.delivery_condition & context.flags
1✔
1833

1834
            if (
1!
1835
                not delivery_condition_satisfied
1836
                and self.delivery_condition & LogCondition.INITIALIZATION
1837
                and self._owner._owner.initialization_status is ContextFlags.INITIALIZING
1838
            ):
1839
                delivery_condition_satisfied = True
×
1840

1841
            if delivery_condition_satisfied:
1✔
1842
                if not self.stateful:
1!
1843
                    execution_id = None
×
1844
                else:
1845
                    execution_id = context.execution_id
1✔
1846
                # ADD TO PIPELINE HERE
1847
                context.rpc_pipeline.put(
1✔
1848
                    Entry(
1849
                        componentName=self._get_root_owner().name,
1850
                        parameterName=self._get_root_parameter().name,
1851
                        time=f'{time.run}:{time.trial}:{time.pass_}:{time.time_step}',
1852
                        context=execution_id,
1853
                        value=ndArray(
1854
                            shape=list(value.shape),
1855
                            data=list(value.flatten())
1856
                        )
1857
                    )
1858
                )
1859

1860
    def _get_root_owner(self):
1✔
1861
        owner = self
1✔
1862
        while True:
1863
            if hasattr(owner, '_owner'):
1✔
1864
                owner = owner._owner
1✔
1865
            else:
1866
                return owner
1✔
1867

1868
    def _get_root_parameter(self):
1✔
1869
        root = self._get_root_owner()
1✔
1870
        return self._owner._owner if not self._owner._owner == root else self
1✔
1871

1872
    def clear_log(self, contexts=NotImplemented):
1✔
1873
        """
1874
            Clears the log of this Parameter for every context in **contexts**
1875
        """
1876
        if self.log is None:
1✔
1877
            return
1✔
1878

1879
        if contexts is NotImplemented:
1✔
1880
            self.log.clear()
1✔
1881
            return
1✔
1882

1883
        if not isinstance(contexts, list):
1✔
1884
            contexts = [contexts]
1✔
1885

1886
        contexts = [parse_context(c) for c in contexts]
1✔
1887
        execution_ids = [
1✔
1888
            c.execution_id if hasattr(c, 'execution_id') else c
1889
            for c in contexts
1890
        ]
1891

1892
        try:
1✔
1893
            for eid in execution_ids:
1✔
1894
                self.log.pop(eid, None)
1✔
1895
        except TypeError:
×
1896
            self.log.pop(execution_ids, None)
×
1897

1898
    def clear_history(
1✔
1899
        self,
1900
        contexts: typing.Union[Context, typing.List[Context]] = NotImplemented
1901
    ):
1902
        """
1903
            Clears the history of this Parameter for every context in
1904
            `contexts`
1905

1906
            Args:
1907
                contexts
1908
        """
1909
        if not isinstance(contexts, list):
1!
1910
            contexts = [contexts]
1✔
1911

1912
        contexts = [parse_context(c) for c in contexts]
1✔
1913
        execution_ids = [
1✔
1914
            c.execution_id if hasattr(c, 'execution_id') else c
1915
            for c in contexts
1916
        ]
1917

1918
        for eid in execution_ids:
1✔
1919
            try:
1✔
1920
                self.history[eid].clear()
1✔
1921
            except KeyError:
×
1922
                pass
×
1923

1924
    def _initialize_from_context(self, context=None, base_context=Context(execution_id=None), override=True):
1✔
1925
        try:
1✔
1926
            try:
1✔
1927
                cur_val = self.values[context.execution_id]
1✔
1928
            except KeyError:
1✔
1929
                cur_val = None
1✔
1930

1931
            if cur_val is None or override:
1✔
1932
                try:
1✔
1933
                    new_val = self.values[base_context.execution_id]
1✔
1934
                except KeyError:
1✔
1935
                    return
1✔
1936

1937
                try:
1✔
1938
                    new_history = self.history[base_context.execution_id]
1✔
1939
                except KeyError:
1✔
1940
                    new_history = NotImplemented
1✔
1941

1942
                new_val = copy_parameter_value(new_val)
1✔
1943
                self.values[context.execution_id] = new_val
1✔
1944

1945
                if new_history is None:
1✔
1946
                    raise ParameterError('history should always be a collections.deque if it exists')
1947
                elif new_history is not NotImplemented:
1✔
1948
                    # shallow copy is OK because history should not change
1949
                    self.history[context.execution_id] = copy.copy(new_history)
1✔
1950

1951
        except ParameterError as e:
×
1952
            raise ParameterError('Error when attempting to initialize from {0}: {1}'.format(base_context.execution_id, e))
1953

1954
    # KDM 7/30/18: the below is weird like this in order to use this like a property, but also include it
1955
    # in the interface for user simplicity: that is, inheritable (by this Parameter's children or from its parent),
1956
    # visible in a Parameter's repr, and easily settable by the user
1957
    def _set_default_value(self, value, directly=False, check_scalar=False):
1✔
1958
        """
1959
        Set default_value
1960

1961
        Args:
1962
            value: new default_value
1963
            directly (bool, optional): if False, passes **value**
1964
                through parse and validation steps. Defaults to False.
1965
            check_scalar (bool, optional): if True, sets
1966
                _scalar_converted attribute as appropriate for
1967
                **value**. Defaults to False.
1968
        """
1969
        if not directly:
1✔
1970
            value = self._parse(value, check_scalar=check_scalar)
1✔
1971
            self._validate(value)
1✔
1972

1973
        super().__setattr__('default_value', value)
1✔
1974

1975
    def _set_history_max_length(self, value):
1✔
1976
        if value < self.history_min_length:
1✔
1977
            raise ParameterError(f'Parameter {self._owner._owner}.{self.name} requires history of length at least {self.history_min_length}.')
1978
        super().__setattr__('history_max_length', value)
1✔
1979
        for execution_id in self.history:
1✔
1980
            self.history[execution_id] = collections.deque(self.history[execution_id], maxlen=value)
1✔
1981

1982
    def _set_log_condition(self, value):
1✔
1983
        if not isinstance(value, LogCondition):
1✔
1984
            if value is True:
1!
1985
                value = LogCondition.ALL_ASSIGNMENTS
1✔
1986
            else:
1987
                try:
×
1988
                    value = LogCondition.from_string(value)
×
1989
                except (AttributeError, LogError):
×
1990
                    try:
×
1991
                        value = LogCondition(value)
×
1992
                    except ValueError:
×
1993
                        # if this fails, value can't be interpreted as a LogCondition
1994
                        raise
×
1995

1996
        super().__setattr__('log_condition', value)
1✔
1997

1998
    def _set_spec(self, value):
1✔
1999
        if self.parse_spec:
1✔
2000
            value = self._parse(value)
1✔
2001
        super().__setattr__('spec', value)
1✔
2002

2003
    @property
1✔
2004
    def source(self):
1✔
2005
        return self
1✔
2006

2007
    @property
1✔
2008
    def final_source(self):
1✔
2009
        return self
1✔
2010

2011

2012
class _ParameterAliasMeta(type):
1✔
2013
    # these will not be taken from the source
2014
    _unshared_attrs = ['name', 'aliases']
1✔
2015

2016
    def __init__(self, *args, **kwargs):
1✔
2017
        super().__init__(*args, **kwargs)
1✔
2018
        for k in Parameter().__dict__:
1✔
2019
            if k not in self._unshared_attrs:
1✔
2020
                setattr(
1✔
2021
                    self,
2022
                    k,
2023
                    property(
2024
                        fget=get_alias_property_getter(k, attr='source'),
2025
                        fset=get_alias_property_setter(k, attr='source')
2026
                    )
2027
                )
2028

2029

2030
# TODO: may not completely work with history/history_max_length
2031
class ParameterAlias(ParameterBase, metaclass=_ParameterAliasMeta):
1✔
2032
    """
2033
        A counterpart to `Parameter` that represents a pseudo-Parameter alias that
2034
        refers to another `Parameter`, but has a different name
2035
    """
2036
    def __init__(self, source=None, name=None):
1✔
2037
        super().__init__(name=name)
1✔
2038

2039
        self.source = source
1✔
2040

2041
        try:
1✔
2042
            source._register_alias(name)
1✔
2043
        except AttributeError:
×
2044
            pass
×
2045

2046
    def __getattr__(self, attr):
1✔
2047
        return getattr(self.source, attr)
1✔
2048

2049
    # must override deepcopy despite it being essentially shallow
2050
    # because otherwise it will default to Parameter.__deepcopy__ and
2051
    # return an instance of Parameter
2052
    def __deepcopy__(self, memo):
1✔
2053
        result = ParameterAlias(source=self._source, name=self.name)
1✔
2054
        memo[id(self)] = result
1✔
2055

2056
        return result
1✔
2057

2058
    @property
1✔
2059
    def source(self):
1✔
2060
        return unproxy_weakproxy(self._source)
1✔
2061

2062
    @source.setter
1✔
2063
    def source(self, value):
1✔
2064
        try:
1✔
2065
            self._source = weakref.proxy(value)
1✔
2066
        except TypeError:
1✔
2067
            self._source = value
1✔
2068

2069

2070
def _SharedParameter_default_getter(self, context=None):
1✔
2071
    return self.source._get(context)
1✔
2072

2073

2074
def _SharedParameter_default_setter(value, self, context=None):
1✔
2075
    return self.source._set(value, context)
1✔
2076

2077

2078
class SharedParameter(Parameter):
1✔
2079
    """
2080
        A Parameter that is not a "true" Parameter of a Component but a
2081
        reference to a Parameter on one of the Component's attributes or
2082
        other Parameters. `Values <Parameter.values>` are shared via
2083
        getter and setter. Mainly used for more user-friendly access to
2084
        certain Parameters, as a sort of cross-object alias.
2085

2086
        .. technical_note::
2087
            See `above <Parameter_Special_Classes>` for when it is
2088
            appropriate to use a SharedParameter
2089

2090
        Attributes:
2091

2092
            shared_parameter_name
2093
                the name of the target Parameter on the owning
2094
                Component's `attribute_name` Parameter or attribute
2095

2096
                :type: str
2097
                :default: `Parameter.name`
2098

2099
            attribute_name
2100
                the name of the owning Component's Parameter or
2101
                attribute on which `shared_parameter_name` is the target
2102
                Parameter of this object
2103

2104
                :type: str
2105
                :default: 'function'
2106

2107
            primary
2108
                whether the default value specified in the
2109
                SharedParameter should take precedence over the default
2110
                value specified in its target
2111

2112
                :type: bool
2113
                :default: False
2114

2115
            getter
2116
                :type: types.FunctionType
2117
                :default: a function that returns the value of the \
2118
                *shared_parameter_name* parameter of the \
2119
                *attribute_name* Parameter/attribute of this \
2120
                Parameter's owning Component
2121

2122
            setter
2123
                :type: types.FunctionType
2124
                :default: a function that sets the value of the \
2125
                *shared_parameter_name* parameter of the \
2126
                *attribute_name* Parameter/attribute of this \
2127
                Parameter's owning Component and returns the set value
2128
    """
2129
    _additional_param_attr_properties = Parameter._additional_param_attr_properties.union({'name'})
1✔
2130
    _uninherited_attrs = Parameter._uninherited_attrs.union({'attribute_name', 'shared_parameter_name'})
1✔
2131
    # attributes that should not be inherited from source attr
2132
    _unsourced_attrs = {'default_value', 'primary', 'getter', 'setter', 'aliases'}
1✔
2133

2134
    def __init__(
1✔
2135
        self,
2136
        default_value=None,
2137
        attribute_name=None,
2138
        shared_parameter_name=None,
2139
        primary=False,
2140
        getter=_SharedParameter_default_getter,
2141
        setter=_SharedParameter_default_setter,
2142
        **kwargs
2143
    ):
2144

2145
        super().__init__(
1✔
2146
            default_value=default_value,
2147
            getter=getter,
2148
            setter=setter,
2149
            attribute_name=attribute_name,
2150
            shared_parameter_name=shared_parameter_name,
2151
            primary=primary,
2152
            _source_exists=False,
2153
            **kwargs
2154
        )
2155

2156
    def __getattr__(self, attr):
1✔
2157
        try:
1✔
2158
            if attr in self._unsourced_attrs:
1✔
2159
                raise AttributeError
2160
            return getattr(self.source, attr)
1✔
2161
        except AttributeError:
1✔
2162
            return super().__getattr__(attr)
1✔
2163

2164
    def __setattr__(self, attr, value):
1✔
2165
        if self._source_exists and attr in self._sourced_attrs:
1✔
2166
            setattr(self.source, attr, value)
1✔
2167
        else:
2168
            super().__setattr__(attr, value)
1✔
2169

2170
    def _cache_inherited_attrs(self):
1✔
2171
        super()._cache_inherited_attrs(
1✔
2172
            exclusions=self._sourced_attrs
2173
        )
2174

2175
    def _restore_inherited_attrs(self):
1✔
2176
        super()._restore_inherited_attrs(
1✔
2177
            exclusions=self._sourced_attrs
2178
        )
2179

2180
    def _set_name(self, name):
1✔
2181
        if self.shared_parameter_name is None:
1✔
2182
            self.shared_parameter_name = name
1✔
2183

2184
        super(Parameter, self).__setattr__('name', name)
1✔
2185

2186
    def _validate_source(self):
1✔
2187
        from psyneulink.core.components.component import Component, ComponentsMeta
1✔
2188

2189
        if self.source is None:
1✔
2190
            raise ParameterInvalidSourceError(self)
2191

2192
        if (
1✔
2193
            self.source is not None
2194
            and isinstance(self._owner._owner, Component)
2195
            and isinstance(self.source._owner._owner, ComponentsMeta)
2196
        ):
2197
            raise ParameterInvalidSourceError(self, detail=f'Instance to class {self._owner._owner} -> {self.source._owner._owner}')
2198

2199
    def _call_getter(self, context, **kwargs):
1✔
2200
        self._validate_source()
1✔
2201
        return super()._call_getter(context, **kwargs)
1✔
2202

2203
    def _call_setter(self, value, context, compilation_sync, **kwargs):
1✔
2204
        self._validate_source()
1✔
2205
        return super()._call_setter(value, context, compilation_sync, **kwargs)
1✔
2206

2207
    @handle_external_context()
1✔
2208
    def get_previous(
1✔
2209
        self,
2210
        context=None,
2211
        index: int = 1,
2212
        range_start: int = None,
2213
        range_end: int = None,
2214
    ):
2215
        return self.source.get_previous(context, index, range_start, range_end)
×
2216

2217
    @handle_external_context()
1✔
2218
    def get_delta(self, context=None):
1✔
2219
        return self.source.get_delta(context)
×
2220

2221
    @property
1✔
2222
    def source(self):
1✔
2223
        from psyneulink.core.components.component import Component, ComponentsMeta
1✔
2224

2225
        try:
1✔
2226
            owning_component = self._owner._owner
1✔
NEW
2227
        except AttributeError:
×
NEW
2228
            return None
×
2229

2230
        try:
1✔
2231
            obj = getattr(owning_component.parameters, self.attribute_name)
1✔
2232
            if obj.stateful:
1✔
2233
                raise ParameterError(
2234
                    f'Parameter {type(obj._owner._owner).__name__}.{self.attribute_name}'
2235
                    f' is the target object of {type(self).__name__}'
2236
                    f' {type(owning_component).__name__}.{self.name} and'
2237
                    f' cannot be stateful.'
2238
                )
2239
            obj = obj.values[None]
1✔
2240
        except AttributeError:
1✔
2241
            try:
1✔
2242
                obj = getattr(owning_component, self.attribute_name)
1✔
2243
            except AttributeError:
×
2244
                return None
×
UNCOV
2245
        except KeyError:
×
2246
            # KeyError means there is no stored value for this
2247
            # parameter, which should only occur when the source is
2248
            # desired for a descriptive parameter attribute value (e.g.
2249
            # stateful or loggable) and when either self._owner._owner
2250
            # is a type or is in the process of instantiating a
2251
            # Parameter for an instance of a Component
NEW
2252
            return None
×
2253

2254
        if (
1✔
2255
            isinstance(owning_component, Component)
2256
            and isinstance(obj, ComponentsMeta)
2257
        ):
2258
            # don't mix Parameters on instantiated objects with
2259
            # those on classes
2260
            return None
1✔
2261

2262
        try:
1✔
2263
            obj = getattr(obj.parameters, self.shared_parameter_name)
1✔
2264
            if not self._source_exists:
1✔
2265
                for p in self._param_attrs:
1✔
2266
                    if p not in self._uninherited_attrs and p not in self._unsourced_attrs:
1✔
2267
                        try:
1✔
2268
                            delattr(self, p)
1✔
2269
                        except AttributeError:
×
2270
                            pass
×
2271
                self._source_exists = True
1✔
2272
            return obj
1✔
2273
        except AttributeError:
1✔
2274
            return None
1✔
2275

2276
    @property
1✔
2277
    def final_source(self):
1✔
2278
        base_param = self
1✔
2279
        while isinstance(base_param, SharedParameter):
1✔
2280
            base_param = base_param.source
1✔
2281

2282
        return base_param
1✔
2283

2284
    @property
1✔
2285
    def _sourced_attrs(self):
1✔
2286
        return set([a for a in self._param_attrs if a not in self._unsourced_attrs])
1✔
2287

2288

2289
class FunctionParameter(SharedParameter):
1✔
2290
    """
2291
        A special (and most common) case `SharedParameter` that
2292
        references a Parameter on one of the Component's functions.
2293

2294
        Attributes:
2295

2296
            function_parameter_name
2297
                the name of the target Parameter on the owning
2298
                Component's `function_name` Parameter
2299

2300
                :type: str
2301
                :default: `Parameter.name`
2302

2303
            function_name
2304
                the name of the owning Component's Parameter on which
2305
                `function_parameter_name` is the target Parameter of
2306
                this object
2307

2308
                :type: str
2309
                :default: 'function'
2310
    """
2311
    _uninherited_attrs = SharedParameter._uninherited_attrs.union({'function_name', 'function_parameter_name'})
1✔
2312

2313
    def __init__(
1✔
2314
        self,
2315
        default_value=None,
2316
        function_parameter_name=None,
2317
        function_name='function',
2318
        primary=True,
2319
        **kwargs
2320
    ):
2321
        super().__init__(
1✔
2322
            default_value=default_value,
2323
            function_name=function_name,
2324
            function_parameter_name=function_parameter_name,
2325
            primary=primary,
2326
            **kwargs
2327
        )
2328

2329
    @property
1✔
2330
    def attribute_name(self):
1✔
2331
        return self.function_name
1✔
2332

2333
    @attribute_name.setter
1✔
2334
    def attribute_name(self, value):
1✔
2335
        self.function_name = value
×
2336

2337
    @property
1✔
2338
    def shared_parameter_name(self):
1✔
2339
        return self.function_parameter_name
1✔
2340

2341
    @shared_parameter_name.setter
1✔
2342
    def shared_parameter_name(self, value):
1✔
2343
        self.function_parameter_name = value
1✔
2344

2345

2346
# KDM 6/29/18: consider assuming that ALL parameters are stateful
2347
#   and that anything that you would want to set as not stateful
2348
#   are actually just "settings" or "preferences", like former prefs,
2349
#   PROJECTION_TYPE, PROJECTION_SENDER
2350
# classifications:
2351
#   stateful = False : Preference
2352
#   read_only = True : "something", computationally relevant but just for information
2353
#   user = False     : not something the user cares about but uses same infrastructure
2354
#
2355
# only current candidate for separation seems to be on stateful
2356
# for now, leave everything together. separate later if necessary
2357
class ParametersBase(ParametersTemplate):
1✔
2358
    """
2359
        Base class for inner `Parameters` classes on Components (see `Component.Parameters` for example)
2360
    """
2361
    _parsing_method_prefix = '_parse_'
1✔
2362
    _validation_method_prefix = '_validate_'
1✔
2363

2364
    def __init__(self, owner, parent=None):
1✔
2365
        self._initializing = True
1✔
2366
        self._nonexistent_attr_cache = set()
1✔
2367

2368
        super().__init__(owner=owner, parent=parent)
1✔
2369

2370
        aliases_to_create = {}
1✔
2371
        for param_name in copy.copy(self._params):
1✔
2372
            try:
1✔
2373
                param_value = getattr(self, param_name)
1✔
2374
            except AttributeError:
1✔
2375
                param_value = NotImplemented
1✔
2376

2377
            constructor_default = get_init_signature_default_value(self._owner, param_name)
1✔
2378

2379
            if (
1✔
2380
                (
2381
                    constructor_default is not None
2382
                    and constructor_default is not inspect._empty
2383
                )
2384
                or self._param_is_specified_in_class(param_name)
2385
            ):
2386
                # KDM 6/25/18: NOTE: this may need special handling if you're creating a ParameterAlias directly
2387
                # in a class's Parameters class
2388
                setattr(self, param_name, param_value)
1✔
2389
            else:
2390
                parent_param = getattr(self._parent, param_name)
1✔
2391
                if isinstance(parent_param, ParameterAlias):
1✔
2392
                    # store aliases we need to create here and then create them later, because
2393
                    # the param that the alias is going to refer to may not have been created yet
2394
                    # (the alias then may refer to the parent Parameter instead of the Parameter associated with this
2395
                    # Parameters class)
2396
                    aliases_to_create[param_name] = parent_param.source.name
1✔
2397
                else:
2398
                    new_param = copy.deepcopy(parent_param)
1✔
2399
                    new_param._owner = self
1✔
2400
                    new_param._inherited = True
1✔
2401

2402
                    setattr(self, param_name, new_param)
1✔
2403

2404
        for alias_name, source_name in aliases_to_create.items():
1✔
2405
            # getattr here and not above, because the alias may be
2406
            # iterated over before the source
2407
            setattr(self, alias_name, ParameterAlias(name=alias_name, source=getattr(self, source_name)))
1✔
2408

2409
        values = self.values(show_all=True)
1✔
2410
        for param, value in values.items():
1✔
2411
            self._validate(param, value.default_value)
1✔
2412

2413
            if value.dependencies is not None:
1✔
2414
                value.dependencies = {getattr(d, 'name', d) for d in value.dependencies}
1✔
2415
                assert all(dep in values for dep in value.dependencies), (
1✔
2416
                    f'invalid Parameter name among {param} dependencies: {value.dependencies}'
2417
                )
2418

2419
        self._initializing = False
1✔
2420

2421
    def _throw_attr_error(self, attr):
1✔
2422
        raise AttributeError(
2423
            f"No attribute '{attr}' exists in the parameter hierarchy{self._owner_string}."
2424
        ) from None
2425

2426
    def __getattr__(self, attr):
1✔
2427
        if (
1✔
2428
            attr in self._params
2429
            or attr in self._nonexistent_attr_cache
2430
            # attr can't be in __dict__ or __getattr__ would not be called
2431
            or (
2432
                self._parent is not None
2433
                and attr in self._parent._nonexistent_attr_cache
2434
            )
2435
        ):
2436
            self._nonexistent_attr_cache.add(attr)
1✔
2437
            self._throw_attr_error(attr)
1✔
2438

2439
        try:
1✔
2440
            return getattr(self._parent, attr)
1✔
2441
        except AttributeError:
1✔
2442
            self._nonexistent_attr_cache.add(attr)
1✔
2443
            self._throw_attr_error(attr)
1✔
2444

2445
    def __setattr__(self, attr, value):
1✔
2446
        # handles parsing: Parameter or ParameterAlias housekeeping if assigned, or creation of a Parameter
2447
        # if just a value is assigned
2448
        if not self._is_parameter(attr):
1✔
2449
            super().__setattr__(attr, value)
1✔
2450
        else:
2451
            if isinstance(value, Parameter):
1✔
2452
                is_new_parameter = False
1✔
2453

2454
                if value._owner is None:
1✔
2455
                    value._owner = self
1✔
2456
                    is_new_parameter = True
1✔
2457
                elif value._owner is not self and self._initializing:
1✔
2458
                    # case where no Parameters class defined on subclass
2459
                    # but default value overridden in __init__
2460
                    value = copy.deepcopy(value)
1✔
2461
                    value._owner = self
1✔
2462

2463
                if value.name is None:
1✔
2464
                    value.name = attr
1✔
2465

2466
                if self._initializing and not value._inherited:
1✔
2467
                    reconciled_value = self._reconcile_value_with_init_default(attr, value.default_value)
1✔
2468
                    value._set_default_value(reconciled_value, check_scalar=is_new_parameter)
1✔
2469

2470
                super().__setattr__(attr, value)
1✔
2471

2472
                if value.aliases is not None:
1✔
2473
                    conflicts = []
1✔
2474
                    for alias in value.aliases:
1✔
2475
                        # there is a conflict if a non-ParameterAlias exists
2476
                        # with the same name as the planned alias
2477
                        if alias in self:
1✔
2478
                            try:
1✔
2479
                                alias_param = getattr(self, alias)
1✔
2480
                            except AttributeError:
1✔
2481
                                continue
1✔
2482

2483
                            if not isinstance(alias_param, ParameterAlias):
1!
2484
                                conflicts.append(alias)
×
2485

2486
                        super().__setattr__(alias, ParameterAlias(source=getattr(self, attr), name=alias))
1✔
2487
                        self._register_parameter(alias)
1✔
2488

2489
                    if len(conflicts) == 1:
1✔
2490
                        raise ParameterError(
2491
                            f'Attempting to create an alias for the {value.name}'
2492
                            f' Parameter on {self._owner.__name__} that would'
2493
                            f' override the {conflicts[0]} Parameter. Instead,'
2494
                            f' create a {conflicts[0]} Parameter with alias {value.name}.'
2495
                        )
2496
                    elif len(conflicts) > 1:
1✔
2497
                        raise ParameterError(
2498
                            f'Attempting to create aliases for the {value.name}'
2499
                            f' Parameter on {self._owner.__name__} that would'
2500
                            f' override other Parameters: {sorted(conflicts)}'
2501
                        )
2502

2503
            elif isinstance(value, ParameterAlias):
1✔
2504
                if value.name is None:
1!
2505
                    value.name = attr
×
2506
                if isinstance(value.source, str):
1!
2507
                    try:
×
2508
                        value.source = getattr(self, value.source)
×
2509
                        value.source._register_alias(attr)
×
2510
                    except AttributeError:
×
2511
                        # developer error
2512
                        raise ParameterError(
2513
                            '{0}: Attempted to create an alias named {1} to {2} but attr {2} does not exist'.format(
2514
                                self, attr, value.source
2515
                            )
2516
                        )
2517
                super().__setattr__(attr, value)
1✔
2518
            else:
2519
                try:
1✔
2520
                    current_value = getattr(self, attr)
1✔
2521
                except AttributeError:
×
2522
                    current_value = None
×
2523

2524
                if self._initializing:
1✔
2525
                    value = self._reconcile_value_with_init_default(attr, value)
1✔
2526

2527
                # assign value to default_value
2528
                if isinstance(current_value, (Parameter, ParameterAlias)):
1✔
2529
                    # construct a copy because the original may be used as a base for reset()
2530
                    new_param = copy.deepcopy(current_value)
1✔
2531
                    # set _inherited before default_value because it will
2532
                    # restore from cache
2533
                    new_param._inherited = False
1✔
2534

2535
                    # the old/replaced Parameter should be discarded
2536
                    current_value._is_invalid_source = True
1✔
2537

2538
                else:
2539
                    new_param = Parameter(name=attr, _owner=self)
1✔
2540

2541
                super().__setattr__(attr, new_param)
1✔
2542
                new_param._set_default_value(value)
1✔
2543

2544
            self._validate(attr, getattr(self, attr).default_value)
1✔
2545
            self._register_parameter(attr)
1✔
2546

2547
        if (
1✔
2548
            (
2549
                attr[0] != '_'
2550
                or attr.startswith(self._parsing_method_prefix)
2551
                or attr.startswith(self._validation_method_prefix)
2552
            )
2553
            and not self._initializing
2554
        ):
2555
            # below does happen during deepcopy, but that should only
2556
            # happen on instances, which won't have _children
2557
            self._invalidate_nonexistent_attr_cache(attr)
1✔
2558

2559
    def _reconcile_value_with_init_default(self, attr, value):
1✔
2560
        constructor_default = get_init_signature_default_value(self._owner, attr)
1✔
2561
        if constructor_default is not None and constructor_default is not inspect._empty:
1✔
2562
            if (
1✔
2563
                value is None
2564
                or not self._param_is_specified_in_class(attr)
2565
                or (
2566
                    type(constructor_default) == type(value)
2567
                    and safe_equals(constructor_default, value)
2568
                )
2569
            ):
2570
                # TODO: consider placing a developer-focused warning here?
2571
                return constructor_default
1✔
2572
            else:
2573
                assert False, (
2574
                    'PROGRAM ERROR: '
2575
                    f'Conflicting default parameter values assigned for Parameter {attr} of {self._owner} in:'
2576
                    f'\n\t{self._owner}.Parameters: {value}'
2577
                    f'\n\t{self._owner}.__init__: {constructor_default}'
2578
                    f'\nRemove one of these assignments. Prefer removing the default_value of {attr} in {self._owner}.Parameters'
2579
                )
2580

2581
        return value
1✔
2582

2583
    def _param_is_specified_in_class(self, param_name):
1✔
2584
        return (
1✔
2585
            param_name in self.__class__.__dict__
2586
            and (
2587
                param_name not in self._parent.__class__.__dict__
2588
                or self._parent.__class__.__dict__[param_name] is not self.__class__.__dict__[param_name]
2589
            )
2590
        )
2591

2592
    def _get_parse_method(self, parameter):
1✔
2593
        """
2594
        Returns:
2595
            the parsing method for the **parameter** or for any Parameter
2596
            attribute (ex: 'modulable') if it exists, or None if it does
2597
            not
2598
        """
2599
        return _get_prefixed_method(self, self._parsing_method_prefix, parameter)
1✔
2600

2601
    def _get_validate_method(self, parameter):
1✔
2602
        """
2603
        Returns:
2604
            the validation method for the **parameter** or for any
2605
            Parameter attribute (ex: 'modulable') if it exists, or None
2606
            if it does not
2607
        """
2608
        return _get_prefixed_method(self, self._validation_method_prefix, parameter)
1✔
2609

2610
    def _validate(self, attr, value):
1✔
2611
        err_msg = None
1✔
2612

2613
        valid_types = getattr(self, attr).valid_types
1✔
2614
        if valid_types is not None:
1✔
2615
            if not isinstance(value, valid_types):
1!
2616
                err_msg = '{0} is an invalid type. Valid types are: {1}'.format(
×
2617
                    type(value),
2618
                    valid_types
2619
                )
2620

2621
        validation_method = self._get_validate_method(attr)
1✔
2622
        if validation_method is not None:
1✔
2623
            err_msg = validation_method(value)
1✔
2624
            # specifically check for False because None indicates a valid assignment
2625
            if err_msg is False:
1!
2626
                err_msg = '{0} returned False'.format(validation_method)
×
2627

2628
        if err_msg is not None:
1✔
2629
            raise ParameterError(
2630
                "Value ({0}) assigned to parameter '{1}' of {2}.parameters is not valid: {3}".format(
2631
                    value,
2632
                    attr,
2633
                    self._owner,
2634
                    err_msg
2635
                )
2636
            )
2637

2638
    def _parse(self, attr, value):
1✔
2639
        parse_method = self._get_parse_method(attr)
1✔
2640
        if parse_method is not None:
1✔
2641
            value = parse_method(value)
1✔
2642
        return value
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc