Kaydet (Commit) 2b869943 authored tarafından Georg Brandl's avatar Georg Brandl

Patch #1675423: PyComplex_AsCComplex() now tries to convert an object

to complex using its __complex__() method before falling back to the
__float__() method. Therefore, the functions in the cmath module now
can operate on objects that define a __complex__() method.
 (backport)
üst 6f187743
...@@ -443,7 +443,9 @@ booleans. The following macros are available, however. ...@@ -443,7 +443,9 @@ booleans. The following macros are available, however.
\begin{cfuncdesc}{double}{PyFloat_AsDouble}{PyObject *pyfloat} \begin{cfuncdesc}{double}{PyFloat_AsDouble}{PyObject *pyfloat}
Return a C \ctype{double} representation of the contents of Return a C \ctype{double} representation of the contents of
\var{pyfloat}. \var{pyfloat}. If \var{pyfloat} is not a Python floating point
object but has a \method{__float__} method, this method will first
be called to convert \var{pyfloat} into a float.
\end{cfuncdesc} \end{cfuncdesc}
\begin{cfuncdesc}{double}{PyFloat_AS_DOUBLE}{PyObject *pyfloat} \begin{cfuncdesc}{double}{PyFloat_AS_DOUBLE}{PyObject *pyfloat}
...@@ -558,8 +560,11 @@ typedef struct { ...@@ -558,8 +560,11 @@ typedef struct {
\end{cfuncdesc} \end{cfuncdesc}
\begin{cfuncdesc}{Py_complex}{PyComplex_AsCComplex}{PyObject *op} \begin{cfuncdesc}{Py_complex}{PyComplex_AsCComplex}{PyObject *op}
Return the \ctype{Py_complex} value of the complex number Return the \ctype{Py_complex} value of the complex number \var{op}.
\var{op}. \versionchanged[If \var{op} is not a Python complex number object
but has a \method{__complex__} method, this method
will first be called to convert \var{op} to a Python
complex number object]{2.6}
\end{cfuncdesc} \end{cfuncdesc}
......
...@@ -5,7 +5,14 @@ ...@@ -5,7 +5,14 @@
\modulesynopsis{Mathematical functions for complex numbers.} \modulesynopsis{Mathematical functions for complex numbers.}
This module is always available. It provides access to mathematical This module is always available. It provides access to mathematical
functions for complex numbers. The functions are: functions for complex numbers. The functions in this module accept
integers, floating-point numbers or complex numbers as arguments.
They will also accept any Python object that has either a
\method{__complex__} or a \method{__float__} method: these methods are
used to convert the object to a complex or floating-point number, respectively, and
the function is then applied to the result of the conversion.
The functions are:
\begin{funcdesc}{acos}{x} \begin{funcdesc}{acos}{x}
Return the arc cosine of \var{x}. Return the arc cosine of \var{x}.
......
#! /usr/bin/env python from test.test_support import run_unittest
""" Simple test script for cmathmodule.c import unittest
Roger E. Masse
"""
import cmath, math import cmath, math
from test.test_support import verbose, verify, TestFailed
class CMathTests(unittest.TestCase):
verify(abs(cmath.log(10) - math.log(10)) < 1e-9) # list of all functions in cmath
verify(abs(cmath.log(10,2) - math.log(10,2)) < 1e-9) test_functions = [getattr(cmath, fname) for fname in [
try: 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atanh',
cmath.log('a') 'cos', 'cosh', 'exp', 'log', 'log10', 'sin', 'sinh',
except TypeError: 'sqrt', 'tan', 'tanh']]
pass # test first and second arguments independently for 2-argument log
else: test_functions.append(lambda x : cmath.log(x, 1729. + 0j))
raise TestFailed test_functions.append(lambda x : cmath.log(14.-27j, x))
try: def cAssertAlmostEqual(self, a, b, rel_eps = 1e-10, abs_eps = 1e-100):
cmath.log(10, 'a') """Check that two complex numbers are almost equal."""
except TypeError: # the two complex numbers are considered almost equal if
pass # either the relative error is <= rel_eps or the absolute error
else: # is tiny, <= abs_eps.
raise TestFailed if a == b == 0:
return
absolute_error = abs(a-b)
testdict = {'acos' : 1.0, relative_error = absolute_error/max(abs(a), abs(b))
'acosh' : 1.0, if relative_error > rel_eps and absolute_error > abs_eps:
'asin' : 1.0, self.fail("%s and %s are not almost equal" % (a, b))
'asinh' : 1.0,
'atan' : 0.2, def test_constants(self):
'atanh' : 0.2, e_expected = 2.71828182845904523536
'cos' : 1.0, pi_expected = 3.14159265358979323846
'cosh' : 1.0, self.assertAlmostEqual(cmath.pi, pi_expected, 9,
'exp' : 1.0, "cmath.pi is %s; should be %s" % (cmath.pi, pi_expected))
'log' : 1.0, self.assertAlmostEqual(cmath.e, e_expected, 9,
'log10' : 1.0, "cmath.e is %s; should be %s" % (cmath.e, e_expected))
'sin' : 1.0,
'sinh' : 1.0, def test_user_object(self):
'sqrt' : 1.0, # Test automatic calling of __complex__ and __float__ by cmath
'tan' : 1.0, # functions
'tanh' : 1.0}
# some random values to use as test values; we avoid values
for func in testdict.keys(): # for which any of the functions in cmath is undefined
f = getattr(cmath, func) # (i.e. 0., 1., -1., 1j, -1j) or would cause overflow
r = f(testdict[func]) cx_arg = 4.419414439 + 1.497100113j
if verbose: flt_arg = -6.131677725
print 'Calling %s(%f) = %f' % (func, testdict[func], abs(r))
# a variety of non-complex numbers, used to check that
p = cmath.pi # non-complex return values from __complex__ give an error
e = cmath.e non_complexes = ["not complex", 1, 5L, 2., None,
if verbose: object(), NotImplemented]
print 'PI = ', abs(p)
print 'E = ', abs(e) # Now we introduce a variety of classes whose instances might
# end up being passed to the cmath functions
# usual case: new-style class implementing __complex__
class MyComplex(object):
def __init__(self, value):
self.value = value
def __complex__(self):
return self.value
# old-style class implementing __complex__
class MyComplexOS:
def __init__(self, value):
self.value = value
def __complex__(self):
return self.value
# classes for which __complex__ raises an exception
class SomeException(Exception):
pass
class MyComplexException(object):
def __complex__(self):
raise SomeException
class MyComplexExceptionOS:
def __complex__(self):
raise SomeException
# some classes not providing __float__ or __complex__
class NeitherComplexNorFloat(object):
pass
class NeitherComplexNorFloatOS:
pass
class MyInt(object):
def __int__(self): return 2
def __long__(self): return 2L
def __index__(self): return 2
class MyIntOS:
def __int__(self): return 2
def __long__(self): return 2L
def __index__(self): return 2
# other possible combinations of __float__ and __complex__
# that should work
class FloatAndComplex(object):
def __float__(self):
return flt_arg
def __complex__(self):
return cx_arg
class FloatAndComplexOS:
def __float__(self):
return flt_arg
def __complex__(self):
return cx_arg
class JustFloat(object):
def __float__(self):
return flt_arg
class JustFloatOS:
def __float__(self):
return flt_arg
for f in self.test_functions:
# usual usage
self.cAssertAlmostEqual(f(MyComplex(cx_arg)), f(cx_arg))
self.cAssertAlmostEqual(f(MyComplexOS(cx_arg)), f(cx_arg))
# other combinations of __float__ and __complex__
self.cAssertAlmostEqual(f(FloatAndComplex()), f(cx_arg))
self.cAssertAlmostEqual(f(FloatAndComplexOS()), f(cx_arg))
self.cAssertAlmostEqual(f(JustFloat()), f(flt_arg))
self.cAssertAlmostEqual(f(JustFloatOS()), f(flt_arg))
# TypeError should be raised for classes not providing
# either __complex__ or __float__, even if they provide
# __int__, __long__ or __index__. An old-style class
# currently raises AttributeError instead of a TypeError;
# this could be considered a bug.
self.assertRaises(TypeError, f, NeitherComplexNorFloat())
self.assertRaises(TypeError, f, MyInt())
self.assertRaises(Exception, f, NeitherComplexNorFloatOS())
self.assertRaises(Exception, f, MyIntOS())
# non-complex return value from __complex__ -> TypeError
for bad_complex in non_complexes:
self.assertRaises(TypeError, f, MyComplex(bad_complex))
self.assertRaises(TypeError, f, MyComplexOS(bad_complex))
# exceptions in __complex__ should be propagated correctly
self.assertRaises(SomeException, f, MyComplexException())
self.assertRaises(SomeException, f, MyComplexExceptionOS())
def test_input_type(self):
# ints and longs should be acceptable inputs to all cmath
# functions, by virtue of providing a __float__ method
for f in self.test_functions:
for arg in [2, 2L, 2.]:
self.cAssertAlmostEqual(f(arg), f(arg.__float__()))
# but strings should give a TypeError
for f in self.test_functions:
for arg in ["a", "long_string", "0", "1j", ""]:
self.assertRaises(TypeError, f, arg)
def test_cmath_matches_math(self):
# check that corresponding cmath and math functions are equal
# for floats in the appropriate range
# test_values in (0, 1)
test_values = [0.01, 0.1, 0.2, 0.5, 0.9, 0.99]
# test_values for functions defined on [-1., 1.]
unit_interval = test_values + [-x for x in test_values] + \
[0., 1., -1.]
# test_values for log, log10, sqrt
positive = test_values + [1.] + [1./x for x in test_values]
nonnegative = [0.] + positive
# test_values for functions defined on the whole real line
real_line = [0.] + positive + [-x for x in positive]
test_functions = {
'acos' : unit_interval,
'asin' : unit_interval,
'atan' : real_line,
'cos' : real_line,
'cosh' : real_line,
'exp' : real_line,
'log' : positive,
'log10' : positive,
'sin' : real_line,
'sinh' : real_line,
'sqrt' : nonnegative,
'tan' : real_line,
'tanh' : real_line}
for fn, values in test_functions.items():
float_fn = getattr(math, fn)
complex_fn = getattr(cmath, fn)
for v in values:
self.cAssertAlmostEqual(float_fn(v), complex_fn(v))
# test two-argument version of log with various bases
for base in [0.5, 2., 10.]:
for v in positive:
self.cAssertAlmostEqual(cmath.log(v, base), math.log(v, base))
def test_main():
run_unittest(CMathTests)
if __name__ == "__main__":
test_main()
...@@ -12,6 +12,11 @@ What's New in Python 2.6 alpha 1? ...@@ -12,6 +12,11 @@ What's New in Python 2.6 alpha 1?
Core and builtins Core and builtins
----------------- -----------------
- Patch #1675423: PyComplex_AsCComplex() now tries to convert an object
to complex using its __complex__() method before falling back to the
__float__() method. Therefore, the functions in the cmath module now
can operate on objects that define a __complex__() method.
- Patch #1623563: allow __class__ assignment for classes with __slots__. - Patch #1623563: allow __class__ assignment for classes with __slots__.
The old and the new class are still required to have the same slot names. The old and the new class are still required to have the same slot names.
......
...@@ -252,12 +252,59 @@ Py_complex ...@@ -252,12 +252,59 @@ Py_complex
PyComplex_AsCComplex(PyObject *op) PyComplex_AsCComplex(PyObject *op)
{ {
Py_complex cv; Py_complex cv;
PyObject *newop = NULL;
static PyObject *complex_str = NULL;
assert(op);
/* If op is already of type PyComplex_Type, return its value */
if (PyComplex_Check(op)) { if (PyComplex_Check(op)) {
return ((PyComplexObject *)op)->cval; return ((PyComplexObject *)op)->cval;
} }
/* If not, use op's __complex__ method, if it exists */
/* return -1 on failure */
cv.real = -1.;
cv.imag = 0.;
if (PyInstance_Check(op)) {
/* this can go away in python 3000 */
if (PyObject_HasAttrString(op, "__complex__")) {
newop = PyObject_CallMethod(op, "__complex__", NULL);
if (!newop)
return cv;
}
/* else try __float__ */
} else {
PyObject *complexfunc;
if (!complex_str) {
if (!(complex_str = PyString_FromString("__complex__")))
return cv;
}
complexfunc = _PyType_Lookup(op->ob_type, complex_str);
/* complexfunc is a borrowed reference */
if (complexfunc) {
newop = PyObject_CallFunctionObjArgs(complexfunc, op, NULL);
if (!newop)
return cv;
}
}
if (newop) {
if (!PyComplex_Check(newop)) {
PyErr_SetString(PyExc_TypeError,
"__complex__ should return a complex object");
Py_DECREF(newop);
return cv;
}
cv = ((PyComplexObject *)newop)->cval;
Py_DECREF(newop);
return cv;
}
/* If neither of the above works, interpret op as a float giving the
real part of the result, and fill in the imaginary part as 0. */
else { else {
/* PyFloat_AsDouble will return -1 on failure */
cv.real = PyFloat_AsDouble(op); cv.real = PyFloat_AsDouble(op);
cv.imag = 0.;
return cv; return cv;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment