Ticket #494 (closed defect: fixed)

Opened 3 years ago

Last modified 5 months ago

Cython functions not bound as methods

Reported by: robertwb Owned by: robertwb
Priority: major Milestone: 0.17
Component: Python Semantics Keywords:
Cc: bfroehle

Description (last modified by scoder) (diff)

In Python, one can do

class A:
    pass

def foo(self):
    print self

A.foo = foo
print A().foo()

and foo will get bound as expected. However, Cython creates PyCFunctions which do not properly bind when accessed (possibly by design). This is the root issue behind #478, and will become more evident once closures start getting used.

PyCFunction does not allow subclassing. The only reasonable fix I see is to create a new type that wraps PyCFunction but behaves like PyFunction. This will have indirection overhead. To avoid overhead, one alternative is for our new type to have the same struct and slots as PyCFunction (with the exception of tp_name, tp_descr_get, tp_alloc, and tp_dealloc of course). This still may have overhead as the Python compiler optimizes for PyCFunction calls--perhaps the user should be allowed to control this (as part of an "ultra pure, I don't care what cost" mode...)?

Change History

Changed 3 years ago by robertwb

  • description modified (diff)

Changed 3 years ago by robertwb

  • owner set to robertwb

 http://hg.cython.org/cython-closures/rev/8dab9a8550e5 and the next couple of changes. (Not on for all functions by default...)

Changed 3 years ago by robertwb

  • milestone changed from 0.13 to 0.14

I've added a test case, but lets enable it in a future release.

Changed 2 years ago by robertwb

  • milestone changed from 0.14.1 to 0.15

Changed 13 months ago by mark

  • milestone changed from 0.15 to 0.17

Changed 10 months ago by bfroehle

  • cc bfroehle added

I've run into this before and have also asked about it on  StackOverflow. In general it's pretty easy to work around in Python 2:

A.foo = types.MethodType(foo, None, A)

But somewhat troublesome in Python 3. The best suggestion I got was:

A.foo = functools.update_wrapper(lambda *a, **kw: foo(*a, **kw), foo)

It's a weird gotcha, but engineering a workaround without introducing performance penalties is likely difficult (if not impossible). Perhaps the best option is just to document it and move on.

Changed 5 months ago by scoder

  • status changed from new to closed
  • resolution set to fixed
  • description modified (diff)

This works with the "binding=True" directive, which is now (since 0.17 IIRC) on by default for .py files.

Note: See TracTickets for help on using tickets.