3

I am trying to define some built-in arithmetic operations for a cdef class in a Cython file.

At first I tried making the __pow__ function like so:

def __pow__(self, other, modulo=None):
    pass

But received the following error message when compiling:

This argument cannot have a default value

(The argument the error message refers to is modulo)

Removing the default value for modulo allows the file to compile properly, but forces the user to provide the third argument, which is not only strange and annoying, but it also prevents the use of the ** operator (pow would have to be used instead).

How can I implement __pow__ in Cython such that the third argument is optional?

Will Da Silva
  • 6,386
  • 2
  • 27
  • 52
  • 1
    Maybe if you wrap the function in a decorator that has the optional parameter you might be able to fool the compiler, but I'd guess not... – Giacomo Alzetta Jun 28 '19 at 14:11
  • 1
    You might be better [submitting a bug report](https://github.com/cython/cython/issues) – DavidW Jun 28 '19 at 14:13
  • @WillDaSilva Lol, can't believe that. I added an answer with what I was mentioning, I believe that's what did it for you? – Giacomo Alzetta Jun 28 '19 at 14:48

2 Answers2

3

You don't need to assign a default value to the third argument in __pow__: cython does that for you. When using the ** operator or pow() with only 2 arguments, the third argument is set to None. You can then explicitly check for None and return NotImplemented if you don't intend to handle the 3-argument form of pow.

A simple example cython class, in _cclass_test.pyx:

# cython: language_level=3
cdef class Test:
    def __pow__(x, y, z):
        if z is None:
            print("Called with just x and y")
        else:
            print("Called with x, y and z")

And an example of its use:

import pyximport
pyximport.install()
from _cclass_test import Test

t = Test()
t ** 5
pow(t, 5)
pow(t, 5, 3)

Output:

$ python cclass_test.py 
Called with just x and y
Called with just x and y
Called with x, y and z

(Tested with Cython version 0.29.12)

jlovell
  • 56
  • 4
  • 1
    Under the hood, Cython is probably just not removing a `None`, rather than putting one there for you. (The [C API version](https://docs.python.org/3/c-api/number.html#c.PyNumber_Power) of `**` requires the caller to pass `None` as a third argument, and the [C hook](https://docs.python.org/3/c-api/typeobj.html#c.PyNumberMethods.nb_power) for implementing `**` takes 3 arguments unconditionally. Cython is probably just compiling your method and sticking it in the C slot directly.) – user2357112 Oct 23 '19 at 08:23
1

You can use a decorator to trick the cython compiler to think that that argument has no default value:

def trickster(func):
    def wrapper(self, other, modulo=None):
        return func(self, other, modulo)
    return wrapper


class MyClass:
   ...
   @trickster
   def __pow__(self, other, modulo):
       ...
Giacomo Alzetta
  • 2,431
  • 6
  • 17
  • Well, this tricked the compiler, but unfortunately it results in a segmentation fault on import. As far as I can tell, Cython special methods cannot be decorated. – Will Da Silva Jun 28 '19 at 18:44
  • @WillDaSilva Uhm, that's strange. Did you try to use a "noop decorator"? I mean: replace `modulo=None` with just `modulo` and see if it gives a segmentation fault anyway? – Giacomo Alzetta Jul 01 '19 at 07:17