commit bf3427bd1be7356f591e59b698d45ee8844e5df9 Author: MiCHiLU Date: Tue Sep 18 18:32:48 2012 +0900 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..524e741 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +build +dist +tags + +syntax: glob + +*.py? +*.so +*.sw? +*~ +.DS_Store + +syntax: regexp diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..45f2c10 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,5 @@ +---------------- +2012-09-18 3.2.3 +---------------- + +This was the first release. Roughly equivalent to Python 3.2.3. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..43388e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,289 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations (now Zope +Corporation, see http://www.zope.com). In 2001, the Python Software +Foundation (PSF, see http://www.python.org/psf/) was formed, a +non-profit organization created specifically to own Python-related +Intellectual Property. Zope Corporation is a sponsoring member of +the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.2 2.1.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2.1 2.2 2002 PSF yes + 2.2.2 2.2.1 2002 PSF yes + 2.2.3 2.2.2 2003 PSF yes + 2.3 2.2.2 2002-2003 PSF yes + 2.3.1 2.3 2002-2003 PSF yes + 2.3.2 2.3.1 2002-2003 PSF yes + 2.3.3 2.3.2 2002-2003 PSF yes + 2.3.4 2.3.3 2004 PSF yes + 2.3.5 2.3.4 2005 PSF yes + 2.4 2.3 2004 PSF yes + 2.4.1 2.4 2005 PSF yes + 2.4.2 2.4.1 2005 PSF yes + 2.4.3 2.4.2 2006 PSF yes + 2.4.4 2.4.3 2006 PSF yes + 2.5 2.4 2006 PSF yes + 2.5.1 2.5 2007 PSF yes + 2.5.2 2.5.1 2008 PSF yes + 2.5.3 2.5.2 2008 PSF yes + 2.6 2.5 2008 PSF yes + 2.6.1 2.6 2008 PSF yes + 2.6.2 2.6.1 2009 PSF yes + 2.6.3 2.6.2 2009 PSF yes + 2.6.4 2.6.3 2009 PSF yes + 2.6.5 2.6.4 2010 PSF yes + 3.0 2.6 2008 PSF yes + 3.0.1 3.0 2009 PSF yes + 3.1 3.0.1 2009 PSF yes + 3.1.1 3.1 2009 PSF yes + 3.1.2 3.1.1 2010 PSF yes + 3.1.3 3.1.2 2010 PSF yes + 3.1.4 3.1.3 2011 PSF yes + 3.2 3.1 2011 PSF yes + 3.2.1 3.2 2011 PSF yes + 3.2.2 3.2.1 2011 PSF yes + 3.2.3 3.2.2 2012 PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..3784af7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +include *.txt +include LICENSE* +include setup.py +include setup.cfg +include ChangeLog +include MANIFEST.in + +include *.py + +prune build +prune dist +prune .git* diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..1585aed --- /dev/null +++ b/README.txt @@ -0,0 +1,10 @@ +This is a backport of the Python 3.2 functools module for use on +Python versions 2.4 through 2.7. + +Refer to the Python 3.2 documentation for usage information: + http://docs.python.org/3.2/library/functools.html + +Bugs? Try to reproduce them on the latest Python 3.2.x itself and file bug +reports on http://bugs.python.org/. + +-- ENDOH takanao djmchl@gmail.com diff --git a/functools32.py b/functools32.py new file mode 100644 index 0000000..85ea257 --- /dev/null +++ b/functools32.py @@ -0,0 +1,208 @@ +"""functools.py - Tools for working with functions and callable objects +""" +# Python module wrapper for _functools C module +# to allow utilities written in Python to be added +# to the functools module. +# Written by Nick Coghlan +# and Raymond Hettinger +# Copyright (C) 2006-2010 Python Software Foundation. +# See C source code for _functools credits/copyright + +__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', + 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial'] + +from _functools import partial, reduce +from collections import OrderedDict, namedtuple +try: + from _thread import allocate_lock as Lock +except: + from _dummy_thread import allocate_lock as Lock + +# update_wrapper() and wraps() are tools to help write +# wrapper functions that can handle naive introspection + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__', '__annotations__') +WRAPPER_UPDATES = ('__dict__',) +def update_wrapper(wrapper, + wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Update a wrapper function to look like the wrapped function + + wrapper is the function to be updated + wrapped is the original function + assigned is a tuple naming the attributes assigned directly + from the wrapped function to the wrapper function (defaults to + functools.WRAPPER_ASSIGNMENTS) + updated is a tuple naming the attributes of the wrapper that + are updated with the corresponding attribute from the wrapped + function (defaults to functools.WRAPPER_UPDATES) + """ + wrapper.__wrapped__ = wrapped + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + pass + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + # Return the wrapper so this can be used as a decorator via partial() + return wrapper + +def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Decorator factory to apply update_wrapper() to a wrapper function + + Returns a decorator that invokes update_wrapper() with the decorated + function as the wrapper argument and the arguments to wraps() as the + remaining arguments. Default arguments are as for update_wrapper(). + This is a convenience function to simplify applying partial() to + update_wrapper(). + """ + return partial(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + +def total_ordering(cls): + """Class decorator that fills in missing ordering methods""" + convert = { + '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)), + ('__le__', lambda self, other: self < other or self == other), + ('__ge__', lambda self, other: not self < other)], + '__le__': [('__ge__', lambda self, other: not self <= other or self == other), + ('__lt__', lambda self, other: self <= other and not self == other), + ('__gt__', lambda self, other: not self <= other)], + '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)), + ('__ge__', lambda self, other: self > other or self == other), + ('__le__', lambda self, other: not self > other)], + '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other), + ('__gt__', lambda self, other: self >= other and not self == other), + ('__lt__', lambda self, other: not self >= other)] + } + # Find user-defined comparisons (not those inherited from object). + roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)] + if not roots: + raise ValueError('must define at least one ordering operation: < > <= >=') + root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ + for opname, opfunc in convert[root]: + if opname not in roots: + opfunc.__name__ = opname + opfunc.__doc__ = getattr(int, opname).__doc__ + setattr(cls, opname, opfunc) + return cls + +def cmp_to_key(mycmp): + """Convert a cmp= function into a key= function""" + class K(object): + __slots__ = ['obj'] + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + def __le__(self, other): + return mycmp(self.obj, other.obj) <= 0 + def __ge__(self, other): + return mycmp(self.obj, other.obj) >= 0 + def __ne__(self, other): + return mycmp(self.obj, other.obj) != 0 + __hash__ = None + return K + +_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize") + +def lru_cache(maxsize=100): + """Least-recently-used cache decorator. + + If *maxsize* is set to None, the LRU features are disabled and the cache + can grow without bound. + + Arguments to the cached function must be hashable. + + View the cache statistics named tuple (hits, misses, maxsize, currsize) with + f.cache_info(). Clear the cache and statistics with f.cache_clear(). + Access the underlying function with f.__wrapped__. + + See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + + """ + # Users should only access the lru_cache through its public API: + # cache_info, cache_clear, and f.__wrapped__ + # The internals of the lru_cache are encapsulated for thread safety and + # to allow the implementation to change (including a possible C version). + + def decorating_function(user_function, + tuple=tuple, sorted=sorted, len=len, KeyError=KeyError): + + hits = misses = 0 + kwd_mark = (object(),) # separates positional and keyword args + lock = Lock() # needed because OrderedDict isn't threadsafe + + if maxsize is None: + cache = dict() # simple cache without ordering or size limit + + @wraps(user_function) + def wrapper(*args, **kwds): + nonlocal hits, misses + key = args + if kwds: + key += kwd_mark + tuple(sorted(kwds.items())) + try: + result = cache[key] + hits += 1 + return result + except KeyError: + pass + result = user_function(*args, **kwds) + cache[key] = result + misses += 1 + return result + else: + cache = OrderedDict() # ordered least recent to most recent + cache_popitem = cache.popitem + cache_renew = cache.move_to_end + + @wraps(user_function) + def wrapper(*args, **kwds): + nonlocal hits, misses + key = args + if kwds: + key += kwd_mark + tuple(sorted(kwds.items())) + with lock: + try: + result = cache[key] + cache_renew(key) # record recent use of this key + hits += 1 + return result + except KeyError: + pass + result = user_function(*args, **kwds) + with lock: + cache[key] = result # record recent use of this key + misses += 1 + if len(cache) > maxsize: + cache_popitem(0) # purge least recently used cache entry + return result + + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(hits, misses, maxsize, len(cache)) + + def cache_clear(): + """Clear the cache and cache statistics""" + nonlocal hits, misses + with lock: + cache.clear() + hits = misses = 0 + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return wrapper + + return decorating_function diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..03b9855 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,6 @@ +[sdist] +formats=gztar,zip + +[bdist_rpm] +release = 1 +group = Development/Languages/Python diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..c93f48b --- /dev/null +++ b/setup.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +import sys +from distutils.core import setup + + +def main(): + if not sys.version.startswith('2.'): + sys.stderr.write('This backport is for Python 2.x only.\n') + sys.exit(1) + + setup( + name='functools32', + version='3.2.3', + description='Backport of the functools module from Python 3.2.3 for use on 2.x.', + long_description=""" +This is a backport of the functools standard library module from +Python 3.2.3 for use on Python 2.4, 2.5, 2.6 and 2.7. It includes +new features `lru_cache` (Least-recently-used cache decorator).""", + license='PSF license', + + maintainer='ENDOH takanao', + maintainer_email='djmchl@gmail.com', + url='https://github.com/MiCHiLU/python-functools32', + + py_modules=['functools32'], + ) + + +if __name__ == '__main__': + main() diff --git a/test b/test new file mode 100755 index 0000000..687201c --- /dev/null +++ b/test @@ -0,0 +1,9 @@ +#!/bin/bash + +# This is for my own convenience, edit it for your own environment. + +PYTHON=python +$PYTHON setup.py build || exit 1 + +export PYTHONPATH=./build/lib.linux-x86_64-2.6 +exec $PYTHON test_functools32.py diff --git a/test_functools32.py b/test_functools32.py new file mode 100644 index 0000000..270cab0 --- /dev/null +++ b/test_functools32.py @@ -0,0 +1,699 @@ +import functools +import collections +import sys +import unittest +from test import support +from weakref import proxy +import pickle +from random import choice + +@staticmethod +def PythonPartial(func, *args, **keywords): + 'Pure Python approximation of partial()' + def newfunc(*fargs, **fkeywords): + newkeywords = keywords.copy() + newkeywords.update(fkeywords) + return func(*(args + fargs), **newkeywords) + newfunc.func = func + newfunc.args = args + newfunc.keywords = keywords + return newfunc + +def capture(*args, **kw): + """capture all positional and keyword arguments""" + return args, kw + +def signature(part): + """ return the signature of a partial object """ + return (part.func, part.args, part.keywords, part.__dict__) + +class TestPartial(unittest.TestCase): + + thetype = functools.partial + + def test_basic_examples(self): + p = self.thetype(capture, 1, 2, a=10, b=20) + self.assertEqual(p(3, 4, b=30, c=40), + ((1, 2, 3, 4), dict(a=10, b=30, c=40))) + p = self.thetype(map, lambda x: x*10) + self.assertEqual(list(p([1,2,3,4])), [10, 20, 30, 40]) + + def test_attributes(self): + p = self.thetype(capture, 1, 2, a=10, b=20) + # attributes should be readable + self.assertEqual(p.func, capture) + self.assertEqual(p.args, (1, 2)) + self.assertEqual(p.keywords, dict(a=10, b=20)) + # attributes should not be writable + if not isinstance(self.thetype, type): + return + self.assertRaises(AttributeError, setattr, p, 'func', map) + self.assertRaises(AttributeError, setattr, p, 'args', (1, 2)) + self.assertRaises(AttributeError, setattr, p, 'keywords', dict(a=1, b=2)) + + p = self.thetype(hex) + try: + del p.__dict__ + except TypeError: + pass + else: + self.fail('partial object allowed __dict__ to be deleted') + + def test_argument_checking(self): + self.assertRaises(TypeError, self.thetype) # need at least a func arg + try: + self.thetype(2)() + except TypeError: + pass + else: + self.fail('First arg not checked for callability') + + def test_protection_of_callers_dict_argument(self): + # a caller's dictionary should not be altered by partial + def func(a=10, b=20): + return a + d = {'a':3} + p = self.thetype(func, a=5) + self.assertEqual(p(**d), 3) + self.assertEqual(d, {'a':3}) + p(b=7) + self.assertEqual(d, {'a':3}) + + def test_arg_combinations(self): + # exercise special code paths for zero args in either partial + # object or the caller + p = self.thetype(capture) + self.assertEqual(p(), ((), {})) + self.assertEqual(p(1,2), ((1,2), {})) + p = self.thetype(capture, 1, 2) + self.assertEqual(p(), ((1,2), {})) + self.assertEqual(p(3,4), ((1,2,3,4), {})) + + def test_kw_combinations(self): + # exercise special code paths for no keyword args in + # either the partial object or the caller + p = self.thetype(capture) + self.assertEqual(p(), ((), {})) + self.assertEqual(p(a=1), ((), {'a':1})) + p = self.thetype(capture, a=1) + self.assertEqual(p(), ((), {'a':1})) + self.assertEqual(p(b=2), ((), {'a':1, 'b':2})) + # keyword args in the call override those in the partial object + self.assertEqual(p(a=3, b=2), ((), {'a':3, 'b':2})) + + def test_positional(self): + # make sure positional arguments are captured correctly + for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]: + p = self.thetype(capture, *args) + expected = args + ('x',) + got, empty = p('x') + self.assertTrue(expected == got and empty == {}) + + def test_keyword(self): + # make sure keyword arguments are captured correctly + for a in ['a', 0, None, 3.5]: + p = self.thetype(capture, a=a) + expected = {'a':a,'x':None} + empty, got = p(x=None) + self.assertTrue(expected == got and empty == ()) + + def test_no_side_effects(self): + # make sure there are no side effects that affect subsequent calls + p = self.thetype(capture, 0, a=1) + args1, kw1 = p(1, b=2) + self.assertTrue(args1 == (0,1) and kw1 == {'a':1,'b':2}) + args2, kw2 = p() + self.assertTrue(args2 == (0,) and kw2 == {'a':1}) + + def test_error_propagation(self): + def f(x, y): + x / y + self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0)) + self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0) + self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0) + self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1) + + def test_weakref(self): + f = self.thetype(int, base=16) + p = proxy(f) + self.assertEqual(f.func, p.func) + f = None + self.assertRaises(ReferenceError, getattr, p, 'func') + + def test_with_bound_and_unbound_methods(self): + data = list(map(str, range(10))) + join = self.thetype(str.join, '') + self.assertEqual(join(data), '0123456789') + join = self.thetype(''.join) + self.assertEqual(join(data), '0123456789') + + def test_repr(self): + args = (object(), object()) + args_repr = ', '.join(repr(a) for a in args) + kwargs = {'a': object(), 'b': object()} + kwargs_repr = ', '.join("%s=%r" % (k, v) for k, v in kwargs.items()) + if self.thetype is functools.partial: + name = 'functools.partial' + else: + name = self.thetype.__name__ + + f = self.thetype(capture) + self.assertEqual('{}({!r})'.format(name, capture), + repr(f)) + + f = self.thetype(capture, *args) + self.assertEqual('{}({!r}, {})'.format(name, capture, args_repr), + repr(f)) + + f = self.thetype(capture, **kwargs) + self.assertEqual('{}({!r}, {})'.format(name, capture, kwargs_repr), + repr(f)) + + f = self.thetype(capture, *args, **kwargs) + self.assertEqual('{}({!r}, {}, {})'.format(name, capture, args_repr, kwargs_repr), + repr(f)) + + def test_pickle(self): + f = self.thetype(signature, 'asdf', bar=True) + f.add_something_to__dict__ = True + f_copy = pickle.loads(pickle.dumps(f)) + self.assertEqual(signature(f), signature(f_copy)) + +class PartialSubclass(functools.partial): + pass + +class TestPartialSubclass(TestPartial): + + thetype = PartialSubclass + +class TestPythonPartial(TestPartial): + + thetype = PythonPartial + + # the python version hasn't a nice repr + def test_repr(self): pass + + # the python version isn't picklable + def test_pickle(self): pass + +class TestUpdateWrapper(unittest.TestCase): + + def check_wrapper(self, wrapper, wrapped, + assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + # Check attributes were assigned + for name in assigned: + self.assertTrue(getattr(wrapper, name) is getattr(wrapped, name)) + # Check attributes were updated + for name in updated: + wrapper_attr = getattr(wrapper, name) + wrapped_attr = getattr(wrapped, name) + for key in wrapped_attr: + self.assertTrue(wrapped_attr[key] is wrapper_attr[key]) + + def _default_update(self): + def f(a:'This is a new annotation'): + """This is a test""" + pass + f.attr = 'This is also a test' + def wrapper(b:'This is the prior annotation'): + pass + functools.update_wrapper(wrapper, f) + return wrapper, f + + def test_default_update(self): + wrapper, f = self._default_update() + self.check_wrapper(wrapper, f) + self.assertIs(wrapper.__wrapped__, f) + self.assertEqual(wrapper.__name__, 'f') + self.assertEqual(wrapper.attr, 'This is also a test') + self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation') + self.assertNotIn('b', wrapper.__annotations__) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_default_update_doc(self): + wrapper, f = self._default_update() + self.assertEqual(wrapper.__doc__, 'This is a test') + + def test_no_update(self): + def f(): + """This is a test""" + pass + f.attr = 'This is also a test' + def wrapper(): + pass + functools.update_wrapper(wrapper, f, (), ()) + self.check_wrapper(wrapper, f, (), ()) + self.assertEqual(wrapper.__name__, 'wrapper') + self.assertEqual(wrapper.__doc__, None) + self.assertEqual(wrapper.__annotations__, {}) + self.assertFalse(hasattr(wrapper, 'attr')) + + def test_selective_update(self): + def f(): + pass + f.attr = 'This is a different test' + f.dict_attr = dict(a=1, b=2, c=3) + def wrapper(): + pass + wrapper.dict_attr = {} + assign = ('attr',) + update = ('dict_attr',) + functools.update_wrapper(wrapper, f, assign, update) + self.check_wrapper(wrapper, f, assign, update) + self.assertEqual(wrapper.__name__, 'wrapper') + self.assertEqual(wrapper.__doc__, None) + self.assertEqual(wrapper.attr, 'This is a different test') + self.assertEqual(wrapper.dict_attr, f.dict_attr) + + def test_missing_attributes(self): + def f(): + pass + def wrapper(): + pass + wrapper.dict_attr = {} + assign = ('attr',) + update = ('dict_attr',) + # Missing attributes on wrapped object are ignored + functools.update_wrapper(wrapper, f, assign, update) + self.assertNotIn('attr', wrapper.__dict__) + self.assertEqual(wrapper.dict_attr, {}) + # Wrapper must have expected attributes for updating + del wrapper.dict_attr + with self.assertRaises(AttributeError): + functools.update_wrapper(wrapper, f, assign, update) + wrapper.dict_attr = 1 + with self.assertRaises(AttributeError): + functools.update_wrapper(wrapper, f, assign, update) + + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_builtin_update(self): + # Test for bug #1576241 + def wrapper(): + pass + functools.update_wrapper(wrapper, max) + self.assertEqual(wrapper.__name__, 'max') + self.assertTrue(wrapper.__doc__.startswith('max(')) + self.assertEqual(wrapper.__annotations__, {}) + +class TestWraps(TestUpdateWrapper): + + def _default_update(self): + def f(): + """This is a test""" + pass + f.attr = 'This is also a test' + @functools.wraps(f) + def wrapper(): + pass + self.check_wrapper(wrapper, f) + return wrapper + + def test_default_update(self): + wrapper = self._default_update() + self.assertEqual(wrapper.__name__, 'f') + self.assertEqual(wrapper.attr, 'This is also a test') + + @unittest.skipIf(not sys.flags.optimize <= 1, + "Docstrings are omitted with -O2 and above") + def test_default_update_doc(self): + wrapper = self._default_update() + self.assertEqual(wrapper.__doc__, 'This is a test') + + def test_no_update(self): + def f(): + """This is a test""" + pass + f.attr = 'This is also a test' + @functools.wraps(f, (), ()) + def wrapper(): + pass + self.check_wrapper(wrapper, f, (), ()) + self.assertEqual(wrapper.__name__, 'wrapper') + self.assertEqual(wrapper.__doc__, None) + self.assertFalse(hasattr(wrapper, 'attr')) + + def test_selective_update(self): + def f(): + pass + f.attr = 'This is a different test' + f.dict_attr = dict(a=1, b=2, c=3) + def add_dict_attr(f): + f.dict_attr = {} + return f + assign = ('attr',) + update = ('dict_attr',) + @functools.wraps(f, assign, update) + @add_dict_attr + def wrapper(): + pass + self.check_wrapper(wrapper, f, assign, update) + self.assertEqual(wrapper.__name__, 'wrapper') + self.assertEqual(wrapper.__doc__, None) + self.assertEqual(wrapper.attr, 'This is a different test') + self.assertEqual(wrapper.dict_attr, f.dict_attr) + +class TestReduce(unittest.TestCase): + func = functools.reduce + + def test_reduce(self): + class Squares: + def __init__(self, max): + self.max = max + self.sofar = [] + + def __len__(self): + return len(self.sofar) + + def __getitem__(self, i): + if not 0 <= i < self.max: raise IndexError + n = len(self.sofar) + while n <= i: + self.sofar.append(n*n) + n += 1 + return self.sofar[i] + def add(x, y): + return x + y + self.assertEqual(self.func(add, ['a', 'b', 'c'], ''), 'abc') + self.assertEqual( + self.func(add, [['a', 'c'], [], ['d', 'w']], []), + ['a','c','d','w'] + ) + self.assertEqual(self.func(lambda x, y: x*y, range(2,8), 1), 5040) + self.assertEqual( + self.func(lambda x, y: x*y, range(2,21), 1), + 2432902008176640000 + ) + self.assertEqual(self.func(add, Squares(10)), 285) + self.assertEqual(self.func(add, Squares(10), 0), 285) + self.assertEqual(self.func(add, Squares(0), 0), 0) + self.assertRaises(TypeError, self.func) + self.assertRaises(TypeError, self.func, 42, 42) + self.assertRaises(TypeError, self.func, 42, 42, 42) + self.assertEqual(self.func(42, "1"), "1") # func is never called with one item + self.assertEqual(self.func(42, "", "1"), "1") # func is never called with one item + self.assertRaises(TypeError, self.func, 42, (42, 42)) + self.assertRaises(TypeError, self.func, add, []) # arg 2 must not be empty sequence with no initial value + self.assertRaises(TypeError, self.func, add, "") + self.assertRaises(TypeError, self.func, add, ()) + self.assertRaises(TypeError, self.func, add, object()) + + class TestFailingIter: + def __iter__(self): + raise RuntimeError + self.assertRaises(RuntimeError, self.func, add, TestFailingIter()) + + self.assertEqual(self.func(add, [], None), None) + self.assertEqual(self.func(add, [], 42), 42) + + class BadSeq: + def __getitem__(self, index): + raise ValueError + self.assertRaises(ValueError, self.func, 42, BadSeq()) + + # Test reduce()'s use of iterators. + def test_iterator_usage(self): + class SequenceClass: + def __init__(self, n): + self.n = n + def __getitem__(self, i): + if 0 <= i < self.n: + return i + else: + raise IndexError + + from operator import add + self.assertEqual(self.func(add, SequenceClass(5)), 10) + self.assertEqual(self.func(add, SequenceClass(5), 42), 52) + self.assertRaises(TypeError, self.func, add, SequenceClass(0)) + self.assertEqual(self.func(add, SequenceClass(0), 42), 42) + self.assertEqual(self.func(add, SequenceClass(1)), 0) + self.assertEqual(self.func(add, SequenceClass(1), 42), 42) + + d = {"one": 1, "two": 2, "three": 3} + self.assertEqual(self.func(add, d), "".join(d.keys())) + +class TestCmpToKey(unittest.TestCase): + def test_cmp_to_key(self): + def mycmp(x, y): + return y - x + self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)), + [4, 3, 2, 1, 0]) + + def test_hash(self): + def mycmp(x, y): + return y - x + key = functools.cmp_to_key(mycmp) + k = key(10) + self.assertRaises(TypeError, hash, k) + self.assertFalse(isinstance(k, collections.Hashable)) + +class TestTotalOrdering(unittest.TestCase): + + def test_total_ordering_lt(self): + @functools.total_ordering + class A: + def __init__(self, value): + self.value = value + def __lt__(self, other): + return self.value < other.value + def __eq__(self, other): + return self.value == other.value + self.assertTrue(A(1) < A(2)) + self.assertTrue(A(2) > A(1)) + self.assertTrue(A(1) <= A(2)) + self.assertTrue(A(2) >= A(1)) + self.assertTrue(A(2) <= A(2)) + self.assertTrue(A(2) >= A(2)) + + def test_total_ordering_le(self): + @functools.total_ordering + class A: + def __init__(self, value): + self.value = value + def __le__(self, other): + return self.value <= other.value + def __eq__(self, other): + return self.value == other.value + self.assertTrue(A(1) < A(2)) + self.assertTrue(A(2) > A(1)) + self.assertTrue(A(1) <= A(2)) + self.assertTrue(A(2) >= A(1)) + self.assertTrue(A(2) <= A(2)) + self.assertTrue(A(2) >= A(2)) + + def test_total_ordering_gt(self): + @functools.total_ordering + class A: + def __init__(self, value): + self.value = value + def __gt__(self, other): + return self.value > other.value + def __eq__(self, other): + return self.value == other.value + self.assertTrue(A(1) < A(2)) + self.assertTrue(A(2) > A(1)) + self.assertTrue(A(1) <= A(2)) + self.assertTrue(A(2) >= A(1)) + self.assertTrue(A(2) <= A(2)) + self.assertTrue(A(2) >= A(2)) + + def test_total_ordering_ge(self): + @functools.total_ordering + class A: + def __init__(self, value): + self.value = value + def __ge__(self, other): + return self.value >= other.value + def __eq__(self, other): + return self.value == other.value + self.assertTrue(A(1) < A(2)) + self.assertTrue(A(2) > A(1)) + self.assertTrue(A(1) <= A(2)) + self.assertTrue(A(2) >= A(1)) + self.assertTrue(A(2) <= A(2)) + self.assertTrue(A(2) >= A(2)) + + def test_total_ordering_no_overwrite(self): + # new methods should not overwrite existing + @functools.total_ordering + class A(int): + pass + self.assertTrue(A(1) < A(2)) + self.assertTrue(A(2) > A(1)) + self.assertTrue(A(1) <= A(2)) + self.assertTrue(A(2) >= A(1)) + self.assertTrue(A(2) <= A(2)) + self.assertTrue(A(2) >= A(2)) + + def test_no_operations_defined(self): + with self.assertRaises(ValueError): + @functools.total_ordering + class A: + pass + + def test_bug_10042(self): + @functools.total_ordering + class TestTO: + def __init__(self, value): + self.value = value + def __eq__(self, other): + if isinstance(other, TestTO): + return self.value == other.value + return False + def __lt__(self, other): + if isinstance(other, TestTO): + return self.value < other.value + raise TypeError + with self.assertRaises(TypeError): + TestTO(8) <= () + +class TestLRU(unittest.TestCase): + + def test_lru(self): + def orig(x, y): + return 3*x+y + f = functools.lru_cache(maxsize=20)(orig) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(maxsize, 20) + self.assertEqual(currsize, 0) + self.assertEqual(hits, 0) + self.assertEqual(misses, 0) + + domain = range(5) + for i in range(1000): + x, y = choice(domain), choice(domain) + actual = f(x, y) + expected = orig(x, y) + self.assertEqual(actual, expected) + hits, misses, maxsize, currsize = f.cache_info() + self.assertTrue(hits > misses) + self.assertEqual(hits + misses, 1000) + self.assertEqual(currsize, 20) + + f.cache_clear() # test clearing + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 0) + self.assertEqual(misses, 0) + self.assertEqual(currsize, 0) + f(x, y) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 0) + self.assertEqual(misses, 1) + self.assertEqual(currsize, 1) + + # Test bypassing the cache + self.assertIs(f.__wrapped__, orig) + f.__wrapped__(x, y) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 0) + self.assertEqual(misses, 1) + self.assertEqual(currsize, 1) + + # test size zero (which means "never-cache") + @functools.lru_cache(0) + def f(): + nonlocal f_cnt + f_cnt += 1 + return 20 + self.assertEqual(f.cache_info().maxsize, 0) + f_cnt = 0 + for i in range(5): + self.assertEqual(f(), 20) + self.assertEqual(f_cnt, 5) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 0) + self.assertEqual(misses, 5) + self.assertEqual(currsize, 0) + + # test size one + @functools.lru_cache(1) + def f(): + nonlocal f_cnt + f_cnt += 1 + return 20 + self.assertEqual(f.cache_info().maxsize, 1) + f_cnt = 0 + for i in range(5): + self.assertEqual(f(), 20) + self.assertEqual(f_cnt, 1) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 4) + self.assertEqual(misses, 1) + self.assertEqual(currsize, 1) + + # test size two + @functools.lru_cache(2) + def f(x): + nonlocal f_cnt + f_cnt += 1 + return x*10 + self.assertEqual(f.cache_info().maxsize, 2) + f_cnt = 0 + for x in 7, 9, 7, 9, 7, 9, 8, 8, 8, 9, 9, 9, 8, 8, 8, 7: + # * * * * + self.assertEqual(f(x), x*10) + self.assertEqual(f_cnt, 4) + hits, misses, maxsize, currsize = f.cache_info() + self.assertEqual(hits, 12) + self.assertEqual(misses, 4) + self.assertEqual(currsize, 2) + + def test_lru_with_maxsize_none(self): + @functools.lru_cache(maxsize=None) + def fib(n): + if n < 2: + return n + return fib(n-1) + fib(n-2) + self.assertEqual([fib(n) for n in range(16)], + [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]) + self.assertEqual(fib.cache_info(), + functools._CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)) + fib.cache_clear() + self.assertEqual(fib.cache_info(), + functools._CacheInfo(hits=0, misses=0, maxsize=None, currsize=0)) + + def test_lru_with_exceptions(self): + # Verify that user_function exceptions get passed through without + # creating a hard-to-read chained exception. + # http://bugs.python.org/issue13177 + for maxsize in (None, 100): + @functools.lru_cache(maxsize) + def func(i): + return 'abc'[i] + self.assertEqual(func(0), 'a') + with self.assertRaises(IndexError) as cm: + func(15) + self.assertIsNone(cm.exception.__context__) + # Verify that the previous exception did not result in a cached entry + with self.assertRaises(IndexError): + func(15) + +def test_main(verbose=None): + test_classes = ( + TestPartial, + TestPartialSubclass, + TestPythonPartial, + TestUpdateWrapper, + TestTotalOrdering, + TestCmpToKey, + TestWraps, + TestReduce, + TestLRU, + ) + support.run_unittest(*test_classes) + + # verify reference counting + if verbose and hasattr(sys, "gettotalrefcount"): + import gc + counts = [None] * 5 + for i in range(len(counts)): + support.run_unittest(*test_classes) + gc.collect() + counts[i] = sys.gettotalrefcount() + print(counts) + +if __name__ == '__main__': + test_main(verbose=True)