14
14
15
15
class Param :
16
16
""" Provides a parameter specification to be used within a Params instance. """
17
- def __init__ (self , value , doc : Text = None , dtype : Type = None , required : bool = False ):
17
+ def __init__ (self , value , doc : Text = None , dtype : Type = None , required : bool = False , params_class = None ):
18
18
"""
19
19
Constructs a parameter specification to be used in a Params instance:
20
20
@@ -31,17 +31,28 @@ class MyParams(pp.Params):
31
31
:param dtype: (Optional) type
32
32
:param required: default is True.
33
33
"""
34
- self .default_value = value
34
+ self .params_class = params_class
35
+ self ._default_value = value
35
36
self .doc_string = doc
36
37
self .required = required
37
- self .dtype = type (value ) if (dtype is None and value is not None ) else dtype
38
- if value is not None :
38
+ self .dtype = dtype
39
+ if dtype is None and value is not None and not callable (value ):
40
+ self .dtype = type (value )
41
+ if value is not None and not callable (value ):
39
42
if not isinstance (value , self .dtype ):
40
43
raise RuntimeError (f"Param({ value } ) does not match dtype:[{ self .dtype } ]" )
41
44
self .name = None
45
+ self .is_property = callable (value )
42
46
47
+ @property
48
+ def default_value (self ):
49
+ return self ._default_value (self .params_class ) if self .is_property else self ._default_value
43
50
44
- class Params (dict ): # TODO use collections.UserDict instead of dict - see #1
51
+ def value (self , params ):
52
+ return self ._default_value (params ) if self .is_property else getattr (self .name , params )
53
+
54
+
55
+ class Params (dict ):
45
56
""" Base class for defining safe parameter dictionaries.
46
57
47
58
Example:
@@ -67,37 +78,48 @@ class MyParams(pp.Params):
67
78
68
79
def __init_subclass__ (cls , ** kwargs ):
69
80
""" Aggregates the Param spec of the parameters over the hierarchy. """
70
- specs = {}
71
- for base in reversed ( cls .__bases__ ) :
81
+ base_specs = {}
82
+ for base in cls .__bases__ :
72
83
if issubclass (base , Params ):
73
- specs .update (base .__specs )
84
+ base_specs .update (base .__specs )
74
85
86
+ cls_specs = [] # evaluate in order of declaration
75
87
for attr , value in cls .__dict__ .items ():
76
88
if attr .startswith ("_" ) or callable (getattr (cls , attr )):
77
89
continue
78
90
79
- param_spec = getattr (cls , attr )
80
- if isinstance (param_spec , property ):
81
- param_spec = Param (param_spec .fget ( cls ) )
82
- elif not isinstance (param_spec , Param ):
91
+ attr_val = getattr (cls , attr )
92
+ if isinstance (attr_val , property ):
93
+ param_spec = Param (attr_val .fget , params_class = cls )
94
+ elif not isinstance (attr_val , Param ):
83
95
param_spec = Param (value )
96
+ else :
97
+ param_spec = attr_val
84
98
85
99
param_spec .name = attr
86
- specs [ attr ] = param_spec
100
+ cls_specs . append (( attr , param_spec ))
87
101
88
- for attr , value in specs .items ():
102
+ _specs = {}
103
+ for attr , value in list (base_specs .items ()) + cls_specs :
89
104
setattr (cls , attr , value .default_value )
105
+ _specs [attr ] = value
90
106
91
- cls .__specs = specs
107
+ cls .__specs = _specs
92
108
cls .__defaults = {key : val .default_value for key , val in cls .__specs .items ()}
93
109
94
110
def __init__ (self , * args , ** kwargs ):
95
111
super (Params , self ).__init__ ()
96
112
self .update (self .__defaults ) # start with default values
97
113
self .update (dict (* args )) # override with tuple list
98
114
self .update (kwargs ) # override with kwargs
115
+ # update any overridden @property parameters
116
+ prop_specs = list (filter (lambda spec : spec .is_property ,
117
+ self .__class__ .__specs .values ()))
99
118
100
- def update (self , arg = None , ** kwargs ):
119
+ for spec in prop_specs :
120
+ self .update ({spec .name : spec .value (self )})
121
+
122
+ def update (self , arg = None , ** kwargs ): # see dict.update()
101
123
if arg :
102
124
keys = getattr (arg , "keys" ) if hasattr (arg , "keys" ) else None
103
125
if keys and (inspect .ismethod (keys ) or inspect .isbuiltin (keys )):
@@ -111,7 +133,10 @@ def update(self, arg=None, **kwargs):
111
133
112
134
def __getattribute__ (self , attr ):
113
135
if not attr .startswith ("_" ) and attr in self .__defaults :
114
- return self .__getitem__ (attr )
136
+ if self .__specs [attr ].is_property :
137
+ return self .__specs [attr ].value (self )
138
+ else :
139
+ return self .__getitem__ (attr )
115
140
return object .__getattribute__ (self , attr )
116
141
117
142
def __setattr__ (self , key , value ):
0 commit comments