Skip to content

Commit

Permalink
Merge branch 'main' into fix_private_namespace
Browse files Browse the repository at this point in the history
  • Loading branch information
maximlt committed Jul 14, 2023
2 parents 3617839 + 7a6a2f5 commit 86794ed
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 65 deletions.
2 changes: 2 additions & 0 deletions param/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,8 @@ def __init__(self, *, objects=Undefined, default=Undefined, instantiate=Undefine
# Required as Parameter sets allow_None=True if default is None
if allow_None is Undefined:
self.allow_None = self._slot_defaults['allow_None']
else:
self.allow_None = allow_None
if self.default is not None and self.check_on_set is True:
self._validate(self.default)

Expand Down
41 changes: 23 additions & 18 deletions param/parameterized.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,8 +1254,6 @@ def _set_instantiate(self,instantiate):
# having this code avoids needless instantiation.
if self.readonly:
self.instantiate = False
elif self.constant is True:
self.instantiate = True
elif instantiate is not Undefined:
self.instantiate = instantiate
else:
Expand Down Expand Up @@ -1813,30 +1811,35 @@ def _setup_params(self_,**params):
"""
Initialize default and keyword parameter values.
First, ensures that all Parameters with 'instantiate=True'
(typically used for mutable Parameters) are copied directly
into each object, to ensure that there is an independent copy
(to avoid surprising aliasing errors). Then sets each of the
keyword arguments, warning when any of them are not defined as
parameters.
Constant Parameters can be set during calls to this method.
First, ensures that all Parameters with 'instantiate=True' (typically
used for mutable Parameters) are copied directly into each object, to
ensure that there is an independent copy (to avoid surprising aliasing
errors). Second, ensures that Parameters with 'constant=True' are
referenced on the instance, to make sure that setting a constant
Parameter on the class doesn't affect already created instances. Then
sets each of the keyword arguments, raising when any of them are not
defined as parameters.
"""
self = self_.param.self
## Deepcopy all 'instantiate=True' parameters
# (building a set of names first to avoid redundantly
# instantiating a later-overridden parent class's parameter)
params_to_instantiate = {}
params_to_deepcopy = {}
params_to_ref = {}
for class_ in classlist(type(self)):
if not issubclass(class_, Parameterized):
continue
for (k, v) in class_.param._parameters.items():
# (avoid replacing name with the default of None)
if v.instantiate and k != "name":
params_to_instantiate[k] = v
params_to_deepcopy[k] = v
elif v.constant and k != 'name':
params_to_ref[k] = v

for p in params_to_instantiate.values():
for p in params_to_deepcopy.values():
self.param._instantiate_param(p)
for p in params_to_ref.values():
self.param._instantiate_param(p, deepcopy=False)

## keyword arg setting
for name, val in params.items():
Expand All @@ -1857,9 +1860,11 @@ def _changed(cls, event):
"""
return not Comparator.is_equal(event.old, event.new)

def _instantiate_param(self_, param_obj, dict_=None, key=None):
# deepcopy param_obj.default into self._param__private.values (or dict_ if supplied)
# under the parameter's name (or key if supplied)
def _instantiate_param(self_, param_obj, dict_=None, key=None, deepcopy=True):
# deepcopy or store a reference to reference param_obj.default into
# self._param__private.values (or dict_ if supplied) under the
# parameter's name (or key if supplied)
instantiator = copy.deepcopy if deepcopy else lambda o: o
self = self_.self
dict_ = dict_ or self._param__private.values
key = key or param_obj.name
Expand All @@ -1868,10 +1873,10 @@ def _instantiate_param(self_, param_obj, dict_=None, key=None):
if param_key in shared_parameters._shared_cache:
new_object = shared_parameters._shared_cache[param_key]
else:
new_object = copy.deepcopy(param_obj.default)
new_object = instantiator(param_obj.default)
shared_parameters._shared_cache[param_key] = new_object
else:
new_object = copy.deepcopy(param_obj.default)
new_object = instantiator(param_obj.default)

dict_[key] = new_object

Expand Down
46 changes: 46 additions & 0 deletions tests/testobjectselector.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from collections import OrderedDict

import param
import pytest

from .utils import check_defaults

Expand Down Expand Up @@ -104,6 +105,51 @@ def test_unbound_allow_None_not_dynamic(self):

assert s.allow_None is None

def test_allow_None_set_and_behavior_class(self):
class P(param.Parameterized):
a = param.ObjectSelector(objects=dict(a=1), allow_None=True)
b = param.ObjectSelector(objects=dict(a=1), allow_None=False)
c = param.ObjectSelector(default=1, objects=dict(a=1), allow_None=True)
d = param.ObjectSelector(default=1, objects=dict(a=1), allow_None=False)

assert P.param.a.allow_None is True
assert P.param.b.allow_None is False
assert P.param.c.allow_None is True
assert P.param.d.allow_None is False

P.a = None
assert P.a is None
with pytest.raises(ValueError):
P.b = None
P.c = None
assert P.c is None
with pytest.raises(ValueError):
P.d = None

def test_allow_None_set_and_behavior_instance(self):
class P(param.Parameterized):
a = param.ObjectSelector(objects=dict(a=1), allow_None=True)
b = param.ObjectSelector(objects=dict(a=1), allow_None=False)
c = param.ObjectSelector(default=1, objects=dict(a=1), allow_None=True)
d = param.ObjectSelector(default=1, objects=dict(a=1), allow_None=False)

p = P()

assert p.param.a.allow_None is True
assert p.param.b.allow_None is False
assert p.param.c.allow_None is True
assert p.param.d.allow_None is False

p.a = None
assert p.a is None
with pytest.raises(ValueError):
p.b = None
p.c = None
assert p.c is None
with pytest.raises(ValueError):
p.d = None


def test_set_object_constructor(self):
p = self.P(e=6)
self.assertEqual(p.e, 6)
Expand Down
77 changes: 31 additions & 46 deletions tests/testparameterizedobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,37 @@ class C(A, B): pass

assert C.name == 'AA'

def test_constant_parameter_modify_class_before(self):
"""Test you can set on class and the new default is picked up
by new instances"""
TestPO.const=9
testpo = TestPO()
self.assertEqual(testpo.const,9)

def test_constant_parameter_modify_class_after_init(self):
"""Test that setting the value on the class doesn't update the instance value
even when the instance value hasn't yet been set"""
oobj = []
class P(param.Parameterized):
x = param.Parameter(default=oobj, constant=True)

p1 = P()

P.x = nobj = [0]
assert P.x is nobj
assert p1.x == oobj
assert p1.x is oobj

p2 = P()
assert p2.x == nobj
assert p2.x is nobj

def test_constant_parameter_after_init(self):
"""Test that you can't set a constant parameter after construction."""
testpo = TestPO(const=17)
self.assertEqual(testpo.const,17)
self.assertRaises(TypeError,setattr,testpo,'const',10)

def test_constant_parameter(self):
"""Test that you can't set a constant parameter after construction."""
testpo = TestPO(const=17)
Expand All @@ -274,52 +305,6 @@ def test_constant_parameter(self):
testpo = TestPO()
self.assertEqual(testpo.const,9)

def test_parameter_constant_instantiate(self):
# instantiate is automatically set to True when constant=True
assert TestPO.param.const.instantiate is True

class C(param.Parameterized):
# instantiate takes precedence when True
a = param.Parameter(instantiate=True, constant=False)
b = param.Parameter(instantiate=False, constant=False)
c = param.Parameter(instantiate=False, constant=True)
d = param.Parameter(constant=True)
e = param.Parameter(constant=False)
f = param.Parameter()

assert C.param.a.constant is False
assert C.param.a.instantiate is True
assert C.param.b.constant is False
assert C.param.b.instantiate is False
assert C.param.c.constant is True
assert C.param.c.instantiate is True
assert C.param.d.constant is True
assert C.param.d.instantiate is True
assert C.param.e.constant is False
assert C.param.e.instantiate is False
assert C.param.f.constant is False
assert C.param.f.instantiate is False

def test_parameter_constant_instantiate_subclass(self):

obj = object()

class A(param.Parameterized):
x = param.Parameter(obj)

class B(param.Parameterized):
x = param.Parameter(constant=True)

assert A.param.x.constant is False
assert A.param.x.instantiate is False
assert B.param.x.constant is True
assert B.param.x.instantiate is True

a = A()
b = B()
assert a.x is obj
assert b.x is not obj

def test_readonly_parameter(self):
"""Test that you can't set a read-only parameter on construction or as an attribute."""
testpo = TestPO()
Expand Down
57 changes: 56 additions & 1 deletion tests/testselector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
from collections import OrderedDict

import param
import pytest

from.utils import check_defaults
from .utils import check_defaults

opts=dict(A=[1,2],B=[3,4],C=dict(a=1,b=2))

Expand Down Expand Up @@ -103,6 +104,50 @@ def test_allow_None_is_None(self):
assert p.param.s.allow_None is None
assert p.param.d.allow_None is None

def test_allow_None_set_and_behavior_class(self):
class P(param.Parameterized):
a = param.Selector(objects=dict(a=1), allow_None=True)
b = param.Selector(objects=dict(a=1), allow_None=False)
c = param.Selector(default=1, objects=dict(a=1), allow_None=True)
d = param.Selector(default=1, objects=dict(a=1), allow_None=False)

assert P.param.a.allow_None is True
assert P.param.b.allow_None is False
assert P.param.c.allow_None is True
assert P.param.d.allow_None is False

P.a = None
assert P.a is None
with pytest.raises(ValueError):
P.b = None
P.c = None
assert P.c is None
with pytest.raises(ValueError):
P.d = None

def test_allow_None_set_and_behavior_instance(self):
class P(param.Parameterized):
a = param.Selector(objects=dict(a=1), allow_None=True)
b = param.Selector(objects=dict(a=1), allow_None=False)
c = param.Selector(default=1, objects=dict(a=1), allow_None=True)
d = param.Selector(default=1, objects=dict(a=1), allow_None=False)

p = P()

assert p.param.a.allow_None is True
assert p.param.b.allow_None is False
assert p.param.c.allow_None is True
assert p.param.d.allow_None is False

p.a = None
assert p.a is None
with pytest.raises(ValueError):
p.b = None
p.c = None
assert p.c is None
with pytest.raises(ValueError):
p.d = None

def test_autodefault(self):
class P(param.Parameterized):
o1 = param.Selector(objects=[6, 7])
Expand Down Expand Up @@ -337,3 +382,13 @@ class B(A):
assert b.param.p.objects == [0, 1]
assert b.param.p.default == 1
assert b.param.p.check_on_set is True

def test_no_instantiate_when_constant(self):
# https://github.com/holoviz/param/issues/287
objs = [object(), object()]

class A(param.Parameterized):
p = param.Selector(default=objs[0], objects=objs, constant=True)

a = A()
assert a.p is objs[0]

0 comments on commit 86794ed

Please sign in to comment.