diff options
-rw-r--r-- | Doc/c-api/concrete.rst | 14 | ||||
-rw-r--r-- | Doc/c-api/utilities.rst | 8 | ||||
-rw-r--r-- | Doc/library/collections.rst | 4 | ||||
-rw-r--r-- | Doc/library/curses.rst | 13 | ||||
-rw-r--r-- | Doc/library/logging.rst | 112 | ||||
-rw-r--r-- | Doc/library/rational.rst | 14 | ||||
-rw-r--r-- | Lib/email/mime/multipart.py | 6 | ||||
-rw-r--r-- | Lib/email/test/test_email.py | 3 | ||||
-rwxr-xr-x | Lib/rational.py | 76 | ||||
-rw-r--r-- | Lib/test/crashers/weakref_in_del.py | 17 | ||||
-rw-r--r-- | Lib/test/test_rational.py | 69 | ||||
-rw-r--r-- | Lib/test/test_threading_local.py | 44 | ||||
-rw-r--r-- | Modules/_sre.c | 3 | ||||
-rw-r--r-- | Objects/complexobject.c | 68 |
14 files changed, 312 insertions, 139 deletions
diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index cc5c9d5..343223c 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -1259,7 +1259,7 @@ These are the UTF-8 codec APIs: .. cfunction:: PyObject* PyUnicode_AsUTF8String(PyObject *unicode) - Encode a Unicode objects using UTF-8 and return the result as Python string + Encode a Unicode object using UTF-8 and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1411,7 +1411,7 @@ These are the "Unicode Escape" codec APIs: .. cfunction:: PyObject* PyUnicode_AsUnicodeEscapeString(PyObject *unicode) - Encode a Unicode objects using Unicode-Escape and return the result as Python + Encode a Unicode object using Unicode-Escape and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1435,7 +1435,7 @@ These are the "Raw Unicode Escape" codec APIs: .. cfunction:: PyObject* PyUnicode_AsRawUnicodeEscapeString(PyObject *unicode) - Encode a Unicode objects using Raw-Unicode-Escape and return the result as + Encode a Unicode object using Raw-Unicode-Escape and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1459,7 +1459,7 @@ ordinals and only these are accepted by the codecs during encoding. .. cfunction:: PyObject* PyUnicode_AsLatin1String(PyObject *unicode) - Encode a Unicode objects using Latin-1 and return the result as Python string + Encode a Unicode object using Latin-1 and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1483,7 +1483,7 @@ codes generate errors. .. cfunction:: PyObject* PyUnicode_AsASCIIString(PyObject *unicode) - Encode a Unicode objects using ASCII and return the result as Python string + Encode a Unicode object using ASCII and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1532,7 +1532,7 @@ characters to different code points. .. cfunction:: PyObject* PyUnicode_AsCharmapString(PyObject *unicode, PyObject *mapping) - Encode a Unicode objects using the given *mapping* object and return the result + Encode a Unicode object using the given *mapping* object and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. @@ -1582,7 +1582,7 @@ the user settings on the machine running the codec. .. cfunction:: PyObject* PyUnicode_AsMBCSString(PyObject *unicode) - Encode a Unicode objects using MBCS and return the result as Python string + Encode a Unicode object using MBCS and return the result as Python string object. Error handling is "strict". Return *NULL* if an exception was raised by the codec. diff --git a/Doc/c-api/utilities.rst b/Doc/c-api/utilities.rst index c30a62a..6138c63 100644 --- a/Doc/c-api/utilities.rst +++ b/Doc/c-api/utilities.rst @@ -201,12 +201,12 @@ Importing Modules .. cfunction:: PyObject* PyImport_ImportModuleNoBlock(const char *name) This version of :cfunc:`PyImport_ImportModule` does not block. It's intended - to be used in C function which import other modules to execute a function. + to be used in C functions that import other modules to execute a function. The import may block if another thread holds the import lock. The function - :cfunc:`PyImport_ImportModuleNoBlock` doesn't block. It first tries to fetch + :cfunc:`PyImport_ImportModuleNoBlock` never blocks. It first tries to fetch the module from sys.modules and falls back to :cfunc:`PyImport_ImportModule` - unless the the lock is hold. In the latter case the function raises an - ImportError. + unless the lock is held, in which case the function will raise an + :exc:`ImportError`. .. cfunction:: PyObject* PyImport_ImportModuleEx(char *name, PyObject *globals, PyObject *locals, PyObject *fromlist) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index cbc9c6b..d138d23 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -579,8 +579,8 @@ Default values can be implemented by using :meth:`_replace` to customize a prototype instance:: >>> Account = namedtuple('Account', 'owner balance transaction_count') - >>> model_account = Account('<owner name>', 0.0, 0) - >>> johns_account = model_account._replace(owner='John') + >>> default_account = Account('<owner name>', 0.0, 0) + >>> johns_account = default_account._replace(owner='John') .. rubric:: Footnotes diff --git a/Doc/library/curses.rst b/Doc/library/curses.rst index 07ccc55..7f82bca 100644 --- a/Doc/library/curses.rst +++ b/Doc/library/curses.rst @@ -16,6 +16,19 @@ for DOS, OS/2, and possibly other systems as well. This extension module is designed to match the API of ncurses, an open-source curses library hosted on Linux and the BSD variants of Unix. +.. note:: + + Since version 5.4, the ncurses library decides how to interpret non-ASCII data + using the ``nl_langinfo`` function. That means that you have to call + :func:`locale.setlocale` in the application and encode Unicode strings + using one of the system's available encodings. This example uses the + system's default encoding:: + + import locale + locale.setlocale(locale.LC_ALL, '') + code = locale.getpreferredencoding() + + Then use *code* as the encoding for :meth:`str.encode` calls. .. seealso:: diff --git a/Doc/library/logging.rst b/Doc/library/logging.rst index 258bb0a..923d9f2 100644 --- a/Doc/library/logging.rst +++ b/Doc/library/logging.rst @@ -1179,65 +1179,65 @@ to do what you need. Here's an example script which uses this class, which also illustrates what dict-like behaviour is needed from an arbitrary "dict-like" object for use in the constructor:: -import logging - -class ConnInfo: - """ - An example class which shows how an arbitrary class can be used as - the 'extra' context information repository passed to a LoggerAdapter. - """ - - def __getitem__(self, name): - """ - To allow this instance to look like a dict. - """ - from random import choice - if name == "ip": - result = choice(["127.0.0.1", "192.168.0.1"]) - elif name == "user": - result = choice(["jim", "fred", "sheila"]) - else: - result = self.__dict__.get(name, "?") - return result - - def __iter__(self): - """ - To allow iteration over keys, which will be merged into - the LogRecord dict before formatting and output. - """ - keys = ["ip", "user"] - keys.extend(self.__dict__.keys()) - return keys.__iter__() - -if __name__ == "__main__": - from random import choice - levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) - a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"), - { "ip" : "123.231.231.123", "user" : "sheila" }) - logging.basicConfig(level=logging.DEBUG, - format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s") - a1.debug("A debug message") - a1.info("An info message with %s", "some parameters") - a2 = logging.LoggerAdapter(logging.getLogger("d.e.f"), ConnInfo()) - for x in range(10): - lvl = choice(levels) - lvlname = logging.getLevelName(lvl) - a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters") + import logging + + class ConnInfo: + """ + An example class which shows how an arbitrary class can be used as + the 'extra' context information repository passed to a LoggerAdapter. + """ + + def __getitem__(self, name): + """ + To allow this instance to look like a dict. + """ + from random import choice + if name == "ip": + result = choice(["127.0.0.1", "192.168.0.1"]) + elif name == "user": + result = choice(["jim", "fred", "sheila"]) + else: + result = self.__dict__.get(name, "?") + return result + + def __iter__(self): + """ + To allow iteration over keys, which will be merged into + the LogRecord dict before formatting and output. + """ + keys = ["ip", "user"] + keys.extend(self.__dict__.keys()) + return keys.__iter__() + + if __name__ == "__main__": + from random import choice + levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) + a1 = logging.LoggerAdapter(logging.getLogger("a.b.c"), + { "ip" : "123.231.231.123", "user" : "sheila" }) + logging.basicConfig(level=logging.DEBUG, + format="%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s") + a1.debug("A debug message") + a1.info("An info message with %s", "some parameters") + a2 = logging.LoggerAdapter(logging.getLogger("d.e.f"), ConnInfo()) + for x in range(10): + lvl = choice(levels) + lvlname = logging.getLevelName(lvl) + a2.log(lvl, "A message at %s level with %d %s", lvlname, 2, "parameters") When this script is run, the output should look something like this:: -2008-01-18 14:49:54,023 a.b.c DEBUG IP: 123.231.231.123 User: sheila A debug message -2008-01-18 14:49:54,023 a.b.c INFO IP: 123.231.231.123 User: sheila An info message with some parameters -2008-01-18 14:49:54,023 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters -2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: jim A message at INFO level with 2 parameters -2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters -2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: fred A message at ERROR level with 2 parameters -2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters -2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters -2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: jim A message at WARNING level with 2 parameters -2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: fred A message at INFO level with 2 parameters -2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters -2008-01-18 14:49:54,033 d.e.f WARNING IP: 127.0.0.1 User: jim A message at WARNING level with 2 parameters + 2008-01-18 14:49:54,023 a.b.c DEBUG IP: 123.231.231.123 User: sheila A debug message + 2008-01-18 14:49:54,023 a.b.c INFO IP: 123.231.231.123 User: sheila An info message with some parameters + 2008-01-18 14:49:54,023 d.e.f CRITICAL IP: 192.168.0.1 User: jim A message at CRITICAL level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: jim A message at INFO level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: fred A message at ERROR level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f ERROR IP: 127.0.0.1 User: sheila A message at ERROR level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: jim A message at WARNING level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f INFO IP: 192.168.0.1 User: fred A message at INFO level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters + 2008-01-18 14:49:54,033 d.e.f WARNING IP: 127.0.0.1 User: jim A message at WARNING level with 2 parameters .. versionadded:: 2.6 diff --git a/Doc/library/rational.rst b/Doc/library/rational.rst index dd18305..8ed702f 100644 --- a/Doc/library/rational.rst +++ b/Doc/library/rational.rst @@ -15,6 +15,7 @@ Rational number class. .. class:: Rational(numerator=0, denominator=1) Rational(other_rational) + Rational(string) The first version requires that *numerator* and *denominator* are instances of :class:`numbers.Integral` and returns a new @@ -22,10 +23,12 @@ Rational number class. *denominator* is :const:`0`, raises a :exc:`ZeroDivisionError`. The second version requires that *other_rational* is an instance of :class:`numbers.Rational` and returns an instance of - :class:`Rational` with the same value. + :class:`Rational` with the same value. The third version expects a + string of the form ``[-+]?[0-9]+(/[0-9]+)?``, optionally surrounded + by spaces. Implements all of the methods and operations from - :class:`numbers.Rational` and is hashable. + :class:`numbers.Rational` and is immutable and hashable. .. method:: Rational.from_float(flt) @@ -36,6 +39,13 @@ Rational number class. 10)`` +.. method:: Rational.from_decimal(dec) + + This classmethod constructs a :class:`Rational` representing the + exact value of *dec*, which must be a + :class:`decimal.Decimal`. + + .. method:: Rational.__floor__() Returns the greatest :class:`int` ``<= self``. Will be accessible diff --git a/Lib/email/mime/multipart.py b/Lib/email/mime/multipart.py index 5c8c9db..9661865 100644 --- a/Lib/email/mime/multipart.py +++ b/Lib/email/mime/multipart.py @@ -34,6 +34,12 @@ class MIMEMultipart(MIMEBase): keyword arguments (or passed into the _params argument). """ MIMEBase.__init__(self, 'multipart', _subtype, **_params) + + # Initialise _payload to an empty list as the Message superclass's + # implementation of is_multipart assumes that _payload is a list for + # multipart messages. + self._payload = [] + if _subparts: for p in _subparts: self.attach(p) diff --git a/Lib/email/test/test_email.py b/Lib/email/test/test_email.py index c544004..1ca41e9 100644 --- a/Lib/email/test/test_email.py +++ b/Lib/email/test/test_email.py @@ -1892,6 +1892,9 @@ message 2 eq(msg.get_payload(0), text1) eq(msg.get_payload(1), text2) + def test_default_multipart_constructor(self): + msg = MIMEMultipart() + self.assertTrue(msg.is_multipart()) # A general test of parser->model->generator idempotency. IOW, read a message diff --git a/Lib/rational.py b/Lib/rational.py index c913ec7..71ffff7 100755 --- a/Lib/rational.py +++ b/Lib/rational.py @@ -6,6 +6,7 @@ import math import numbers import operator +import re __all__ = ["Rational"] @@ -75,6 +76,10 @@ def _binary_float_to_ratio(x): return (top, 2 ** -e) +_RATIONAL_FORMAT = re.compile( + r'^\s*(?P<sign>[-+]?)(?P<num>\d+)(?:/(?P<denom>\d+))?\s*$') + + class Rational(RationalAbc): """This class implements rational numbers. @@ -83,18 +88,41 @@ class Rational(RationalAbc): and the denominator defaults to 1 so that Rational(3) == 3 and Rational() == 0. + Rationals can also be constructed from strings of the form + '[-+]?[0-9]+(/[0-9]+)?', optionally surrounded by spaces. + """ __slots__ = ('_numerator', '_denominator') - def __init__(self, numerator=0, denominator=1): - if (not isinstance(numerator, numbers.Integral) and - isinstance(numerator, RationalAbc) and - denominator == 1): - # Handle copies from other rationals. - other_rational = numerator - numerator = other_rational.numerator - denominator = other_rational.denominator + # We're immutable, so use __new__ not __init__ + def __new__(cls, numerator=0, denominator=1): + """Constructs a Rational. + + Takes a string, another Rational, or a numerator/denominator pair. + + """ + self = super(Rational, cls).__new__(cls) + + if denominator == 1: + if isinstance(numerator, str): + # Handle construction from strings. + input = numerator + m = _RATIONAL_FORMAT.match(input) + if m is None: + raise ValueError('Invalid literal for Rational: ' + input) + numerator = int(m.group('num')) + # Default denominator to 1. That's the only optional group. + denominator = int(m.group('denom') or 1) + if m.group('sign') == '-': + numerator = -numerator + + elif (not isinstance(numerator, numbers.Integral) and + isinstance(numerator, RationalAbc)): + # Handle copies from other rationals. + other_rational = numerator + numerator = other_rational.numerator + denominator = other_rational.denominator if (not isinstance(numerator, numbers.Integral) or not isinstance(denominator, numbers.Integral)): @@ -107,10 +135,15 @@ class Rational(RationalAbc): g = _gcd(numerator, denominator) self._numerator = int(numerator // g) self._denominator = int(denominator // g) + return self @classmethod def from_float(cls, f): - """Converts a float to a rational number, exactly.""" + """Converts a finite float to a rational number, exactly. + + Beware that Rational.from_float(0.3) != Rational(3, 10). + + """ if not isinstance(f, float): raise TypeError("%s.from_float() only takes floats, not %r (%s)" % (cls.__name__, f, type(f).__name__)) @@ -118,6 +151,26 @@ class Rational(RationalAbc): raise TypeError("Cannot convert %r to %s." % (f, cls.__name__)) return cls(*_binary_float_to_ratio(f)) + @classmethod + def from_decimal(cls, dec): + """Converts a finite Decimal instance to a rational number, exactly.""" + from decimal import Decimal + if not isinstance(dec, Decimal): + raise TypeError( + "%s.from_decimal() only takes Decimals, not %r (%s)" % + (cls.__name__, dec, type(dec).__name__)) + if not dec.is_finite(): + # Catches infinities and nans. + raise TypeError("Cannot convert %s to %s." % (dec, cls.__name__)) + sign, digits, exp = dec.as_tuple() + digits = int(''.join(map(str, digits))) + if sign: + digits = -digits + if exp >= 0: + return cls(digits * 10 ** exp) + else: + return cls(digits, 10 ** -exp) + @property def numerator(a): return a._numerator @@ -128,15 +181,14 @@ class Rational(RationalAbc): def __repr__(self): """repr(self)""" - return ('rational.Rational(%r,%r)' % - (self.numerator, self.denominator)) + return ('Rational(%r,%r)' % (self.numerator, self.denominator)) def __str__(self): """str(self)""" if self.denominator == 1: return str(self.numerator) else: - return '(%s/%s)' % (self.numerator, self.denominator) + return '%s/%s' % (self.numerator, self.denominator) def _operator_fallbacks(monomorphic_operator, fallback_operator): """Generates forward and reverse operators given a purely-rational diff --git a/Lib/test/crashers/weakref_in_del.py b/Lib/test/crashers/weakref_in_del.py deleted file mode 100644 index 2e9b186..0000000 --- a/Lib/test/crashers/weakref_in_del.py +++ /dev/null @@ -1,17 +0,0 @@ -import weakref - -# http://python.org/sf/1377858 -# Fixed for new-style classes in 2.5c1. - -ref = None - -def test_weakref_in_del(): - class Target(): - def __del__(self): - global ref - ref = weakref.ref(self) - - w = Target() - -if __name__ == '__main__': - test_weakref_in_del() diff --git a/Lib/test/test_rational.py b/Lib/test/test_rational.py index 952a97f..e57adce 100644 --- a/Lib/test/test_rational.py +++ b/Lib/test/test_rational.py @@ -45,6 +45,44 @@ class RationalTest(unittest.TestCase): self.assertRaises(TypeError, R, 1.5) self.assertRaises(TypeError, R, 1.5 + 3j) + self.assertRaises(TypeError, R, R(1, 2), 3) + self.assertRaises(TypeError, R, "3/2", 3) + + def testFromString(self): + self.assertEquals((5, 1), _components(R("5"))) + self.assertEquals((3, 2), _components(R("3/2"))) + self.assertEquals((3, 2), _components(R(" \n +3/2"))) + self.assertEquals((-3, 2), _components(R("-3/2 "))) + self.assertEquals((3, 2), _components(R(" 03/02 \n "))) + self.assertEquals((3, 2), _components(R(" 03/02 \n "))) + + self.assertRaisesMessage( + ZeroDivisionError, "Rational(3, 0)", + R, "3/0") + self.assertRaisesMessage( + ValueError, "Invalid literal for Rational: 3/", + R, "3/") + self.assertRaisesMessage( + ValueError, "Invalid literal for Rational: 3 /2", + R, "3 /2") + self.assertRaisesMessage( + # Denominators don't need a sign. + ValueError, "Invalid literal for Rational: 3/+2", + R, "3/+2") + self.assertRaisesMessage( + # Imitate float's parsing. + ValueError, "Invalid literal for Rational: + 3/2", + R, "+ 3/2") + self.assertRaisesMessage( + # Only parse fractions, not decimals. + ValueError, "Invalid literal for Rational: 3.2", + R, "3.2") + + def testImmutable(self): + r = R(7, 3) + r.__init__(2, 15) + self.assertEquals((7, 3), _components(r)) + def testFromFloat(self): self.assertRaisesMessage( TypeError, "Rational.from_float() only takes floats, not 3 (int)", @@ -72,6 +110,31 @@ class RationalTest(unittest.TestCase): TypeError, "Cannot convert nan to Rational.", R.from_float, nan) + def testFromDecimal(self): + self.assertRaisesMessage( + TypeError, + "Rational.from_decimal() only takes Decimals, not 3 (int)", + R.from_decimal, 3) + self.assertEquals(R(0), R.from_decimal(Decimal("-0"))) + self.assertEquals(R(5, 10), R.from_decimal(Decimal("0.5"))) + self.assertEquals(R(5, 1000), R.from_decimal(Decimal("5e-3"))) + self.assertEquals(R(5000), R.from_decimal(Decimal("5e3"))) + self.assertEquals(1 - R(1, 10**30), + R.from_decimal(Decimal("0." + "9" * 30))) + + self.assertRaisesMessage( + TypeError, "Cannot convert Infinity to Rational.", + R.from_decimal, Decimal("inf")) + self.assertRaisesMessage( + TypeError, "Cannot convert -Infinity to Rational.", + R.from_decimal, Decimal("-inf")) + self.assertRaisesMessage( + TypeError, "Cannot convert NaN to Rational.", + R.from_decimal, Decimal("nan")) + self.assertRaisesMessage( + TypeError, "Cannot convert sNaN to Rational.", + R.from_decimal, Decimal("snan")) + def testConversions(self): self.assertTypedEquals(-1, trunc(R(-11, 10))) self.assertTypedEquals(-2, math.floor(R(-11, 10))) @@ -175,7 +238,7 @@ class RationalTest(unittest.TestCase): self.assertTypedEquals(1.0 + 0j, (1.0 + 0j) ** R(1, 10)) def testMixingWithDecimal(self): - """Decimal refuses mixed comparisons.""" + # Decimal refuses mixed comparisons. self.assertRaisesMessage( TypeError, "unsupported operand type(s) for +: 'Rational' and 'Decimal'", @@ -238,8 +301,8 @@ class RationalTest(unittest.TestCase): self.assertFalse(R(5, 2) == 2) def testStringification(self): - self.assertEquals("rational.Rational(7,3)", repr(R(7, 3))) - self.assertEquals("(7/3)", str(R(7, 3))) + self.assertEquals("Rational(7,3)", repr(R(7, 3))) + self.assertEquals("7/3", str(R(7, 3))) self.assertEquals("7", str(R(7, 1))) def testHash(self): diff --git a/Lib/test/test_threading_local.py b/Lib/test/test_threading_local.py index 0aaedbc..b7dd538 100644 --- a/Lib/test/test_threading_local.py +++ b/Lib/test/test_threading_local.py @@ -1,9 +1,51 @@ import unittest from doctest import DocTestSuite from test import test_support +import threading +import weakref +import gc + +class Weak(object): + pass + +def target(local, weaklist): + weak = Weak() + local.weak = weak + weaklist.append(weakref.ref(weak)) + +class ThreadingLocalTest(unittest.TestCase): + + def test_local_refs(self): + self._local_refs(20) + self._local_refs(50) + self._local_refs(100) + + def _local_refs(self, n): + local = threading.local() + weaklist = [] + for i in range(n): + t = threading.Thread(target=target, args=(local, weaklist)) + t.start() + t.join() + del t + + gc.collect() + self.assertEqual(len(weaklist), n) + + # XXX threading.local keeps the local of the last stopped thread alive. + deadlist = [weak for weak in weaklist if weak() is None] + self.assertEqual(len(deadlist), n-1) + + # Assignment to the same thread local frees it sometimes (!) + local.someothervar = None + gc.collect() + deadlist = [weak for weak in weaklist if weak() is None] + self.assert_(len(deadlist) in (n-1, n), (n, len(deadlist))) def test_main(): - suite = DocTestSuite('_threading_local') + suite = unittest.TestSuite() + suite.addTest(DocTestSuite('_threading_local')) + suite.addTest(unittest.makeSuite(ThreadingLocalTest)) try: from thread import _local diff --git a/Modules/_sre.c b/Modules/_sre.c index b81a8e2..bfe4ae9 100644 --- a/Modules/_sre.c +++ b/Modules/_sre.c @@ -2677,7 +2677,7 @@ _compile(PyObject* self_, PyObject* args) return NULL; n = PyList_GET_SIZE(code); - + /* coverity[ampersand_in_size] */ self = PyObject_NEW_VAR(PatternObject, &Pattern_Type, n); if (!self) return NULL; @@ -3187,6 +3187,7 @@ pattern_new_match(PatternObject* pattern, SRE_STATE* state, int status) if (status > 0) { /* create match object (with room for extra group marks) */ + /* coverity[ampersand_in_size] */ match = PyObject_NEW_VAR(MatchObject, &Match_Type, 2*(pattern->groups+1)); if (!match) diff --git a/Objects/complexobject.c b/Objects/complexobject.c index de4641c..a7bf342 100644 --- a/Objects/complexobject.c +++ b/Objects/complexobject.c @@ -375,24 +375,24 @@ complex_hash(PyComplexObject *v) static int to_complex(PyObject **pobj, Py_complex *pc) { - PyObject *obj = *pobj; - - pc->real = pc->imag = 0.0; - if (PyLong_Check(obj)) { - pc->real = PyLong_AsDouble(obj); - if (pc->real == -1.0 && PyErr_Occurred()) { - *pobj = NULL; - return -1; - } - return 0; - } - if (PyFloat_Check(obj)) { - pc->real = PyFloat_AsDouble(obj); - return 0; - } - Py_INCREF(Py_NotImplemented); - *pobj = Py_NotImplemented; - return -1; + PyObject *obj = *pobj; + + pc->real = pc->imag = 0.0; + if (PyLong_Check(obj)) { + pc->real = PyLong_AsDouble(obj); + if (pc->real == -1.0 && PyErr_Occurred()) { + *pobj = NULL; + return -1; + } + return 0; + } + if (PyFloat_Check(obj)) { + pc->real = PyFloat_AsDouble(obj); + return 0; + } + Py_INCREF(Py_NotImplemented); + *pobj = Py_NotImplemented; + return -1; } @@ -401,8 +401,8 @@ complex_add(PyObject *v, PyObject *w) { Py_complex result; Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); + TO_COMPLEX(v, a); + TO_COMPLEX(w, b); PyFPE_START_PROTECT("complex_add", return 0) result = c_sum(a, b); PyFPE_END_PROTECT(result) @@ -414,8 +414,8 @@ complex_sub(PyObject *v, PyObject *w) { Py_complex result; Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); + TO_COMPLEX(v, a); + TO_COMPLEX(w, b); PyFPE_START_PROTECT("complex_sub", return 0) result = c_diff(a, b); PyFPE_END_PROTECT(result) @@ -427,8 +427,8 @@ complex_mul(PyObject *v, PyObject *w) { Py_complex result; Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); + TO_COMPLEX(v, a); + TO_COMPLEX(w, b); PyFPE_START_PROTECT("complex_mul", return 0) result = c_prod(a, b); PyFPE_END_PROTECT(result) @@ -440,8 +440,8 @@ complex_div(PyObject *v, PyObject *w) { Py_complex quot; Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); + TO_COMPLEX(v, a); + TO_COMPLEX(w, b); PyFPE_START_PROTECT("complex_div", return 0) errno = 0; quot = c_quot(a, b); @@ -477,8 +477,8 @@ complex_pow(PyObject *v, PyObject *w, PyObject *z) Py_complex exponent; long int_exponent; Py_complex a, b; - TO_COMPLEX(v, a); - TO_COMPLEX(w, b); + TO_COMPLEX(v, a); + TO_COMPLEX(w, b); if (z != Py_None) { PyErr_SetString(PyExc_ValueError, "complex modulo"); @@ -557,8 +557,8 @@ complex_richcompare(PyObject *v, PyObject *w, int op) { PyObject *res; Py_complex i, j; - TO_COMPLEX(v, i); - TO_COMPLEX(w, j); + TO_COMPLEX(v, i); + TO_COMPLEX(w, j); if (op != Py_EQ && op != Py_NE) { /* XXX Should eventually return NotImplemented */ @@ -673,11 +673,11 @@ complex_subtype_from_string(PyTypeObject *type, PyObject *v) start = s; while (*s && isspace(Py_CHARMASK(*s))) s++; - if (s[0] == '\0') { + if (s[0] == '\0') { PyErr_SetString(PyExc_ValueError, "complex() arg is an empty string"); return NULL; - } + } if (s[0] == '(') { /* Skip over possible bracket from repr(). */ got_bracket = 1; @@ -837,7 +837,7 @@ complex_new(PyTypeObject *type, PyObject *args, PyObject *kwds) "complex() can't take second arg" " if first is a string"); return NULL; - } + } return complex_subtype_from_string(type, r); } if (i != NULL && PyUnicode_Check(i)) { @@ -915,7 +915,7 @@ complex_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; } cr.real = PyFloat_AsDouble(tmp); - cr.imag = 0.0; /* Shut up compiler warning */ + cr.imag = 0.0; /* Shut up compiler warning */ Py_DECREF(tmp); } if (i == NULL) { |