source: orange/orange/Orange/misc/__init__.py @ 7776:ca08d5700668

Revision 7776:ca08d5700668, 11.9 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Added deprecation utility functions.

Line 
1"""
2
3.. index:: misc
4
5Module Orange.misc contains common functions and classes which are used in other modules.
6
7==================
8Counters
9==================
10
11.. index:: misc
12.. index::
13   single: misc; counters
14
15.. automodule:: Orange.misc.counters
16  :members:
17
18==================
19Render
20==================
21
22.. index:: misc
23.. index::
24   single: misc; render
25
26.. automodule:: Orange.misc.render
27  :members:
28
29==================
30Selection
31==================
32
33.. index:: selection
34.. index::
35   single: misc; selection
36
37Many machine learning techniques generate a set different solutions or have to
38choose, as for instance in classification tree induction, between different
39features. The most trivial solution is to iterate through the candidates,
40compare them and remember the optimal one. The problem occurs, however, when
41there are multiple candidates that are equally good, and the naive approaches
42would select the first or the last one, depending upon the formulation of
43the if-statement.
44
45:class:`Orange.misc.selection` provides a class that makes a random choice
46in such cases. Each new candidate is compared with the currently optimal
47one; it replaces the optimal if it is better, while if they are equal,
48one is chosen by random. The number of competing optimal candidates is stored,
49so in this random choice the probability to select the new candidate (over the
50current one) is 1/w, where w is the current number of equal candidates,
51including the present one. One can easily verify that this gives equal
52chances to all candidates, independent of the order in which they are presented.
53
54.. automodule:: Orange.misc.selection
55  :members:
56
57Example
58--------
59
60The following snippet loads the data set lymphography and prints out the
61feature with the highest information gain.
62
63part of `misc-selection-bestonthefly.py`_ (uses `lymphography.tab`_)
64
65.. literalinclude:: code/misc-selection-bestonthefly.py
66  :lines: 7-16
67
68Our candidates are tuples gain ratios and features, so we set
69:obj:`callCompareOn1st` to make the compare function compare the first element
70(gain ratios). We could achieve the same by initializing the object like this:
71
72part of `misc-selection-bestonthefly.py`_ (uses `lymphography.tab`_)
73
74.. literalinclude:: code/misc-selection-bestonthefly.py
75  :lines: 18-18
76
77
78The other way to do it is through indices.
79
80`misc-selection-bestonthefly.py`_ (uses `lymphography.tab`_)
81
82.. literalinclude:: code/misc-selection-bestonthefly.py
83  :lines: 25-
84
85.. _misc-selection-bestonthefly.py: code/misc-selection-bestonthefly.py.py
86.. _lymphography.tab: code/lymphography.tab
87
88Here we only give gain ratios to :obj:`bestOnTheFly`, so we don't have to specify a
89special compare operator. After checking all features we get the index of the
90optimal one by calling :obj:`winnerIndex`.
91
92"""
93
94
95__all__ = ["counters", "selection", "render", "deprecated_members",
96           "deprecated_keywords", "deprecated_attribute", "deprecation_warning"]
97
98import random, types, sys
99import time
100
101def getobjectname(x, default=""):
102    if type(x)==types.StringType:
103        return x
104     
105    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
106        if getattr(x, i, ""):
107            return getattr(x, i)
108
109    if hasattr(x, "__class__"):
110        r = repr(x.__class__)
111        if r[1:5]=="type":
112            return str(x.__class__)[7:-2]
113        elif r[1:6]=="class":
114            return str(x.__class__)[8:-2]
115    return default
116
117
118def demangleExamples(x):
119    if type(x)==types.TupleType:
120        return x
121    else:
122        return x, 0
123
124
125def frange(*argw):
126    start, stop, step = 0.0, 1.0, 0.1
127    if len(argw)==1:
128        start=step=argw[0]
129    elif len(argw)==2:
130        stop, step = argw
131    elif len(argw)==3:
132        start, stop, step = argw
133    elif len(argw)>3:
134        raise AttributeError, "1-3 arguments expected"
135
136    stop+=1e-10
137    i=0
138    res=[]
139    while 1:
140        f=start+i*step
141        if f>stop:
142            break
143        res.append(f)
144        i+=1
145    return res
146
147verbose = 0
148
149def printVerbose(text, *verb):
150    if len(verb) and verb[0] or verbose:
151        print text
152
153class ConsoleProgressBar(object):
154    def __init__(self, title="", charwidth=40, step=1, output=sys.stderr):
155        self.title = title + " "
156        self.charwidth = charwidth
157        self.step = step
158        self.currstring = ""
159        self.state = 0
160        self.output = output
161
162    def clear(self, i=-1):
163        try:
164            if hasattr(self.output, "isatty") and self.output.isatty():
165                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
166            else:
167                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
168        except Exception: ## If for some reason we failed
169            self.output.write("\n")
170
171    def getstring(self):
172        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
173        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth\
174            - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
175
176    def printline(self, string):
177        try:
178            self.clear()
179            self.output.write(string)
180            self.output.flush()
181        except Exception:
182            pass
183        self.currstring = string
184
185    def __call__(self, newstate=None):
186        if newstate == None:
187            newstate = self.state + self.step
188        if int(newstate) != int(self.state):
189            self.state = newstate
190            self.printline(self.getstring())
191        else:
192            self.state = newstate
193
194    def finish(self):
195        self.__call__(100)
196        self.output.write("\n")
197
198def progressBarMilestones(count, iterations=100):
199    return set([int(i*count/float(iterations)) for i in range(iterations)])
200
201def lru_cache(maxsize=100):
202    """ A least recently used cache function decorator.
203    (Similar to the functools.lru_cache in python 3.2)
204    """
205   
206    def decorating_function(func):
207        import functools
208        cache = {}
209       
210        functools.wraps(func)
211        def wrapped(*args, **kwargs):
212            key = args + tuple(sorted(kwargs.items()))
213            if key not in cache:
214                res = func(*args, **kwargs)
215                cache[key] = (time.time(), res)
216                if len(cache) > maxsize:
217                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
218                    del cache[key]
219            else:
220                _, res = cache[key]
221                cache[key] = (time.time(), res) # update the time
222               
223            return res
224       
225        def clear():
226            cache.clear()
227       
228        wrapped.clear = clear
229       
230        return wrapped
231    return decorating_function
232
233
234"""\
235Deprecation utility functions.
236
237"""
238
239import warnings
240def deprecation_warning(old, new, stacklevel=-2):
241    warnings.warn("'%s' is deprecated. Use '%s' instead!" % (old, new), DeprecationWarning, stacklevel=stacklevel)
242   
243# We need to get the instancemethod type
244class _Foo():
245    def bar(self):
246        pass
247instancemethod = type(_Foo.bar)
248del _Foo
249
250function = type(lambda: None)
251
252class universal_set(set):
253    """ A universal set, pretends it contains everything.
254    """
255    def __contains__(self, value):
256        return True
257   
258from functools import wraps
259
260def deprecated_members(name_map, wrap_methods="all", in_place=True):
261    """ Decorate a class with properties for accessing attributes, and methods
262    with deprecated names. In addition methods from the `wrap_methods` list
263    will be wrapped to receive mapped keyword arguments.
264   
265    :param name_map: A dictionary mapping old into new names.
266    :type name_map: dict
267   
268    :param wrap_methods: A list of method names to wrap. Wrapped methods will
269        be called with mapped keyword arguments (by default all methods will
270        be wrapped).
271    :type wrap_methods: list
272   
273    Example ::
274           
275        >>> @deprecated_members({"fooBar": "foo_bar", "setFooBar":"set_foo_bar"},
276        ...                    wrap_methods=["set_foo_bar", "__init__"])
277        ... class A(object):
278        ...     def __init__(self, foo_bar="bar"):
279        ...         self.set_foo_bar(foo_bar)
280        ...     
281        ...     def set_foo_bar(self, foo_bar="bar"):
282        ...         self.foo_bar = foo_bar
283        ...         
284        ...
285        >>> a = A(fooBar="foo")
286        __main__:1: DeprecationWarning: 'fooBar' is deprecated. Use 'foo_bar' instead!
287        >>> print a.fooBar, a.foo_bar
288        foo foo
289        >>> a.setFooBar("FooBar!")
290        __main__:1: DeprecationWarning: 'setFooBar' is deprecated. Use 'set_foo_bar' instead!
291       
292    """
293    def is_wrapped(method):
294        """ Is member method already wrapped.
295        """
296        if getattr(method, "_deprecate_members_wrapped", False):
297            return True
298        elif hasattr(method, "im_func"):
299            im_func = method.im_func
300            return getattr(im_func, "_deprecate_members_wrapped", False)
301        else:
302            return False
303       
304    if wrap_methods == "all":
305        wrap_methods = universal_set()
306    elif not wrap_methods:
307        wrap_methods = set()
308       
309    def wrapper(cls):
310        cls_names = {}
311        # Create properties for accessing deprecated members
312        for old_name, new_name in name_map.items():
313            cls_names[old_name] = deprecated_attribute(old_name, new_name)
314           
315        # wrap member methods to map keyword arguments
316        for key, value in cls.__dict__.items():
317            if isinstance(value, (instancemethod, function)) \
318                and not is_wrapped(value) and key in wrap_methods:
319               
320                wrapped = deprecated_keywords(name_map)(value)
321                wrapped._deprecate_members_wrapped = True # A flag indicating this function already maps keywords
322                cls_names[key] = wrapped
323        if in_place:
324            for key, val in cls_names.items():
325                setattr(cls, key, val)
326            return cls
327        else:
328            return type(cls.__name__, (cls,), cls_names)
329       
330    return wrapper
331
332def deprecated_keywords(name_map):
333    """ Deprecates the keyword arguments of the function.
334   
335    Example ::
336   
337        >>> @deprecated_keywords({"myArg": "my_arg"})
338        ... def my_func(my_arg=None):
339        ...     print my_arg
340        ...
341        ...
342        >>> my_func(myArg="Arg")
343        __main__:1: DeprecationWarning: 'myArg' is deprecated. Use 'my_arg' instead!
344        Arg
345       
346    """
347    def decorator(func):
348        @wraps(func)
349        def wrap_call(*args, **kwargs):
350            kwargs = dict(kwargs)
351            for name in name_map:
352                if name in kwargs:
353                    deprecation_warning(name, name_map[name], stacklevel=3)
354                    kwargs[name_map[name]] = kwargs[name]
355                    del kwargs[name]
356            return func(*args, **kwargs)
357        return wrap_call
358    return decorator
359
360def deprecated_attribute(old_name, new_name):
361    """ Return a property object that accesses an attribute named `new_name`
362    and raises a deprecation warning when doing so.
363   
364    Example ::
365   
366        >>> class A(object):
367        ...     def __init__(self):
368        ...         self.my_attr = "123"
369        ...     myAttr = deprecated_attribute("myAttr", "my_attr")
370        ...
371        ...
372        >>> a = A()
373        >>> print a.myAttr
374        __main__:1: DeprecationWarning: 'myAttr' is deprecated. Use 'my_attr' instead!
375        123
376       
377    """
378    def fget(self):
379        deprecation_warning(old_name, new_name, stacklevel=3)
380        return getattr(self, new_name)
381   
382    def fset(self, value):
383        deprecation_warning(old_name, new_name, stacklevel=3)
384        setattr(self, new_name, value)
385   
386    def fdel(self):
387        deprecation_warning(old_name, new_name, stacklevel=3)
388        delattr(self, new_name)
389   
390    prop = property(fget, fset, fdel,
391                    doc="A deprecated member '%s'. Use '%s' instead." % (old_name, new_name))
392    return prop
393   
394def _test():
395    import doctest
396    doctest.testmod()
397   
398if __name__ == "__main__":
399    _test()
Note: See TracBrowser for help on using the repository browser.