source: orange/Orange/utils/__init__.py @ 10581:94a831b04ec3

Revision 10581:94a831b04ec3, 17.7 KB checked in by markotoplak, 2 years ago (diff)

Moved some other scripts from misc to utils and Orange imports and canvas not works, although not systematically tested.

Line 
1"""
2.. index:: utils
3
4Orange.utils contains developer utilities.
5
6------------------
7Reporting progress
8------------------
9
10.. autoclass:: Orange.utils.ConsoleProgressBar
11    :members:
12
13-----------------------------
14Deprecation utility functions
15-----------------------------
16
17.. autofunction:: Orange.utils.deprecation_warning
18
19.. autofunction:: Orange.utils.deprecated_members
20
21.. autofunction:: Orange.utils.deprecated_keywords
22
23.. autofunction:: Orange.utils.deprecated_attribute
24
25.. autofunction:: Orange.utils.deprecated_function_name
26
27----------------
28Other submodules
29----------------
30
31.. automodule:: Orange.utils.environ
32
33.. automodule:: Orange.utils.counters
34  :members:
35
36.. automodule:: Orange.utils.render
37  :members:
38
39.. automodule:: Orange.utils.addons
40
41"""
42
43__all__ = ["deprecated_members", "deprecated_keywords",
44           "deprecated_attribute", "deprecation_warning",
45           "deprecated_function_name",
46           "counters", "render", "serverfiles"]
47
48import environ
49
50import warnings
51def deprecation_warning(old, new, stacklevel=-2):
52    """ Raise a deprecation warning of an obsolete attribute access.
53   
54    :param old: Old attribute name (used in warning message).
55    :param new: New attribute name (used in warning message).
56   
57    """
58    warnings.warn("'%s' is deprecated. Use '%s' instead!" % (old, new), DeprecationWarning, stacklevel=stacklevel)
59   
60# We need to get the instancemethod type
61class _Foo():
62    def bar(self):
63        pass
64instancemethod = type(_Foo.bar)
65del _Foo
66
67function = type(lambda: None)
68
69class universal_set(set):
70    """ A universal set, pretends it contains everything.
71    """
72    def __contains__(self, value):
73        return True
74   
75from functools import wraps
76
77def deprecated_members(name_map, wrap_methods="all", in_place=True):
78    """ Decorate a class with properties for accessing attributes, and methods
79    with deprecated names. In addition methods from the `wrap_methods` list
80    will be wrapped to receive mapped keyword arguments.
81   
82    :param name_map: A dictionary mapping old into new names.
83    :type name_map: dict
84   
85    :param wrap_methods: A list of method names to wrap. Wrapped methods will
86        be called with mapped keyword arguments (by default all methods will
87        be wrapped).
88    :type wrap_methods: list
89   
90    :param in_place: If True the class will be modified in place, otherwise
91        it will be subclassed (default True).
92    :type in_place: bool
93   
94    Example ::
95           
96        >>> class A(object):
97        ...     def __init__(self, foo_bar="bar"):
98        ...         self.set_foo_bar(foo_bar)
99        ...     
100        ...     def set_foo_bar(self, foo_bar="bar"):
101        ...         self.foo_bar = foo_bar
102        ...
103        ... A = deprecated_members(
104        ... {"fooBar": "foo_bar",
105        ...  "setFooBar":"set_foo_bar"},
106        ... wrap_methods=["set_foo_bar", "__init__"])(A)
107        ...
108        ...
109        >>> a = A(fooBar="foo")
110        __main__:1: DeprecationWarning: 'fooBar' is deprecated. Use 'foo_bar' instead!
111        >>> print a.fooBar, a.foo_bar
112        foo foo
113        >>> a.setFooBar("FooBar!")
114        __main__:1: DeprecationWarning: 'setFooBar' is deprecated. Use 'set_foo_bar' instead!
115       
116    .. note:: This decorator does nothing if \
117        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
118        variable is set to `True`.
119       
120    """
121    if environ.orange_no_deprecated_members:
122        return lambda cls: cls
123   
124    def is_wrapped(method):
125        """ Is member method already wrapped.
126        """
127        if getattr(method, "_deprecate_members_wrapped", False):
128            return True
129        elif hasattr(method, "im_func"):
130            im_func = method.im_func
131            return getattr(im_func, "_deprecate_members_wrapped", False)
132        else:
133            return False
134       
135    if wrap_methods == "all":
136        wrap_methods = universal_set()
137    elif not wrap_methods:
138        wrap_methods = set()
139       
140    def wrapper(cls):
141        cls_names = {}
142        # Create properties for accessing deprecated members
143        for old_name, new_name in name_map.items():
144            cls_names[old_name] = deprecated_attribute(old_name, new_name)
145           
146        # wrap member methods to map keyword arguments
147        for key, value in cls.__dict__.items():
148            if isinstance(value, (instancemethod, function)) \
149                and not is_wrapped(value) and key in wrap_methods:
150               
151                wrapped = deprecated_keywords(name_map)(value)
152                wrapped._deprecate_members_wrapped = True # A flag indicating this function already maps keywords
153                cls_names[key] = wrapped
154        if in_place:
155            for key, val in cls_names.items():
156                setattr(cls, key, val)
157            return cls
158        else:
159            return type(cls.__name__, (cls,), cls_names)
160       
161    return wrapper
162
163def deprecated_keywords(name_map):
164    """ Deprecates the keyword arguments of the function.
165   
166    Example ::
167   
168        >>> @deprecated_keywords({"myArg": "my_arg"})
169        ... def my_func(my_arg=None):
170        ...     print my_arg
171        ...
172        ...
173        >>> my_func(myArg="Arg")
174        __main__:1: DeprecationWarning: 'myArg' is deprecated. Use 'my_arg' instead!
175        Arg
176       
177    .. note:: This decorator does nothing if \
178        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
179        variable is set to `True`.
180       
181    """
182    if environ.orange_no_deprecated_members:
183        return lambda func: func
184    for name in name_map.values():
185        if name in name_map:
186            raise ValueError("Deprecation keys and values overlap; this could"
187                             " cause trouble!")
188   
189    def decorator(func):
190        @wraps(func)
191        def wrap_call(*args, **kwargs):
192            kwargs = dict(kwargs)
193            for name in name_map:
194                if name in kwargs:
195                    deprecation_warning(name, name_map[name], stacklevel=3)
196                    kwargs[name_map[name]] = kwargs[name]
197                    del kwargs[name]
198            return func(*args, **kwargs)
199        return wrap_call
200    return decorator
201
202def deprecated_attribute(old_name, new_name):
203    """ Return a property object that accesses an attribute named `new_name`
204    and raises a deprecation warning when doing so.
205
206    ..
207
208        >>> sys.stderr = sys.stdout
209   
210    Example ::
211   
212        >>> class A(object):
213        ...     def __init__(self):
214        ...         self.my_attr = "123"
215        ...     myAttr = deprecated_attribute("myAttr", "my_attr")
216        ...
217        ...
218        >>> a = A()
219        >>> print a.myAttr
220        ...:1: DeprecationWarning: 'myAttr' is deprecated. Use 'my_attr' instead!
221        123
222       
223    .. note:: This decorator does nothing and returns None if \
224        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
225        variable is set to `True`.
226       
227    """
228    if environ.orange_no_deprecated_members:
229        return None
230   
231    def fget(self):
232        deprecation_warning(old_name, new_name, stacklevel=3)
233        return getattr(self, new_name)
234   
235    def fset(self, value):
236        deprecation_warning(old_name, new_name, stacklevel=3)
237        setattr(self, new_name, value)
238   
239    def fdel(self):
240        deprecation_warning(old_name, new_name, stacklevel=3)
241        delattr(self, new_name)
242   
243    prop = property(fget, fset, fdel,
244                    doc="A deprecated member '%s'. Use '%s' instead." % (old_name, new_name))
245    return prop
246
247class class_property(object):
248    def __init__(self, fget=None, fset=None, fdel=None, doc="class property"):
249        self.fget = fget
250        self.fset = fset
251        self.fdel = fdel
252        self.__doc__ = doc
253       
254    def __get__(self, instance, owner):
255        if instance is None:
256            return self.fget(owner)
257        else:
258            return self.fget(instance)               
259           
260def deprecated_class_attribute(old_name, new_name):
261    """ Return a property object that accesses an class attribute
262    named `new_name` and raises a deprecation warning when doing so.
263   
264    """
265    if environ.orange_no_deprecated_members:
266        return None
267   
268    def fget(self):
269        deprecation_warning(old_name, new_name, stacklevel=3)
270        return getattr(self, new_name)
271       
272    prop = class_property(fget,
273                    doc="A deprecated class member '%s'. Use '%s' instead." % (old_name, new_name))
274    return prop
275
276def deprecated_function_name(func):
277    """ Return a wrapped function that raises an deprecation warning when
278    called. This should be used for deprecation of module level function names.
279   
280    Example ::
281   
282        >>> def func_a(arg):
283        ...    print "This is func_a  (used to be named funcA) called with", arg
284        ...
285        ...
286        >>> funcA = deprecated_function_name(func_a)
287        >>> funcA(None)
288         
289   
290    .. note:: This decorator does nothing and if \
291        :obj:`Orange.utils.environ.orange_no_deprecated_members` environment \
292        variable is set to `True`.
293       
294    """
295    if environ.orange_no_deprecated_members:
296        return func
297   
298    @wraps(func)
299    def wrapped(*args, **kwargs):
300        warnings.warn("Deprecated function name. Use %r instead!" % func.__name__,
301                      DeprecationWarning, stacklevel=2)
302        return func(*args, **kwargs)
303    return wrapped
304   
305class ConsoleProgressBar(object):
306    """ A class to for printing progress bar reports in the console.
307   
308    Example ::
309   
310        >>> import sys, time
311        >>> progress = ConsoleProgressBar("Example", output=sys.stdout)
312        >>> for i in range(100):
313        ...    progress.advance()
314        ...    # Or progress.set_state(i)
315        ...    time.sleep(0.01)
316        ...
317        ...
318        Example ===================================>100%
319       
320    """
321    def __init__(self, title="", charwidth=40, step=1, output=None):
322        """ Initialize the progress bar.
323       
324        :param title: The title for the progress bar.
325        :type title: str
326        :param charwidth: The maximum progress bar width in characters.
327       
328            .. todo:: Get the console width from the ``output`` if the
329                information can be retrieved.
330               
331        :type charwidth: int
332        :param step: A default step used if ``advance`` is called without
333            any  arguments
334       
335        :type step: int
336        :param output: The output file. If None (default) then ``sys.stderr``
337            is used.
338           
339        :type output: An file like object to print the progress report to.
340         
341        """
342        self.title = title + " "
343        self.charwidth = charwidth
344        self.step = step
345        self.currstring = ""
346        self.state = 0
347        if output is None:
348            output = sys.stderr
349        self.output = output
350
351    def clear(self, i=-1):
352        """ Clear the current progress line indicator string.
353        """
354        try:
355            if hasattr(self.output, "isatty") and self.output.isatty():
356                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
357            else:
358                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
359        except Exception: ## If for some reason we failed
360            self.output.write("\n")
361
362    def getstring(self):
363        """ Return the progress indicator string.
364        """
365        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
366        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth\
367            - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
368
369    def printline(self, string):
370        """ Print the ``string`` to the output file.
371        """
372        try:
373            self.clear()
374            self.output.write(string)
375            self.output.flush()
376        except Exception:
377            pass
378        self.currstring = string
379
380    def __call__(self, newstate=None):
381        """ Set the ``newstate`` as the current state of the progress bar.
382        ``newstate`` must be in the interval [0, 100].
383       
384        .. note:: ``set_state`` is the prefered way to set a new steate.
385       
386        :param newstate: The new state of the progress bar.
387        :type newstate: float
388         
389        """
390        if newstate is None:
391            self.advance()
392        else:
393            self.set_state(newstate)
394           
395    def set_state(self, newstate):
396        """ Set the ``newstate`` as the current state of the progress bar.
397        ``newstate`` must be in the interval [0, 100].
398       
399        :param newstate: The new state of the progress bar.
400        :type newstate: float
401       
402        """
403        if int(newstate) != int(self.state):
404            self.state = newstate
405            self.printline(self.getstring())
406        else:
407            self.state = newstate
408           
409    def advance(self, step=None):
410        """ Advance the current state by ``step``. If ``step`` is None use
411        the default step as set at class initialization.
412         
413        """
414        if step is None:
415            step = self.step
416           
417        newstate = self.state + step
418        self.set_state(newstate)
419
420    def finish(self):
421        """ Finish the progress bar (i.e. set the state to 100 and
422        print the final newline to the ``output`` file).
423        """
424        self.__call__(100)
425        self.output.write("\n")
426
427def progress_bar_milestones(count, iterations=100):
428    return set([int(i*count/float(iterations)) for i in range(iterations)])
429
430progressBarMilestones = deprecated_function_name(progress_bar_milestones)
431
432import random, types, sys
433import time
434
435def getobjectname(x, default=""):
436    if type(x)==types.StringType:
437        return x
438     
439    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
440        if getattr(x, i, ""):
441            return getattr(x, i)
442
443    if hasattr(x, "__class__"):
444        r = repr(x.__class__)
445        if r[1:5]=="type":
446            return str(x.__class__)[7:-2]
447        elif r[1:6]=="class":
448            return str(x.__class__)[8:-2]
449    return default
450
451
452def demangle_examples(x):
453    if type(x)==types.TupleType:
454        return x
455    else:
456        return x, 0
457
458def frange(*argw):
459    """ Like builtin `range` but works with floats
460    """
461    start, stop, step = 0.0, 1.0, 0.1
462    if len(argw)==1:
463        start=step=argw[0]
464    elif len(argw)==2:
465        stop, step = argw
466    elif len(argw)==3:
467        start, stop, step = argw
468    elif len(argw)>3:
469        raise AttributeError, "1-3 arguments expected"
470
471    stop+=1e-10
472    i=0
473    res=[]
474    while 1:
475        f=start+i*step
476        if f>stop:
477            break
478        res.append(f)
479        i+=1
480    return res
481
482verbose = 0
483
484def print_verbose(text, *verb):
485    if len(verb) and verb[0] or verbose:
486        print text
487
488def lru_cache(maxsize=100):
489    """ A least recently used cache function decorator.
490    (Similar to the functools.lru_cache in python 3.2)
491    """
492   
493    def decorating_function(func):
494        import functools
495        cache = {}
496       
497        @functools.wraps(func)
498        def wrapped(*args, **kwargs):
499            key = args + tuple(sorted(kwargs.items()))
500            if key not in cache:
501                res = func(*args, **kwargs)
502                cache[key] = (time.time(), res)
503                if len(cache) > maxsize:
504                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
505                    del cache[key]
506            else:
507                _, res = cache[key]
508                cache[key] = (time.time(), res) # update the time
509               
510            return res
511       
512        def clear():
513            cache.clear()
514       
515        wrapped.clear = clear
516        wrapped._cache = cache
517       
518        return wrapped
519    return decorating_function
520
521#from Orange.misc.render import contextmanager
522from contextlib import contextmanager
523
524@contextmanager
525def member_set(obj, name, val):
526    """ A context manager that sets member ``name`` on ``obj`` to ``val``
527    and restores the previous value on exit.
528    """
529    old_val = getattr(obj, name, val)
530    setattr(obj, name, val)
531    yield
532    setattr(obj, name, old_val)
533   
534   
535class recursion_limit(object):
536    """ A context manager that sets a new recursion limit.
537   
538    """
539    def __init__(self, limit=1000):
540        self.limit = limit
541       
542    def __enter__(self):
543        self.old_limit = sys.getrecursionlimit()
544        sys.setrecursionlimit(self.limit)
545   
546    def __exit__(self, exc_type, exc_val, exc_tb):
547        sys.setrecursionlimit(self.old_limit)
548       
549"""
550Some utility functions common to Orange classes.
551 
552"""
553
554def _orange__new__(base=None):
555    """ Return an orange 'schizofrenic' __new__ class method.
556   
557    :param base: base orange class (default orange.Learner)
558    :type base: type
559         
560    Example::
561        class NewOrangeLearner(orange.Learner):
562            __new__ = _orange__new(orange.Learner)
563       
564    """
565    if base is None:
566        import Orange
567        base = Orange.core.Learner
568       
569    @wraps(base.__new__)
570    def _orange__new_wrapped(cls, data=None, **kwargs):
571        if base == object:
572            self = base.__new__(cls)
573        else:
574            self = base.__new__(cls, **kwargs)
575
576        if data:
577            self.__init__(**kwargs)
578            return self.__call__(data)
579        else:
580            return self
581    return _orange__new_wrapped
582
583
584def _orange__reduce__(self):
585    """ A default __reduce__ method for orange types. Assumes the object
586    can be reconstructed with the call `constructor(__dict__)` where __dict__
587    if the stored (pickled) __dict__ attribute.
588   
589    Example::
590        class NewOrangeType(orange.Learner):
591            __reduce__ = _orange__reduce()
592    """ 
593    return type(self), (), dict(self.__dict__)
594
595demangleExamples = deprecated_function_name(demangle_examples)
596printVerbose = deprecated_function_name(print_verbose)
597
Note: See TracBrowser for help on using the repository browser.