source: orange/Orange/misc/__init__.py @ 10246:11b418321f79

Revision 10246:11b418321f79, 27.7 KB checked in by janezd <janez.demsar@…>, 2 years ago (diff)

Unified argument names in and 2.5 and 3.0; numerous other changes in documentation

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