-
Notifications
You must be signed in to change notification settings - Fork 0
/
param.py
156 lines (125 loc) · 5.5 KB
/
param.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# EvilPlot
# Copyright 2008 Brigham Young University
#
# This file is part of EvilPlot.
#
# EvilPlot is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# EvilPlot is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# EvilPlot. If not, see <http://www.gnu.org/licenses/>.
#
# Inquiries regarding any further use of the Materials contained on this site,
# please contact the Copyright Licensing Office, Brigham Young University,
# 3760 HBLL, Provo, UT 84602, (801) 422-9339 or 422-3821, e-mail
"""param.py: Create objects with inheritable keyword parameters/attributes
Anything that subclasses ParamObj will be an object for which _params is
a special directory of Param objects.
"""
from six import with_metaclass
#TODO: make it so that you can put None in your dictionary to cancel out
# a parameter defined in one of your superclasses.
class ParamError(Exception):
def __init__(self, clsname, paramname):
self.clsname = clsname
self.paramname = paramname
def __str__(self):
return 'Class %s has no parameter "%s"' % (self.clsname, self.paramname)
class Param(object):
"""A Parameter with name, default value, and documentation.
A list of Params is used by a class of type ParamMeta.
"""
def __init__(self, default=None, doc=None):
self.default = default
self.doc = doc
class ParamMeta(type):
"""A metaclass that lets you define params.
When creating a new class of type ParamMeta, add a dictionary named
params into the class namespace. Add Param objects to the dictionary
with the key being the name of the parameter. Now, each object of the
class will have an attribute with the appropriate name. The value will
default to the default value in the Param object, but it can be
overridden by name in __init__.
Rather than using ParamMeta directly, we recommend that you subclass
ParamObj, which will allow you to override __init__ as long as you
call super's __init__.
"""
def __new__(cls, classname, bases, classdict):
# Make sure we have a params dict in classdict.
if '_params' not in classdict:
classdict['_params'] = {}
params = classdict['_params']
# Collect the params from each of the parent classes.
for base in bases:
try:
baseparams = base._params
except AttributeError:
# This base class doesn't have a params list.
continue
for param_name in baseparams:
if param_name in params:
if params[param_name].doc is None:
params[param_name].doc = baseparams[param_name].doc
else:
params[param_name] = baseparams[param_name]
# Update documentation based on our parameters
if '__doc__' not in classdict:
classdict['__doc__'] = '%s -- Class using Params' % classname
docs = [('%s: %s (default=%s)' % (param_name,
params[param_name].doc, params[param_name].default))
for param_name in params]
docs.sort()
classdict['__doc__'] = classdict['__doc__'] + \
'\n '.join(['\n%s Parameters:' % classname] + docs)
# Write a new __init__ for our classes. If they write their own
# __init__, we refuse to overwrite it.
if '__init__' not in classdict:
def __init__(self, **kwds):
for key in kwds:
if key not in self._params:
raise ParamError(self.__class__.__name__, key)
for param_name in self._params:
if param_name in kwds:
value = kwds[param_name]
else:
value = self._params[param_name].default
setattr(self, param_name, value)
classdict['__init__'] = __init__
# Create and return the new class
return type.__new__(cls, classname, bases, classdict)
class ParamObj(with_metaclass(ParamMeta)):
"""An object that treats "_params" specially.
An object of class ParamObj may contain a dictionary named _params. This
dictionary should have string keys and Param values. For each entry in
the dictionary, an object attribute is created with the same name as the
key, and the value and documentation of the attribute are given by the
arguments to the Param. Inheritance of _params works right.
Example:
class Rabbit(ParamObj):
\"\"\"A small rodent, very similar to a hare, which feeds on grass
and burrows in the earth.
\"\"\"
_params = dict(weight=Param(default=42, doc='Body Weight'))
def __init__(self, name, **kwds):
ParamObj.__init__(**kwds)
>>> m = Mammal()
>>> m.__doc__
'A small rodent, very similar to a hare, which feeds on grass
and burrows in the earth.
weight: Body Weight (default=42)'
>>> m.weight
42
>>> m = Mammal(weight=12)
>>> m.weight
12
"""
__all__ = [ParamObj, Param]
# vim: et sw=4 sts=4