Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use sentinel value for inheriting parameter slots #605

Merged
merged 51 commits into from
Apr 12, 2023
Merged
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7f3bc09
Replace None with _Undefined in constructors, and move validation to …
jbednar Feb 11, 2022
8225ae4
Force TimeSampledFn to have a None default
jbednar Feb 11, 2022
6aca386
Reverted numbergen change
jbednar Feb 12, 2022
98cc7c8
Changed validation to accept _Undefined instead of being deferred
jbednar Feb 12, 2022
df0c9f7
Minor cleanup
jbednar Feb 12, 2022
c776609
Renamed _Undefined to Undefined
jbednar Feb 12, 2022
ce9b249
Replaced un() with _slot_defaults + getattribute
jbednar Feb 16, 2022
20215a4
Added support for callable default values
jbednar Mar 16, 2022
4633477
Merge branch 'main' into undefined
maximlt Feb 20, 2023
0ad7368
make dict_update private
maximlt Feb 20, 2023
5978e38
remove unused and broken check
maximlt Feb 20, 2023
8d5b095
set slot defaults for String
maximlt Feb 21, 2023
86e8d85
move _dict_update to parameterized
maximlt Feb 21, 2023
cd2537a
add _slots_defaults to Bytes
maximlt Feb 21, 2023
73a2d4b
update slot defaults of the direct super class
maximlt Feb 21, 2023
42266e7
update the docstring to refer to Undefined
maximlt Feb 21, 2023
208356f
update Bytes parameter to use the sentinel
maximlt Feb 21, 2023
9e95d28
make allow_None_default private
maximlt Feb 21, 2023
3d87987
Merge branch 'main' into undefined
maximlt Mar 12, 2023
cc6b1da
refactor setting allow_None
maximlt Mar 14, 2023
107df6f
refactor setting instantiate
maximlt Mar 14, 2023
5f28ac5
Merge branch 'main' into undefined
maximlt Mar 14, 2023
538e826
clean up
maximlt Mar 15, 2023
2379afb
fix comparison with Undefined
maximlt Mar 15, 2023
2298f03
undo FileSelector changes
maximlt Mar 15, 2023
8b5a5e0
re-set signature of Bytes
maximlt Mar 15, 2023
8fb5f09
all slots must have a default value
maximlt Mar 15, 2023
1077f2c
Merge branch 'main' into undefined
maximlt Mar 16, 2023
dd634a6
dynamic update of Tuple length
maximlt Mar 16, 2023
f765b56
define Selector default values
maximlt Mar 16, 2023
88f9a9d
define List default values
maximlt Mar 16, 2023
d1c44aa
fix List inheritance
maximlt Mar 16, 2023
15c3cd6
fix comments
maximlt Mar 16, 2023
1211088
update Number behavior test
maximlt Mar 16, 2023
3d9f6fd
fix Selector behavior and adjust tests
maximlt Mar 16, 2023
854a8cb
adjust tuple tests
maximlt Mar 16, 2023
59843b2
adjust parameterized tests
maximlt Mar 16, 2023
5175ad0
Merge branch 'main' into undefined
maximlt Mar 17, 2023
b522d4a
set Tuple length when default is provided
maximlt Mar 17, 2023
953edb4
raise when Undefined is compared
maximlt Mar 17, 2023
576e079
raise better error when a slot has no default value
maximlt Mar 17, 2023
c021efe
Apply suggestions from code review
jbednar Mar 25, 2023
58c688c
rename to _compute_length_of_default
maximlt Apr 4, 2023
f294b57
apply suggestion and rename
maximlt Apr 4, 2023
03b9f4c
rename selector default function
maximlt Apr 4, 2023
8a3e4f2
Merge branch 'main' into undefined
maximlt Apr 4, 2023
81cd1b4
validate the default attribute of Boolean
maximlt Apr 4, 2023
68b6e01
add __repr__ to Undefined
maximlt Apr 11, 2023
2e19f34
improve docs on inheritance
maximlt Apr 11, 2023
9e3f2a9
add docstring to _compute_selector_default
maximlt Apr 11, 2023
0ca0042
add comment on empty_default
maximlt Apr 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Replace None with _Undefined in constructors, and move validation to …
…the ParameterizedMetaclass
jbednar committed Feb 11, 2022
commit 7f3bc09045d17cf69c3fe488610330491f4e1d95
83 changes: 39 additions & 44 deletions param/__init__.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
import datetime as dt
import collections

from .parameterized import (
from .parameterized import ( _Undefined,
Parameterized, Parameter, String, ParameterizedFunction, ParamOverrides,
descendents, get_logger, instance_descriptor, basestring)

@@ -778,8 +778,8 @@ class Number(Dynamic):

__slots__ = ['bounds', 'softbounds', 'inclusive_bounds', 'set_hook', 'step']

def __init__(self, default=0.0, bounds=None, softbounds=None,
inclusive_bounds=(True,True), step=None, **params):
def __init__(self, default=0.0, bounds=_Undefined, softbounds=_Undefined,
inclusive_bounds=(True,True), step=_Undefined, **params):
"""
Initialize this parameter object and store the bounds.

@@ -792,7 +792,6 @@ def __init__(self, default=0.0, bounds=None, softbounds=None,
self.inclusive_bounds = inclusive_bounds
self.softbounds = softbounds
self.step = step
self._validate(default)

def __get__(self, obj, objtype):
"""
@@ -944,7 +943,7 @@ def _validate_step(self, val, step):
class Magnitude(Number):
"""Numeric Parameter required to be in the range [0.0-1.0]."""

def __init__(self, default=1.0, softbounds=None, **params):
def __init__(self, default=1.0, softbounds=_Undefined, **params):
Number.__init__(self, default=default, bounds=(0.0,1.0), softbounds=softbounds, **params)


@@ -977,22 +976,21 @@ class Tuple(Parameter):

__slots__ = ['length']

def __init__(self, default=(0,0), length=None, **params):
def __init__(self, default=(0,0), length=_Undefined, **params):
"""
Initialize a tuple parameter with a fixed length (number of
elements). The length is determined by the initial default
value, if any, and must be supplied explicitly otherwise. The
length is not allowed to change after instantiation.
"""
super(Tuple,self).__init__(default=default, **params)
if length is None and default is not None:
self.length = len(default)
elif length is None and default is None:
if length is _Undefined and self.default is not None:
self.length = len(self.default)
elif length is _Undefined and self.default is None:
raise ValueError("%s: length must be specified if no default is supplied." %
(self.name))
else:
self.length = length
self._validate(default)

def _validate_value(self, val, allow_None):
if val is None and allow_None:
@@ -1114,10 +1112,10 @@ class Composite(Parameter):

__slots__ = ['attribs', 'objtype']

def __init__(self, attribs=None, **kw):
if attribs is None:
def __init__(self, attribs=_Undefined, **kw):
if attribs is _Undefined:
attribs = []
super(Composite, self).__init__(default=None, **kw)
super(Composite, self).__init__(default=_Undefined, **kw)
self.attribs = attribs

def __get__(self, obj, objtype):
@@ -1192,12 +1190,12 @@ class Selector(SelectorBase):

# Selector is usually used to allow selection from a list of
# existing objects, therefore instantiate is False by default.
def __init__(self, objects=None, default=None, instantiate=False,
compute_default_fn=None, check_on_set=None,
allow_None=None, empty_default=False, **params):
def __init__(self, objects=_Undefined, default=_Undefined, instantiate=False,
compute_default_fn=_Undefined, check_on_set=_Undefined,
allow_None=_Undefined, empty_default=False, **params):

autodefault = None
if objects:
if objects is not _Undefined:
if is_ordered_dict(objects):
autodefault = list(objects.values())[0]
elif isinstance(objects, dict):
@@ -1209,9 +1207,9 @@ def __init__(self, objects=None, default=None, instantiate=False,
elif isinstance(objects, list):
autodefault = objects[0]

default = autodefault if (not empty_default and default is None) else default
default = autodefault if (not empty_default and default is _Undefined) else default

if objects is None:
if objects is _Undefined:
objects = []
if isinstance(objects, collections_abc.Mapping):
self.names = objects
@@ -1221,7 +1219,7 @@ def __init__(self, objects=None, default=None, instantiate=False,
self.objects = objects
self.compute_default_fn = compute_default_fn

if check_on_set is not None:
if check_on_set is not _Undefined:
self.check_on_set = check_on_set
elif len(objects) == 0:
self.check_on_set = False
@@ -1232,8 +1230,6 @@ def __init__(self, objects=None, default=None, instantiate=False,
default=default, instantiate=instantiate, **params)
# Required as Parameter sets allow_None=True if default is None
self.allow_None = allow_None
if default is not None and self.check_on_set is True:
self._validate(default)

# Note that if the list of objects is changed, the current value for
# this parameter in existing POs could be outside of the new range.
@@ -1255,6 +1251,10 @@ def _validate(self, val):
"""
val must be None or one of the objects in self.objects.
"""

if self.default is None or self.check_on_set is True:
return

if not self.check_on_set:
self._ensure_value_is_in_objects(val)
return
@@ -1305,7 +1305,7 @@ class ObjectSelector(Selector):
Deprecated. Same as Selector, but with a different constructor for
historical reasons.
"""
def __init__(self, default=None, objects=None, **kwargs):
def __init__(self, default=_Undefined, objects=_Undefined, **kwargs):
super(ObjectSelector,self).__init__(objects=objects, default=default,
empty_default=True, **kwargs)

@@ -1320,11 +1320,10 @@ class ClassSelector(SelectorBase):

__slots__ = ['class_', 'is_instance']

def __init__(self,class_,default=None,instantiate=True,is_instance=True,**params):
def __init__(self, class_, default=_Undefined, instantiate=True, is_instance=True, **params):
self.class_ = class_
self.is_instance = is_instance
super(ClassSelector,self).__init__(default=default,instantiate=instantiate,**params)
self._validate(default)

def _validate(self, val):
super(ClassSelector, self)._validate(val)
@@ -1384,14 +1383,13 @@ class List(Parameter):

__slots__ = ['bounds', 'item_type', 'class_']

def __init__(self, default=[], class_=None, item_type=None,
def __init__(self, default=[], class_=_Undefined, item_type=_Undefined,
instantiate=True, bounds=(0, None), **params):
self.item_type = item_type or class_
self.class_ = self.item_type
self.bounds = bounds
Parameter.__init__(self, default=default, instantiate=instantiate,
**params)
self._validate(default)

def _validate(self, val):
"""
@@ -1463,7 +1461,7 @@ class Dict(ClassSelector):
Parameter whose value is a dictionary.
"""

def __init__(self, default=None, **params):
def __init__(self, default=_Undefined, **params):
super(Dict, self).__init__(dict, default=default, **params)


@@ -1472,7 +1470,7 @@ class Array(ClassSelector):
Parameter whose value is a numpy array.
"""

def __init__(self, default=None, **params):
def __init__(self, default=_Undefined, **params):
from numpy import ndarray
super(Array, self).__init__(ndarray, allow_None=True, default=default, **params)

@@ -1511,13 +1509,12 @@ class DataFrame(ClassSelector):

__slots__ = ['rows', 'columns', 'ordered']

def __init__(self, default=None, rows=None, columns=None, ordered=None, **params):
def __init__(self, default=_Undefined, rows=_Undefined, columns=_Undefined, ordered=_Undefined, **params):
from pandas import DataFrame as pdDFrame
self.rows = rows
self.columns = columns
self.ordered = ordered
super(DataFrame,self).__init__(pdDFrame, default=default, **params)
self._validate(self.default)

def _length_bounds_check(self, bounds, length, name):
message = '{name} length {length} does not match declared bounds of {bounds}'
@@ -1588,12 +1585,11 @@ class Series(ClassSelector):

__slots__ = ['rows']

def __init__(self, default=None, rows=None, allow_None=False, **params):
def __init__(self, default=_Undefined, rows=_Undefined, allow_None=False, **params):
from pandas import Series as pdSeries
self.rows = rows
super(Series,self).__init__(pdSeries, default=default, allow_None=allow_None,
**params)
self._validate(self.default)

def _length_bounds_check(self, bounds, length, name):
message = '{name} length {length} does not match declared bounds of {bounds}'
@@ -1731,8 +1727,8 @@ class Path(Parameter):

__slots__ = ['search_paths']

def __init__(self, default=None, search_paths=None, **params):
if search_paths is None:
def __init__(self, default=_Undefined, search_paths=_Undefined, **params):
if search_paths is _Undefined:
search_paths = []

self.search_paths = search_paths
@@ -1832,7 +1828,7 @@ class FileSelector(Selector):
"""
__slots__ = ['path']

def __init__(self, default=None, path="", **kwargs):
def __init__(self, default=_Undefined, path="", **kwargs):
self.default = default
self.path = path
self.update()
@@ -1860,7 +1856,7 @@ class ListSelector(Selector):
a list of possible objects.
"""

def __init__(self, default=None, objects=None, **kwargs):
def __init__(self, default=_Undefined, objects=_Undefined, **kwargs):
super(ListSelector,self).__init__(
objects=objects, default=default, empty_default=True, **kwargs)

@@ -1885,7 +1881,7 @@ class MultiFileSelector(ListSelector):
"""
__slots__ = ['path']

def __init__(self, default=None, path="", **kwargs):
def __init__(self, default=_Undefined, path="", **kwargs):
self.default = default
self.path = path
self.update()
@@ -1911,7 +1907,7 @@ class Date(Number):
Date parameter of datetime or date type.
"""

def __init__(self, default=None, **kwargs):
def __init__(self, default=_Undefined, **kwargs):
super(Date, self).__init__(default=default, **kwargs)

def _validate_value(self, val, allow_None):
@@ -1955,7 +1951,7 @@ class CalendarDate(Number):
Parameter specifically allowing dates (not datetimes).
"""

def __init__(self, default=None, **kwargs):
def __init__(self, default=_Undefined, **kwargs):
super(CalendarDate, self).__init__(default=default, **kwargs)

def _validate_value(self, val, allow_None):
@@ -2031,10 +2027,9 @@ class Color(Parameter):

__slots__ = ['allow_named']

def __init__(self, default=None, allow_named=True, **kwargs):
def __init__(self, default=_Undefined, allow_named=True, **kwargs):
super(Color, self).__init__(default=default, **kwargs)
self.allow_named = allow_named
self._validate(default)

def _validate(self, val):
self._validate_value(val, self.allow_None)
@@ -2067,8 +2062,8 @@ class Range(NumericTuple):

__slots__ = ['bounds', 'inclusive_bounds', 'softbounds', 'step']

def __init__(self,default=None, bounds=None, softbounds=None,
inclusive_bounds=(True,True), step=None, **params):
def __init__(self, default=_Undefined, bounds=_Undefined, softbounds=_Undefined,
inclusive_bounds=(True,True), step=_Undefined, **params):
self.bounds = bounds
self.inclusive_bounds = inclusive_bounds
self.softbounds = softbounds
30 changes: 23 additions & 7 deletions param/parameterized.py
Original file line number Diff line number Diff line change
@@ -969,7 +969,8 @@ class Foo(Bar):

_serializers = {'json': serializer.JSONSerialization}

def __init__(self,default=None, doc=None, label=None, precedence=None, # pylint: disable-msg=R0913
def __init__(self, default=_Undefined, doc=_Undefined, # pylint: disable-msg=R0913
label=_Undefined, precedence=_Undefined,
instantiate=False, constant=False, readonly=False,
pickle_default_value=True, allow_None=False,
per_instance=True):
@@ -1056,7 +1057,7 @@ class hierarchy (see ParameterizedMetaclass).
self._internal_name = None
self._set_instantiate(instantiate)
self.pickle_default_value = pickle_default_value
self.allow_None = (default is None or allow_None)
self.allow_None = (self.default is None or allow_None)
self.watchers = {}
self.per_instance = per_instance

@@ -1317,7 +1318,7 @@ def __init__(self, default="0.0.0.0", allow_None=False, **kwargs):
def __init__(self, default="", regex=None, allow_None=False, **kwargs):
super(String, self).__init__(default=default, allow_None=allow_None, **kwargs)
self.regex = regex
self.allow_None = (default is None or allow_None)
self.allow_None = (self.default is None or allow_None)
self._validate(default)

def _validate_regex(self, val, regex):
@@ -1831,6 +1832,10 @@ def add_parameter(self_, param_name, param_obj):
except AttributeError:
pass

if hasattr(param_obj, '_validate'):
param_obj._validate(param_obj.default)


# PARAM2_DEPRECATION: Backwards compatibilitity for param<1.12
_add_parameter = add_parameter

@@ -2680,6 +2685,13 @@ def __init__(mcs, name, bases, dict_):
if docstring_signature:
mcs.__class_docstring_signature()

# Validation is done here rather than in the Parameter itself so that slot
# values can be inherited along superclasses
for p in mcs.param.objects().values():
if hasattr(p, '_validate'):
p._validate(p.default)


def __class_docstring_signature(mcs, max_repr_len=15):
"""
Autogenerate a keyword signature in the class docstring for
@@ -2847,14 +2859,14 @@ def __param_inheritance(mcs,param_name,param):
param.instantiate=True
del slots['instantiate']


supers = classlist(mcs)[::-1]
for slot in slots.keys():
superclasses = iter(classlist(mcs)[::-1])
superclasses = iter(supers)

# Search up the hierarchy until param.slot (which has to
# be obtained using getattr(param,slot)) is not None, or
# we run out of classes to search.
while getattr(param,slot) is None:
while getattr(param,slot) is _Undefined:
try:
param_super_class = next(superclasses)
except StopIteration:
@@ -2865,7 +2877,10 @@ def __param_inheritance(mcs,param_name,param):
# (slot might not be there because could be a more
# general type of Parameter)
new_value = getattr(new_param,slot)
setattr(param,slot,new_value)
if new_value is not _Undefined:
setattr(param, slot, new_value)
if getattr(param, slot) is _Undefined:
setattr(param, slot, None)


def get_param_descriptor(mcs,param_name):
@@ -3163,6 +3178,7 @@ def __init__(self, **params):

self.initialized = True


@property
def param(self):
return Parameters(self.__class__, self=self)