Commit ec99487b authored by Stefan Behnel's avatar Stefan Behnel

Update the documentation on the arithmetic special methods and issue a...

Update the documentation on the arithmetic special methods and issue a "backwards compatibility" warning when the reversed method is not implemented.

See https://github.com/cython/cython/issues/2056
parent 85d9738e
......@@ -2078,15 +2078,17 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
extra_arg_decl = ', PyObject* extra_arg'
else:
error(pos, "Unexpected type slot signature: %s" % slot)
return
def has_slot_method(method_name):
def get_slot_method_cname(method_name):
entry = scope.lookup(method_name)
return bool(entry and entry.is_special and entry.func_cname)
return entry.func_cname if entry and entry.is_special else None
def call_slot_method(method_name, reverse):
entry = scope.lookup(method_name)
if entry and entry.is_special and entry.func_cname:
func_cname = get_slot_method_cname(method_name)
if func_cname:
return "%s(%s%s)" % (
entry.func_cname,
func_cname,
"right, left" if reverse else "left, right",
extra_arg)
else:
......@@ -2095,13 +2097,21 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
'Py_TYPE(right)->tp_base' if reverse else 'Py_TYPE(left)->tp_base',
extra_arg)
if get_slot_method_cname(slot.left_slot.method_name) and not get_slot_method_cname(slot.right_slot.method_name):
warning(pos, "Extension type implements %s() but not %s(). "
"The behaviour has changed from previous Cython versions to match Python semantics. "
"You can implement both special methods in a backwards compatible way." % (
slot.left_slot.method_name,
slot.right_slot.method_name,
))
code.putln(
TempitaUtilityCode.load_as_string(
"BinopSlot", "ExtensionTypes.c",
context={
"func_name": func_name,
"slot_name": slot.slot_name,
"overloads_left": int(has_slot_method(slot.left_slot.method_name)),
"overloads_left": int(bool(get_slot_method_cname(slot.left_slot.method_name))),
"call_left": call_slot_method(slot.left_slot.method_name, reverse=False),
"call_right": call_slot_method(slot.right_slot.method_name, reverse=True),
"type_cname": scope.parent_type.typeptr_cname,
......
......@@ -129,20 +129,33 @@ executed unless they are explicitly called by the subclass.
Arithmetic methods
-------------------
Arithmetic operator methods, such as :meth:`__add__`, behave differently from their
Python counterparts. There are no separate "reversed" versions of these
methods (:meth:`__radd__`, etc.) Instead, if the first operand cannot perform the
operation, the same method of the second operand is called, with the operands
in the same order.
This means that you can't rely on the first parameter of these methods being
"self" or being the right type, and you should test the types of both operands
before deciding what to do. If you can't handle the combination of types you've
been given, you should return `NotImplemented`.
This also applies to the in-place arithmetic method :meth:`__ipow__`. It doesn't apply
to any of the other in-place methods (:meth:`__iadd__`, etc.) which always
take `self` as the first argument.
Arithmetic operator methods, such as :meth:`__add__`, used to behave differently
from their Python counterparts in Cython 0.x, following the low-level semantics
of the C-API slot functions. Since Cython 3.0, they are called in the same way
as in Python, including the separate "reversed" versions of these methods
(:meth:`__radd__`, etc.).
Previously, if the first operand could not perform the operation, the same method
of the second operand was called, with the operands in the same order.
This means that you could not rely on the first parameter of these methods being
"self" or being the right type, and you needed to test the types of both operands
before deciding what to do.
If backwards compatibility is needed, the normal operator method (``__add__``, etc.)
can still be implemented to support both variants, applying a type check to the
arguments. The reversed method (``__radd__``, etc.) can always be implemented
with ``self`` as first argument and will be ignored by older Cython versions, whereas
Cython 3.x and later will only call the normal method with the expected argument order,
and otherwise call the reversed method instead.
If you can't handle the combination of types you've been given, you should return
`NotImplemented`. This will let Python's operator implementation first try to apply
the reversed operator to the second operand, and failing that as well, report an
appropriate error to the user.
This change in behaviour also applies to the in-place arithmetic method :meth:`__ipow__`.
It does not apply to any of the other in-place methods (:meth:`__iadd__`, etc.)
which always take `self` as the first argument.
.. _righ_comparisons:
......
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