Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r1187:8a27b7fa299a
Date: 2013-03-07 22:58 +0100
http://bitbucket.org/cffi/cffi/changeset/8a27b7fa299a/

Log:    Kill the historical non-decorator version of ffi.callback(), as a
        motivation for doing "the right thing" (see new paragraph in docs).

diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -231,13 +231,13 @@
         """
         return self._backend.buffer(cdata, size)
 
-    def callback(self, cdecl, python_callable=None, error=None):
-        """Return a callback object or a decorator making such a
-        callback object.  'cdecl' must name a C function pointer type.
-        The callback invokes the specified 'python_callable' (which may
-        be provided either directly or via a decorator).  Important: the
-        callback object must be manually kept alive for as long as the
-        callback may be invoked from the C level.
+    def callback(self, cdecl, error=None):
+        """Return a a decorator making a callback object.  'cdecl' must
+        name a C function or function pointer type.  The decorated
+        Python function is turned into a <cdata> of the specified
+        function pointer type.  Important: the callback object must be
+        manually kept alive for as long as the callback may be invoked
+        from the C level.
         """
         def callback_decorator_wrap(python_callable):
             if not callable(python_callable):
@@ -246,10 +246,10 @@
             return self._backend.callback(cdecl, python_callable, error)
         if isinstance(cdecl, basestring):
             cdecl = self._typeof(cdecl, consider_function_as_funcptr=True)
-        if python_callable is None:
-            return callback_decorator_wrap                # decorator mode
-        else:
-            return callback_decorator_wrap(python_callable)  # direct mode
+        if isinstance(error, types.FunctionType):
+            raise TypeError("Not supported any more: ffi.callback('...', fn)."
+                            " Use the decorator form: @ffi.callback('...')")
+        return callback_decorator_wrap
 
     def getctype(self, cdecl, replace_with=''):
         """Return a string giving the C type 'cdecl', which may be itself
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -983,23 +983,21 @@
 passed as callbacks.  To make new C callback objects that will invoke a
 Python function, you need to use::
 
-    >>> def myfunc(x, y):
-    ...    return x + y
-    ...
-    >>> ffi.callback("int(int, int)", myfunc)
-    <cdata 'int(*)(int, int)' calling <function myfunc at 0xf757bbc4>>
-
-.. versionadded:: 0.4
-   Or equivalently as a decorator:
-
     >>> @ffi.callback("int(int, int)")
     ... def myfunc(x, y):
     ...    return x + y
+    ...
+    >>> myfunc
+    <cdata 'int(*)(int, int)' calling <function myfunc at 0xf757bbc4>>
 
 Note that you can also use a C *function pointer* type like ``"int(*)(int,
 int)"`` (as opposed to a C *function* type like ``"int(int, int)"``).  It
 is equivalent here.
 
+.. versionchanged:: 0.6
+   ``ffi.callback()`` is now only available as a decorator (see later
+   for the motivation).
+
 Warning: like ffi.new(), ffi.callback() returns a cdata that has
 ownership of its C data.  (In this case, the necessary C data contains
 the libffi data structures to do a callback.)  This means that the
@@ -1009,6 +1007,18 @@
 the callback to remain valid forever, store the object in a fresh global
 variable somewhere.)
 
+Generally, C APIs define callbacks taking a general ``void *`` argument.
+You can make good use of it with CFFI as well, in order to avoid
+creating a large number of callback objects.  You would create instead
+only one (or a small number of) callback as global functions.  You
+cannot give a "pointer" to a real Python object as the ``void *``
+argument, but instead you can use its id() casted to ``void *``.  You
+store in a ``weakref.WeakValueDictionary`` the mapping that goes from
+the id() back to the full object.  When the callback is invoked, you
+look up the full object in the mapping, gracefully ignoring it if it was
+already freed.  (In order to encourage this approach, ``ffi.callback()``
+can only be used as a decorator from version 0.6.)
+
 Note that callbacks of a variadic function type are not supported.  A
 workaround is to add custom C code.  In the following example, a
 callback gets a first argument that counts how many extra ``int``
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -658,11 +658,11 @@
 
     def test_functionptr_simple(self):
         ffi = FFI(backend=self.Backend())
-        py.test.raises(TypeError, ffi.callback, "int(*)(int)", 0)
+        py.test.raises(TypeError, ffi.callback("int(*)(int)"), 0)
         def cb(n):
             return n + 1
         cb.__qualname__ = 'cb'
-        p = ffi.callback("int(*)(int)", cb)
+        p = ffi.callback("int(*)(int)")(cb)
         res = p(41)     # calling an 'int(*)(int)', i.e. a function pointer
         assert res == 42 and type(res) is int
         res = p(ffi.cast("int", -41))
@@ -688,47 +688,47 @@
 
     def test_functionptr_voidptr_return(self):
         ffi = FFI(backend=self.Backend())
-        def cb():
+        @ffi.callback("void*(*)()")
+        def p():
             return ffi.NULL
-        p = ffi.callback("void*(*)()", cb)
         res = p()
         assert res is not None
         assert res == ffi.NULL
         int_ptr = ffi.new('int*')
         void_ptr = ffi.cast('void*', int_ptr)
-        def cb():
+        @ffi.callback("void*(*)()")
+        def p():
             return void_ptr
-        p = ffi.callback("void*(*)()", cb)
         res = p()
         assert res == void_ptr
 
     def test_functionptr_intptr_return(self):
         ffi = FFI(backend=self.Backend())
-        def cb():
+        @ffi.callback("int*(*)()")
+        def p():
             return ffi.NULL
-        p = ffi.callback("int*(*)()", cb)
         res = p()
         assert res == ffi.NULL
         int_ptr = ffi.new('int*')
-        def cb():
+        @ffi.callback("int*(*)()")
+        def p():
             return int_ptr
-        p = ffi.callback("int*(*)()", cb)
         res = p()
         assert repr(res).startswith("<cdata 'int *' 0x")
         assert res == int_ptr
         int_array_ptr = ffi.new('int[1]')
-        def cb():
+        @ffi.callback("int*(*)()")
+        def p():
             return int_array_ptr
-        p = ffi.callback("int*(*)()", cb)
         res = p()
         assert repr(res).startswith("<cdata 'int *' 0x")
         assert res == int_array_ptr
 
     def test_functionptr_void_return(self):
         ffi = FFI(backend=self.Backend())
-        def foo():
+        @ffi.callback("void foo()")
+        def foo_cb():
             pass
-        foo_cb = ffi.callback("void foo()", foo)
         result = foo_cb()
         assert result is None
 
@@ -793,9 +793,9 @@
 
     def test_cast_functionptr_and_int(self):
         ffi = FFI(backend=self.Backend())
-        def cb(n):
+        @ffi.callback("int(*)(int)")
+        def a(n):
             return n + 1
-        a = ffi.callback("int(*)(int)", cb)
         p = ffi.cast("void *", a)
         assert p
         b = ffi.cast("int(*)(int)", p)
@@ -805,18 +805,24 @@
 
     def test_callback_crash(self):
         ffi = FFI(backend=self.Backend())
-        def cb(n):
+        @ffi.callback("int(*)(int)", error=42)
+        def a(n):
             raise Exception
-        a = ffi.callback("int(*)(int)", cb, error=42)
         res = a(1)    # and the error reported to stderr
         assert res == 42
+        #
+        @ffi.callback("int(int)", 46)
+        def a(n):
+            raise Exception
+        res = a(2)    # and the error reported to stderr
+        assert res == 46
 
     def test_structptr_argument(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("struct foo_s { int a, b; };")
-        def cb(p):
+        @ffi.callback("int(*)(struct foo_s[])")
+        def a(p):
             return p[0].a * 1000 + p[0].b * 100 + p[1].a * 10 + p[1].b
-        a = ffi.callback("int(*)(struct foo_s[])", cb)
         res = a([[5, 6], {'a': 7, 'b': 8}])
         assert res == 5678
         res = a([[5], {'b': 8}])
@@ -826,10 +832,10 @@
         ffi = FFI(backend=self.Backend())
         ffi.cdef("struct foo_s { int a, b; };")
         seen = []
-        def cb(argv):
+        @ffi.callback("void(*)(char *[])")
+        def a(argv):
             seen.append(ffi.string(argv[0]))
             seen.append(ffi.string(argv[1]))
-        a = ffi.callback("void(*)(char *[])", cb)
         a([ffi.new("char[]", b"foobar"), ffi.new("char[]", b"baz")])
         assert seen == [b"foobar", b"baz"]
 
@@ -1225,9 +1231,9 @@
         assert a[1] == ffi.NULL
         py.test.raises(TypeError, ffi.cast, "int(*)(int)[5]", 0)
         #
-        def cb(n):
+        @ffi.callback("int(*)(int)")
+        def f(n):
             return n + 1
-        f = ffi.callback("int(*)(int)", cb)
         a = ffi.new("int(*[5])(int)", [f, f])
         assert a[1](42) == 43
 
@@ -1235,23 +1241,23 @@
         # In C, function arguments can be declared with a function type,
         # which is automatically replaced with the ptr-to-function type.
         ffi = FFI(backend=self.Backend())
-        def cb(a, b):
+        @ffi.callback("char cb(char, char)")
+        def f(a, b):
             return chr(ord(a) + ord(b)).encode()
-        f = ffi.callback("char cb(char, char)", cb)
         assert f(b'A', b'\x01') == b'B'
+        @ffi.callback("char g(char cb(char, char))")
         def g(callback):
             return callback(b'A', b'\x01')
-        g = ffi.callback("char g(char cb(char, char))", g)
         assert g(f) == b'B'
 
     def test_vararg_callback(self):
         py.test.skip("callback with '...'")
         ffi = FFI(backend=self.Backend())
-        def cb(i, va_list):
+        @ffi.callback("long long cb(long i, ...)")
+        def f(i, va_list):
             j = ffi.va_arg(va_list, "int")
             k = ffi.va_arg(va_list, "long long")
             return i * 2 + j * 3 + k * 5
-        f = ffi.callback("long long cb(long i, ...)", cb)
         res = f(10, ffi.cast("int", 100), ffi.cast("long long", 1000))
         assert res == 20 + 300 + 5000
 
@@ -1265,6 +1271,10 @@
         assert cb(-100, -10) == -90
         sz = ffi.sizeof("long")
         assert cb((1 << (sz*8-1)) - 1, -10) == 42
+        #
+        py.test.raises(TypeError, ffi.callback, "int(int)", lambda n: n+1)
+        py.test.raises(TypeError, ffi.callback, "int(int)", lambda n: n+1,
+                       error=42)
 
     def test_unique_types(self):
         ffi1 = FFI(backend=self.Backend())
diff --git a/testing/test_ffi_backend.py b/testing/test_ffi_backend.py
--- a/testing/test_ffi_backend.py
+++ b/testing/test_ffi_backend.py
@@ -17,7 +17,7 @@
         ffi = FFI(backend=self.Backend())
         ffi.cdef("struct foo_s { int a,b,c,d,e; int x:1; };")
         e = py.test.raises(NotImplementedError, ffi.callback,
-                           "struct foo_s foo(void)", lambda: 42)
+                           "struct foo_s foo(void)")
         assert str(e.value) == ("<struct foo_s(*)(void)>: "
             "cannot pass as argument or return value a struct with bit fields")
 
diff --git a/testing/test_function.py b/testing/test_function.py
--- a/testing/test_function.py
+++ b/testing/test_function.py
@@ -209,8 +209,8 @@
         def cb(charp):
             assert repr(charp).startswith("<cdata 'char *' 0x")
             return 42
-        fptr = ffi.callback("int(*)(const char *txt)", cb)
-        assert fptr != ffi.callback("int(*)(const char *)", cb)
+        fptr = ffi.callback("int(*)(const char *txt)")(cb)
+        assert fptr != ffi.callback("int(*)(const char *)")(cb)
         assert repr(fptr) == "<cdata 'int(*)(char *)' calling %r>" % (cb,)
         res = fptr(b"Hello")
         assert res == 42
@@ -232,9 +232,9 @@
     def test_callback_returning_void(self):
         ffi = FFI(backend=self.Backend())
         for returnvalue in [None, 42]:
-            def cb():
+            @ffi.callback("void(*)(void)")
+            def fptr():
                 return returnvalue
-            fptr = ffi.callback("void(*)(void)", cb)
             old_stderr = sys.stderr
             try:
                 sys.stderr = StringIO()
diff --git a/testing/test_unicode_literals.py b/testing/test_unicode_literals.py
--- a/testing/test_unicode_literals.py
+++ b/testing/test_unicode_literals.py
@@ -67,6 +67,7 @@
 
 def test_callback():
     ffi = FFI()
-    cb = ffi.callback("int(int)",                 # unicode literal
-                      lambda x: x + 42)
+    @ffi.callback("int(int)")                     # unicode literal
+    def cb(x):
+        return x + 42
     assert cb(5) == 47
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -739,7 +739,10 @@
     """)
     lib.reset_cb()
     assert lib.foo(6) == 41
-    my_callback = ffi.callback("int(*)(int)", lambda n: n * 222)
+    #
+    @ffi.callback("int(*)(int)")
+    def my_callback(n):
+        return n * 222
     lib.cb = my_callback
     assert lib.foo(4) == 887
 
@@ -757,7 +760,10 @@
     """)
     lib.reset_cb()
     assert lib.foo(6) == 41
-    my_callback = ffi.callback("int(*)(int)", lambda n: n * 222)
+    #
+    @ffi.callback("int(int)")
+    def my_callback(n):
+        return n * 222
     lib.cb = my_callback
     assert lib.foo(4) == 887
 
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to