source: orange/Orange/misc/__init__.py @ 10020:b96c450ff58a

Revision 10020:b96c450ff58a, 23.3 KB checked in by Matija Polajnar <matija.polajnar@…>, 2 years ago (diff)

Detect trivially detectable errorneous deprecated_members.

Line 
1"""
2.. index:: misc
3
4Module Orange.misc contains common functions and classes which are used in other modules.
5
6.. index: SymMatrix
7
8-----------------------
9SymMatrix
10-----------------------
11
12:obj:`SymMatrix` implements symmetric matrices of size fixed at
13construction time (and stored in :obj:`SymMatrix.dim`).
14
15.. class:: SymMatrix
16
17    .. attribute:: dim
18   
19        Matrix dimension.
20           
21    .. attribute:: matrix_type
22
23        Can be ``SymMatrix.Lower`` (0), ``SymMatrix.Upper`` (1),
24        ``SymMatrix.Symmetric`` (2, default), ``SymMatrix.LowerFilled`` (3) or
25        ``SymMatrix.Upper_Filled`` (4).
26
27        If the matrix type is ``Lower`` or ``Upper``, indexing
28        above or below the diagonal, respectively, will fail.
29        With ``LowerFilled`` and ``Upper_Filled``,
30        the elements upper or lower, respectively, still
31        exist and are set to zero, but they cannot be modified. The
32        default matrix type is ``Symmetric``, but can be changed
33        at any time.
34
35        If matrix type is ``Upper``, it is printed as:
36
37        >>> import Orange
38        >>> m = Orange.misc.SymMatrix(
39        ...     [[1],
40        ...      [2, 4],
41        ...      [3, 6, 9],
42        ...      [4, 8, 12, 16]])
43        >>> m.matrix_type = m.Upper
44        >>> print m
45        (( 1.000,  2.000,  3.000,  4.000),
46         (         4.000,  6.000,  8.000),
47         (                 9.000, 12.000),
48         (                        16.000))
49
50        Changing the type to ``LowerFilled`` changes the printout to
51
52        >>> m.matrix_type = m.LowerFilled
53        >>> print m
54        (( 1.000,  0.000,  0.000,  0.000),
55         ( 2.000,  4.000,  0.000,  0.000),
56         ( 3.000,  6.000,  9.000,  0.000),
57         ( 4.000,  8.000, 12.000, 16.000))
58   
59    .. method:: __init__(dim[, default_value])
60
61        Construct a symmetric matrix of the given dimension.
62
63        :param dim: matrix dimension
64        :type dim: int
65
66        :param default_value: default value (0 by default)
67        :type default_value: double
68       
69       
70    .. method:: __init__(instances)
71
72        Construct a new symmetric matrix containing the given data instances.
73        These can be given as Python list containing lists or tuples.
74
75        :param instances: data instances
76        :type instances: list of lists
77       
78        The following example fills a matrix created above with
79        data in a list::
80
81            import Orange
82            m = [[],
83                 [ 3],
84                 [ 2, 4],
85                 [17, 5, 4],
86                 [ 2, 8, 3, 8],
87                 [ 7, 5, 10, 11, 2],
88                 [ 8, 4, 1, 5, 11, 13],
89                 [ 4, 7, 12, 8, 10, 1, 5],
90                 [13, 9, 14, 15, 7, 8, 4, 6],
91                 [12, 10, 11, 15, 2, 5, 7, 3, 1]]
92                   
93            matrix = Orange.data.SymMatrix(m)
94
95        SymMatrix also stores diagonal elements. They are set
96        to zero, if they are not specified. The missing elements
97        (shorter lists) are set to zero as well. If a list
98        spreads over the diagonal, the constructor checks
99        for asymmetries. For instance, the matrix
100
101        ::
102
103            m = [[],
104                 [ 3,  0, f],
105                 [ 2,  4]]
106   
107        is only OK if f equals 2. Finally, no row can be longer
108        than matrix size. 
109
110    .. method:: get_values()
111   
112        Return all matrix values in a Python list.
113
114    .. method:: get_KNN(i, k)
115   
116        Return k columns with the lowest value in the i-th row.
117       
118        :param i: i-th row
119        :type i: int
120       
121        :param k: number of neighbors
122        :type k: int
123       
124    .. method:: avg_linkage(clusters)
125   
126        Return a symmetric matrix with average distances between given clusters. 
127     
128        :param clusters: list of clusters
129        :type clusters: list of lists
130       
131    .. method:: invert(type)
132   
133        Invert values in the symmetric matrix.
134       
135        :param type: 0 (-X), 1 (1 - X), 2 (max - X), 3 (1 / X)
136        :type type: int
137
138    .. method:: normalize(type)
139   
140        Normalize values in the symmetric matrix.
141       
142        :param type: 0 (normalize to [0, 1] interval), 1 (Sigmoid)
143        :type type: int
144       
145       
146-------------------
147Indexing
148-------------------
149
150For symmetric matrices the order of indices is not important:
151if ``m`` is a SymMatrix, then ``m[2, 4]`` addresses the same element as ``m[4, 2]``.
152
153..
154    .. literalinclude:: code/symmatrix.py
155        :lines: 1-6
156
157>>> import Orange
158>>> m = Orange.misc.SymMatrix(4)
159>>> for i in range(4):
160...    for j in range(i+1):
161...        m[i, j] = (i+1)*(j+1)
162
163
164Although only the lower left half of the matrix was set explicitely,
165the whole matrix is constructed.
166
167>>> print m
168(( 1.000,  2.000,  3.000,  4.000),
169 ( 2.000,  4.000,  6.000,  8.000),
170 ( 3.000,  6.000,  9.000, 12.000),
171 ( 4.000,  8.000, 12.000, 16.000))
172 
173Entire rows are indexed with a single index. They can be iterated
174over in a for loop or sliced (with, for example, ``m[:3]``):
175
176>>> print m[1]
177(2.0, 4.0, 6.0, 8.0)
178>>> m.matrix_type = m.Lower
179>>> for row in m:
180...     print row
181(1.0,)
182(2.0, 4.0)
183(3.0, 6.0, 9.0)
184(4.0, 8.0, 12.0, 16.0)
185
186.. index: Random number generator
187
188-----------------------
189Random number generator
190-----------------------
191
192:obj:`Random` uses the
193`Mersenne twister <http://en.wikipedia.org/wiki/Mersenne_twister>`_ algorithm
194to generate random numbers.
195
196::
197
198    >>> import Orange
199    >>> rg = Orange.misc.Random(42)
200    >>> rg(10)
201    4
202    >>> rg(10)
203    7
204    >>> rg.uses  # We called rg two times.
205    2
206    >>> rg.reset()
207    >>> rg(10)
208    4
209    >>> rg(10)
210    7
211    >>> rg.uses
212    2
213
214
215.. class:: Random(initseed)
216
217    :param initseed: Seed used for initializing the random generator.
218    :type initseed: int
219
220    .. method:: __call__(n)
221
222        Return a random integer R such that 0 <= R < n.
223
224        :type n: int
225
226    .. method:: reset([initseed])
227
228        Reinitialize the random generator with `initseed`. If `initseed`
229        is not given use the existing value of attribute `initseed`.
230
231    .. attribute:: uses
232       
233        The number of times the generator was called after
234        initialization/reset.
235   
236    .. attribute:: initseed
237
238        Random seed.
239
240Two examples or random number generator uses found in the documentation
241are :obj:`Orange.evaluation.testing` and :obj:`Orange.data.Table`.
242
243.. automodule:: Orange.misc.counters
244  :members:
245
246.. automodule:: Orange.misc.render
247  :members:
248
249.. automodule:: Orange.misc.selection
250
251.. automodule:: Orange.misc.addons
252
253.. automodule:: Orange.misc.serverfiles
254
255.. automodule:: Orange.misc.environ
256
257.. automodule:: Orange.misc.r
258
259
260"""
261import environ
262import counters
263import render
264
265from Orange.core import RandomGenerator as Random
266from orange import SymMatrix
267
268# addons is intentionally not imported; if it were, add-ons' directories would
269# be added to the python path. If that sounds OK, this can be changed ...
270
271__all__ = ["counters", "selection", "render", "serverfiles",
272           "deprecated_members", "deprecated_keywords",
273           "deprecated_attribute", "deprecation_warning"]
274
275import random, types, sys
276import time
277
278def getobjectname(x, default=""):
279    if type(x)==types.StringType:
280        return x
281     
282    for i in ["name", "shortDescription", "description", "func_doc", "func_name"]:
283        if getattr(x, i, ""):
284            return getattr(x, i)
285
286    if hasattr(x, "__class__"):
287        r = repr(x.__class__)
288        if r[1:5]=="type":
289            return str(x.__class__)[7:-2]
290        elif r[1:6]=="class":
291            return str(x.__class__)[8:-2]
292    return default
293
294
295def demangle_examples(x):
296    if type(x)==types.TupleType:
297        return x
298    else:
299        return x, 0
300
301
302def frange(*argw):
303    """ Like builtin `range` but works with floats
304    """
305    start, stop, step = 0.0, 1.0, 0.1
306    if len(argw)==1:
307        start=step=argw[0]
308    elif len(argw)==2:
309        stop, step = argw
310    elif len(argw)==3:
311        start, stop, step = argw
312    elif len(argw)>3:
313        raise AttributeError, "1-3 arguments expected"
314
315    stop+=1e-10
316    i=0
317    res=[]
318    while 1:
319        f=start+i*step
320        if f>stop:
321            break
322        res.append(f)
323        i+=1
324    return res
325
326verbose = 0
327
328def print_verbose(text, *verb):
329    if len(verb) and verb[0] or verbose:
330        print text
331
332__doc__ += """\
333------------------
334Reporting progress
335------------------
336
337.. autoclass:: Orange.misc.ConsoleProgressBar
338    :members:
339
340"""
341
342class ConsoleProgressBar(object):
343    """ A class to for printing progress bar reports in the console.
344   
345    Example ::
346   
347        >>> import sys, time
348        >>> progress = ConsoleProgressBar("Example", output=sys.stdout)
349        >>> for i in range(100):
350        ...    progress.advance()
351        ...    # Or progress.set_state(i)
352        ...    time.sleep(0.01)
353        ...
354        ...
355        Example ===================================>100%
356       
357    """
358    def __init__(self, title="", charwidth=40, step=1, output=None):
359        """ Initialize the progress bar.
360       
361        :param title: The title for the progress bar.
362        :type title: str
363        :param charwidth: The maximum progress bar width in characters.
364       
365            .. todo:: Get the console width from the ``output`` if the
366                information can be retrieved.
367               
368        :type charwidth: int
369        :param step: A default step used if ``advance`` is called without
370            any  arguments
371       
372        :type step: int
373        :param output: The output file. If None (default) then ``sys.stderr``
374            is used.
375           
376        :type output: An file like object to print the progress report to.
377         
378        """
379        self.title = title + " "
380        self.charwidth = charwidth
381        self.step = step
382        self.currstring = ""
383        self.state = 0
384        if output is None:
385            output = sys.stderr
386        self.output = output
387
388    def clear(self, i=-1):
389        """ Clear the current progress line indicator string.
390        """
391        try:
392            if hasattr(self.output, "isatty") and self.output.isatty():
393                self.output.write("\b" * (i if i != -1 else len(self.currstring)))
394            else:
395                self.output.seek(-i if i != -1 else -len(self.currstring), 2)
396        except Exception: ## If for some reason we failed
397            self.output.write("\n")
398
399    def getstring(self):
400        """ Return the progress indicator string.
401        """
402        progchar = int(round(float(self.state) * (self.charwidth - 5) / 100.0))
403        return self.title + "=" * (progchar) + ">" + " " * (self.charwidth\
404            - 5 - progchar) + "%3i" % int(round(self.state)) + "%"
405
406    def printline(self, string):
407        """ Print the ``string`` to the output file.
408        """
409        try:
410            self.clear()
411            self.output.write(string)
412            self.output.flush()
413        except Exception:
414            pass
415        self.currstring = string
416
417    def __call__(self, newstate=None):
418        """ Set the ``newstate`` as the current state of the progress bar.
419        ``newstate`` must be in the interval [0, 100].
420       
421        .. note:: ``set_state`` is the prefered way to set a new steate.
422       
423        :param newstate: The new state of the progress bar.
424        :type newstate: float
425         
426        """
427        if newstate is None:
428            self.advance()
429        else:
430            self.set_state(newstate)
431           
432    def set_state(self, newstate):
433        """ Set the ``newstate`` as the current state of the progress bar.
434        ``newstate`` must be in the interval [0, 100].
435       
436        :param newstate: The new state of the progress bar.
437        :type newstate: float
438       
439        """
440        if int(newstate) != int(self.state):
441            self.state = newstate
442            self.printline(self.getstring())
443        else:
444            self.state = newstate
445           
446    def advance(self, step=None):
447        """ Advance the current state by ``step``. If ``step`` is None use
448        the default step as set at class initialization.
449         
450        """
451        if step is None:
452            step = self.step
453           
454        newstate = self.state + step
455        self.set_state(newstate)
456
457    def finish(self):
458        """ Finish the progress bar (i.e. set the state to 100 and
459        print the final newline to the ``output`` file).
460        """
461        self.__call__(100)
462        self.output.write("\n")
463
464def progress_bar_milestones(count, iterations=100):
465    return set([int(i*count/float(iterations)) for i in range(iterations)])
466
467def lru_cache(maxsize=100):
468    """ A least recently used cache function decorator.
469    (Similar to the functools.lru_cache in python 3.2)
470    """
471   
472    def decorating_function(func):
473        import functools
474        cache = {}
475       
476        @functools.wraps(func)
477        def wrapped(*args, **kwargs):
478            key = args + tuple(sorted(kwargs.items()))
479            if key not in cache:
480                res = func(*args, **kwargs)
481                cache[key] = (time.time(), res)
482                if len(cache) > maxsize:
483                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
484                    del cache[key]
485            else:
486                _, res = cache[key]
487                cache[key] = (time.time(), res) # update the time
488               
489            return res
490       
491        def clear():
492            cache.clear()
493       
494        wrapped.clear = clear
495        wrapped._cache = cache
496       
497        return wrapped
498    return decorating_function
499
500#from Orange.misc.render import contextmanager
501from contextlib import contextmanager
502
503
504@contextmanager
505def member_set(obj, name, val):
506    """ A context manager that sets member ``name`` on ``obj`` to ``val``
507    and restores the previous value on exit.
508    """
509    old_val = getattr(obj, name, val)
510    setattr(obj, name, val)
511    yield
512    setattr(obj, name, old_val)
513   
514   
515class recursion_limit(object):
516    """ A context manager that sets a new recursion limit.
517   
518    """
519    def __init__(self, limit=1000):
520        self.limit = limit
521       
522    def __enter__(self):
523        self.old_limit = sys.getrecursionlimit()
524        sys.setrecursionlimit(self.limit)
525   
526    def __exit__(self, exc_type, exc_val, exc_tb):
527        sys.setrecursionlimit(self.old_limit)
528
529
530__doc__ += """\
531-----------------------------
532Deprecation utility functions
533-----------------------------
534
535.. autofunction:: Orange.misc.deprecation_warning
536
537.. autofunction:: Orange.misc.deprecated_members
538
539.. autofunction:: Orange.misc.deprecated_keywords
540
541.. autofunction:: Orange.misc.deprecated_attribute
542
543.. autofunction:: Orange.misc.deprecated_function_name
544
545"""
546
547import warnings
548def deprecation_warning(old, new, stacklevel=-2):
549    """ Raise a deprecation warning of an obsolete attribute access.
550   
551    :param old: Old attribute name (used in warning message).
552    :param new: New attribute name (used in warning message).
553   
554    """
555    warnings.warn("'%s' is deprecated. Use '%s' instead!" % (old, new), DeprecationWarning, stacklevel=stacklevel)
556   
557# We need to get the instancemethod type
558class _Foo():
559    def bar(self):
560        pass
561instancemethod = type(_Foo.bar)
562del _Foo
563
564function = type(lambda: None)
565
566class universal_set(set):
567    """ A universal set, pretends it contains everything.
568    """
569    def __contains__(self, value):
570        return True
571   
572from functools import wraps
573
574def deprecated_members(name_map, wrap_methods="all", in_place=True):
575    """ Decorate a class with properties for accessing attributes, and methods
576    with deprecated names. In addition methods from the `wrap_methods` list
577    will be wrapped to receive mapped keyword arguments.
578   
579    :param name_map: A dictionary mapping old into new names.
580    :type name_map: dict
581   
582    :param wrap_methods: A list of method names to wrap. Wrapped methods will
583        be called with mapped keyword arguments (by default all methods will
584        be wrapped).
585    :type wrap_methods: list
586   
587    :param in_place: If True the class will be modified in place, otherwise
588        it will be subclassed (default True).
589    :type in_place: bool
590   
591    Example ::
592           
593        >>> class A(object):
594        ...     def __init__(self, foo_bar="bar"):
595        ...         self.set_foo_bar(foo_bar)
596        ...     
597        ...     def set_foo_bar(self, foo_bar="bar"):
598        ...         self.foo_bar = foo_bar
599        ...
600        ... A = deprecated_members(
601        ... {"fooBar": "foo_bar",
602        ...  "setFooBar":"set_foo_bar"},
603        ... wrap_methods=["set_foo_bar", "__init__"])(A)
604        ...
605        ...
606        >>> a = A(fooBar="foo")
607        __main__:1: DeprecationWarning: 'fooBar' is deprecated. Use 'foo_bar' instead!
608        >>> print a.fooBar, a.foo_bar
609        foo foo
610        >>> a.setFooBar("FooBar!")
611        __main__:1: DeprecationWarning: 'setFooBar' is deprecated. Use 'set_foo_bar' instead!
612       
613    .. note:: This decorator does nothing if \
614        :obj:`Orange.misc.environ.orange_no_deprecated_members` environment \
615        variable is set to `True`.
616       
617    """
618    if environ.orange_no_deprecated_members:
619        return lambda cls: cls
620   
621    def is_wrapped(method):
622        """ Is member method already wrapped.
623        """
624        if getattr(method, "_deprecate_members_wrapped", False):
625            return True
626        elif hasattr(method, "im_func"):
627            im_func = method.im_func
628            return getattr(im_func, "_deprecate_members_wrapped", False)
629        else:
630            return False
631       
632    if wrap_methods == "all":
633        wrap_methods = universal_set()
634    elif not wrap_methods:
635        wrap_methods = set()
636       
637    def wrapper(cls):
638        cls_names = {}
639        # Create properties for accessing deprecated members
640        for old_name, new_name in name_map.items():
641            cls_names[old_name] = deprecated_attribute(old_name, new_name)
642           
643        # wrap member methods to map keyword arguments
644        for key, value in cls.__dict__.items():
645            if isinstance(value, (instancemethod, function)) \
646                and not is_wrapped(value) and key in wrap_methods:
647               
648                wrapped = deprecated_keywords(name_map)(value)
649                wrapped._deprecate_members_wrapped = True # A flag indicating this function already maps keywords
650                cls_names[key] = wrapped
651        if in_place:
652            for key, val in cls_names.items():
653                setattr(cls, key, val)
654            return cls
655        else:
656            return type(cls.__name__, (cls,), cls_names)
657       
658    return wrapper
659
660def deprecated_keywords(name_map):
661    """ Deprecates the keyword arguments of the function.
662   
663    Example ::
664   
665        >>> @deprecated_keywords({"myArg": "my_arg"})
666        ... def my_func(my_arg=None):
667        ...     print my_arg
668        ...
669        ...
670        >>> my_func(myArg="Arg")
671        __main__:1: DeprecationWarning: 'myArg' is deprecated. Use 'my_arg' instead!
672        Arg
673       
674    .. note:: This decorator does nothing if \
675        :obj:`Orange.misc.environ.orange_no_deprecated_members` environment \
676        variable is set to `True`.
677       
678    """
679    if environ.orange_no_deprecated_members:
680        return lambda func: func
681    for name in name_map.values():
682        if name in name_map:
683            raise ValueError("Deprecation keys and values overlap; this could"
684                             " cause trouble!")
685   
686    def decorator(func):
687        @wraps(func)
688        def wrap_call(*args, **kwargs):
689            kwargs = dict(kwargs)
690            for name in name_map:
691                if name in kwargs:
692                    deprecation_warning(name, name_map[name], stacklevel=3)
693                    kwargs[name_map[name]] = kwargs[name]
694                    del kwargs[name]
695            return func(*args, **kwargs)
696        return wrap_call
697    return decorator
698
699def deprecated_attribute(old_name, new_name):
700    """ Return a property object that accesses an attribute named `new_name`
701    and raises a deprecation warning when doing so.
702
703    ..
704
705        >>> sys.stderr = sys.stdout
706   
707    Example ::
708   
709        >>> class A(object):
710        ...     def __init__(self):
711        ...         self.my_attr = "123"
712        ...     myAttr = deprecated_attribute("myAttr", "my_attr")
713        ...
714        ...
715        >>> a = A()
716        >>> print a.myAttr
717        ...:1: DeprecationWarning: 'myAttr' is deprecated. Use 'my_attr' instead!
718        123
719       
720    .. note:: This decorator does nothing and returns None if \
721        :obj:`Orange.misc.environ.orange_no_deprecated_members` environment \
722        variable is set to `True`.
723       
724    """
725    if environ.orange_no_deprecated_members:
726        return None
727   
728    def fget(self):
729        deprecation_warning(old_name, new_name, stacklevel=3)
730        return getattr(self, new_name)
731   
732    def fset(self, value):
733        deprecation_warning(old_name, new_name, stacklevel=3)
734        setattr(self, new_name, value)
735   
736    def fdel(self):
737        deprecation_warning(old_name, new_name, stacklevel=3)
738        delattr(self, new_name)
739   
740    prop = property(fget, fset, fdel,
741                    doc="A deprecated member '%s'. Use '%s' instead." % (old_name, new_name))
742    return prop
743
744
745def deprecated_function_name(func):
746    """ Return a wrapped function that raises an deprecation warning when
747    called. This should be used for deprecation of module level function names.
748   
749    Example ::
750   
751        >>> def func_a(arg):
752        ...    print "This is func_a  (used to be named funcA) called with", arg
753        ...
754        ...
755        >>> funcA = deprecated_function_name(func_a)
756        >>> funcA(None)
757         
758   
759    .. note:: This decorator does nothing and if \
760        :obj:`Orange.misc.environ.orange_no_deprecated_members` environment \
761        variable is set to `True`.
762       
763    """
764    if environ.orange_no_deprecated_members:
765        return func
766   
767    @wraps(func)
768    def wrapped(*args, **kwargs):
769        warnings.warn("Deprecated function name. Use %r instead!" % func.__name__,
770                      DeprecationWarning, stacklevel=2)
771        return func(*args, **kwargs)
772    return wrapped
773   
774
775"""
776Some utility functions common to Orange classes.
777 
778"""
779
780def _orange__new__(base=None):
781    """ Return an orange 'schizofrenic' __new__ class method.
782   
783    :param base: base orange class (default orange.Learner)
784    :type base: type
785         
786    Example::
787        class NewOrangeLearner(orange.Learner):
788            __new__ = _orange__new(orange.Learner)
789       
790    """
791    if base is None:
792        import Orange
793        base = Orange.core.Learner
794       
795    @wraps(base.__new__)
796    def _orange__new_wrapped(cls, data=None, **kwargs):
797        self = base.__new__(cls, **kwargs)
798        if data:
799            self.__init__(**kwargs)
800            return self.__call__(data)
801        else:
802            return self
803    return _orange__new_wrapped
804
805
806def _orange__reduce__(self):
807    """ A default __reduce__ method for orange types. Assumes the object
808    can be reconstructed with the call `constructor(__dict__)` where __dict__
809    if the stored (pickled) __dict__ attribute.
810   
811    Example::
812        class NewOrangeType(orange.Learner):
813            __reduce__ = _orange__reduce()
814    """ 
815    return type(self), (), dict(self.__dict__)
816
817
818demangleExamples = deprecated_function_name(demangle_examples)
819progressBarMilestones = deprecated_function_name(progress_bar_milestones)
820printVerbose = deprecated_function_name(print_verbose)
821
822# Must be imported after deprecation function definitions
823import selection
Note: See TracBrowser for help on using the repository browser.