Commit e6067f09 authored by Stefan Behnel's avatar Stefan Behnel Committed by GitHub

Merge pull request #2864 from cython/gh2564_enable_binding: Enable "binding" directive by default

parents 4dd7c18d f055a66c
...@@ -9235,7 +9235,10 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin): ...@@ -9235,7 +9235,10 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
scope = Symtab.StructOrUnionScope(cname) scope = Symtab.StructOrUnionScope(cname)
self.defaults = [] self.defaults = []
for arg in nonliteral_objects: for arg in nonliteral_objects:
entry = scope.declare_var(arg.name, arg.type, None, type_ = arg.type
if type_.is_buffer:
type_ = type_.base
entry = scope.declare_var(arg.name, type_, None,
Naming.arg_prefix + arg.name, Naming.arg_prefix + arg.name,
allow_pyobject=True) allow_pyobject=True)
self.defaults.append((arg, entry)) self.defaults.append((arg, entry))
......
...@@ -2300,7 +2300,7 @@ class CFuncDefNode(FuncDefNode): ...@@ -2300,7 +2300,7 @@ class CFuncDefNode(FuncDefNode):
# is_c_class_method whether this is a cclass method # is_c_class_method whether this is a cclass method
child_attrs = ["base_type", "declarator", "body", "py_func_stat", "decorators"] child_attrs = ["base_type", "declarator", "body", "py_func_stat", "decorators"]
outer_attrs = ["decorators"] outer_attrs = ["decorators", "py_func_stat"]
inline_in_pxd = False inline_in_pxd = False
decorators = None decorators = None
...@@ -5041,8 +5041,6 @@ class CClassDefNode(ClassDefNode): ...@@ -5041,8 +5041,6 @@ class CClassDefNode(ClassDefNode):
# This is needed to generate evaluation code for # This is needed to generate evaluation code for
# default values of method arguments. # default values of method arguments.
code.mark_pos(self.pos) code.mark_pos(self.pos)
if self.body:
self.body.generate_execution_code(code)
if not self.entry.type.early_init: if not self.entry.type.early_init:
if self.type_init_args: if self.type_init_args:
self.type_init_args.generate_evaluation_code(code) self.type_init_args.generate_evaluation_code(code)
...@@ -5070,6 +5068,8 @@ class CClassDefNode(ClassDefNode): ...@@ -5070,6 +5068,8 @@ class CClassDefNode(ClassDefNode):
self.type_init_args.free_temps(code) self.type_init_args.free_temps(code)
self.generate_type_ready_code(self.entry, code, True) self.generate_type_ready_code(self.entry, code, True)
if self.body:
self.body.generate_execution_code(code)
# Also called from ModuleNode for early init types. # Also called from ModuleNode for early init types.
@staticmethod @staticmethod
......
...@@ -169,6 +169,7 @@ def get_directive_defaults(): ...@@ -169,6 +169,7 @@ def get_directive_defaults():
# Declare compiler directives # Declare compiler directives
_directive_defaults = { _directive_defaults = {
'binding': True,
'boundscheck' : True, 'boundscheck' : True,
'nonecheck' : False, 'nonecheck' : False,
'initializedcheck' : True, 'initializedcheck' : True,
...@@ -237,8 +238,6 @@ _directive_defaults = { ...@@ -237,8 +238,6 @@ _directive_defaults = {
'test_fail_if_path_exists' : [], 'test_fail_if_path_exists' : [],
# experimental, subject to change # experimental, subject to change
'binding': None,
'formal_grammar': False, 'formal_grammar': False,
} }
......
...@@ -713,7 +713,11 @@ Cython code. Here is the list of currently supported directives: ...@@ -713,7 +713,11 @@ Cython code. Here is the list of currently supported directives:
class attribute (hence the name) and will emulate the attributes class attribute (hence the name) and will emulate the attributes
of Python functions, including introspections like argument names and of Python functions, including introspections like argument names and
annotations. annotations.
Default is False.
Default is True.
.. versionchanged:: 3.0.0
Default changed from False to True
``boundscheck`` (True / False) ``boundscheck`` (True / False)
If set to False, Cython is free to assume that indexing operations If set to False, Cython is free to assume that indexing operations
......
...@@ -959,6 +959,7 @@ def addref(*args): ...@@ -959,6 +959,7 @@ def addref(*args):
def decref(*args): def decref(*args):
for item in args: Py_DECREF(item) for item in args: Py_DECREF(item)
@cython.binding(False)
def get_refcount(x): def get_refcount(x):
return (<PyObject*>x).ob_refcnt return (<PyObject*>x).ob_refcnt
...@@ -991,15 +992,16 @@ def assign_to_object(object[object] buf, int idx, obj): ...@@ -991,15 +992,16 @@ def assign_to_object(object[object] buf, int idx, obj):
See comments on printbuf_object above. See comments on printbuf_object above.
>>> a, b = [1, 2, 3], [4, 5, 6] >>> a, b = [1, 2, 3], [4, 5, 6]
>>> get_refcount(a), get_refcount(b) >>> rca1, rcb1 = get_refcount(a), get_refcount(b)
(2, 2) >>> rca1 == rcb1
True
>>> addref(a) >>> addref(a)
>>> A = ObjectMockBuffer(None, [1, a]) # 1, ...,otherwise it thinks nested lists... >>> A = ObjectMockBuffer(None, [1, a]) # 1, ...,otherwise it thinks nested lists...
>>> get_refcount(a), get_refcount(b) >>> get_refcount(a) == rca1+1, get_refcount(b) == rcb1
(3, 2) (True, True)
>>> assign_to_object(A, 1, b) >>> assign_to_object(A, 1, b)
>>> get_refcount(a), get_refcount(b) >>> get_refcount(a) == rca1, get_refcount(b) == rcb1+1
(2, 3) (True, True)
>>> decref(b) >>> decref(b)
""" """
buf[idx] = obj buf[idx] = obj
...@@ -1010,15 +1012,14 @@ def assign_temporary_to_object(object[object] buf): ...@@ -1010,15 +1012,14 @@ def assign_temporary_to_object(object[object] buf):
See comments on printbuf_object above. See comments on printbuf_object above.
>>> a, b = [1, 2, 3], {4:23} >>> a, b = [1, 2, 3], {4:23}
>>> get_refcount(a) >>> rc1 = get_refcount(a)
2
>>> addref(a) >>> addref(a)
>>> A = ObjectMockBuffer(None, [b, a]) >>> A = ObjectMockBuffer(None, [b, a])
>>> get_refcount(a) >>> get_refcount(a) == rc1+1
3 True
>>> assign_temporary_to_object(A) >>> assign_temporary_to_object(A)
>>> get_refcount(a) >>> get_refcount(a) == rc1
2 True
>>> printbuf_object(A, (2,)) >>> printbuf_object(A, (2,))
{4: 23} 2 {4: 23} 2
......
...@@ -626,6 +626,7 @@ def addref(*args): ...@@ -626,6 +626,7 @@ def addref(*args):
def decref(*args): def decref(*args):
for item in args: Py_DECREF(item) for item in args: Py_DECREF(item)
@cython.binding(False)
def get_refcount(x): def get_refcount(x):
return (<PyObject*>x).ob_refcnt return (<PyObject*>x).ob_refcnt
......
...@@ -1058,6 +1058,7 @@ def addref(*args): ...@@ -1058,6 +1058,7 @@ def addref(*args):
def decref(*args): def decref(*args):
for item in args: Py_DECREF(item) for item in args: Py_DECREF(item)
@cython.binding(False)
def get_refcount(x): def get_refcount(x):
return (<PyObject*>x).ob_refcnt return (<PyObject*>x).ob_refcnt
...@@ -2141,7 +2142,7 @@ def test_object_dtype_copying(): ...@@ -2141,7 +2142,7 @@ def test_object_dtype_copying():
7 7
8 8
9 9
2 5 5
1 5 1 5
""" """
cdef int i cdef int i
...@@ -2162,10 +2163,12 @@ def test_object_dtype_copying(): ...@@ -2162,10 +2163,12 @@ def test_object_dtype_copying():
print m2[i] print m2[i]
obj = m2[5] obj = m2[5]
print get_refcount(obj), obj refcount1 = get_refcount(obj)
print obj
del m2 del m2
print get_refcount(obj), obj refcount2 = get_refcount(obj)
print refcount1 - refcount2, obj
assert unique_refcount == get_refcount(unique), (unique_refcount, get_refcount(unique)) assert unique_refcount == get_refcount(unique), (unique_refcount, get_refcount(unique))
......
cimport cython
cdef class CBase(object): cdef class CBase(object):
cdef int a cdef int a
cdef c_method(self): cdef c_method(self):
...@@ -9,7 +11,8 @@ class PyBase(object): ...@@ -9,7 +11,8 @@ class PyBase(object):
def py_method(self): def py_method(self):
return "PyBase" return "PyBase"
cdef class Both(CBase, PyBase): @cython.binding(True)
cdef class BothBound(CBase, PyBase):
cdef dict __dict__ cdef dict __dict__
""" """
>>> b = Both() >>> b = Both()
...@@ -32,7 +35,7 @@ cdef class Both(CBase, PyBase): ...@@ -32,7 +35,7 @@ cdef class Both(CBase, PyBase):
def call_c_method(self): def call_c_method(self):
return self.c_method() return self.c_method()
cdef class BothSub(Both): cdef class BothSub(BothBound):
""" """
>>> b = BothSub() >>> b = BothSub()
>>> b.py_method() >>> b.py_method()
...@@ -43,3 +46,27 @@ cdef class BothSub(Both): ...@@ -43,3 +46,27 @@ cdef class BothSub(Both):
'Both' 'Both'
""" """
pass pass
@cython.binding(False)
cdef class BothUnbound(CBase, PyBase):
cdef dict __dict__
"""
>>> b = Both()
>>> b.py_method()
'PyBase'
>>> b.cp_method()
'Both'
>>> b.call_c_method()
'Both'
>>> isinstance(b, CBase)
True
>>> isinstance(b, PyBase)
True
"""
cdef c_method(self):
return "Both"
cpdef cp_method(self):
return "Both"
def call_c_method(self):
return self.c_method()
# cython: binding=True
# mode: run
# tag: cyfunction
cpdef int simple() nogil:
"""
>>> simple()
1
"""
return 1
cpdef int call_nogil():
"""
>>> call_nogil()
1
"""
with nogil:
return simple()
cimport cython
cdef sorteditems(d): cdef sorteditems(d):
return tuple(sorted(d.items())) return tuple(sorted(d.items()))
...@@ -90,7 +92,24 @@ cdef class Silly: ...@@ -90,7 +92,24 @@ cdef class Silly:
>>> s.onlyt(1, a=2) >>> s.onlyt(1, a=2)
Traceback (most recent call last): Traceback (most recent call last):
TypeError: onlyt() got an unexpected keyword argument 'a' TypeError: onlyt() got an unexpected keyword argument 'a'
>>> test_no_copy_args(s.onlyt) """
return a
@cython.binding(False) # passthrough of exact same tuple can't work with binding
def onlyt_nobinding(self, *a):
"""
>>> s = Silly()
>>> s.onlyt_nobinding(1)
(1,)
>>> s.onlyt_nobinding(1,2)
(1, 2)
>>> s.onlyt_nobinding(a=1)
Traceback (most recent call last):
TypeError: onlyt_nobinding() got an unexpected keyword argument 'a'
>>> s.onlyt_nobinding(1, a=2)
Traceback (most recent call last):
TypeError: onlyt_nobinding() got an unexpected keyword argument 'a'
>>> test_no_copy_args(s.onlyt_nobinding)
True True
""" """
return a return a
...@@ -130,6 +149,7 @@ cdef class Silly: ...@@ -130,6 +149,7 @@ cdef class Silly:
""" """
return a + sorteditems(k) return a + sorteditems(k)
@cython.binding(False) # passthrough of exact same tuple can't work with binding
def t_kwonly(self, *a, k): def t_kwonly(self, *a, k):
""" """
>>> s = Silly() >>> s = Silly()
......
...@@ -12,3 +12,39 @@ class A: ...@@ -12,3 +12,39 @@ class A:
def foo(self): def foo(self):
return self is not None return self is not None
# assignment of functions used in a "static method" type way behaves differently
# in Python2 and 3
import sys
if sys.version_info[0] == 2:
__doc__ = """>>> B.plus1(1) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: unbound
"""
else:
__doc__ = """>>> B.plus1(1)
2
"""
# with binding==False assignment of functions always worked - doesn't match Python
# behaviour but ensures Cython behaviour stays consistent
__doc__ += """
>>> B.plus1_nobind(1)
2
"""
cimport cython
def f_plus(a):
return a + 1
@cython.binding(False)
def f_plus_nobind(a):
return a+1
cdef class B:
plus1 = f_plus
plus1_nobind = f_plus_nobind
...@@ -11,3 +11,35 @@ class A: ...@@ -11,3 +11,35 @@ class A:
def foo(self): def foo(self):
return self is not None return self is not None
# assignment of functions used in a "static method" type way behaves differently
# in Python2 and 3
import sys
if sys.version_info[0] == 2:
__doc__ = u"""
>>> B.plus1(1) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: unbound
>>> C.plus1(1) #doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: unbound
"""
else:
__doc__ = u"""
>>> B.plus1(1)
2
>>> C.plus1(1)
2
"""
def f_plus(a):
return a + 1
class B:
plus1 = f_plus
class C(object):
plus1 = f_plus
...@@ -3,12 +3,17 @@ ...@@ -3,12 +3,17 @@
cimport cython cimport cython
# Use a single global object for identity checks.
# PyPy can optimise away integer objects, for example, and may fail the 'is' test.
obj = object()
@cython.test_fail_if_path_exists('//NotNode') @cython.test_fail_if_path_exists('//NotNode')
def is_not(a, b): def is_not(a, b):
""" """
>>> is_not(1, 2) >>> is_not(1, 2)
True True
>>> x = 1 >>> x = obj
>>> is_not(x, x) >>> is_not(x, x)
False False
""" """
...@@ -20,7 +25,7 @@ def not_is_not(a, b): ...@@ -20,7 +25,7 @@ def not_is_not(a, b):
""" """
>>> not_is_not(1, 2) >>> not_is_not(1, 2)
False False
>>> x = 1 >>> x = obj
>>> not_is_not(x, x) >>> not_is_not(x, x)
True True
""" """
...@@ -32,7 +37,7 @@ def not_is(a, b): ...@@ -32,7 +37,7 @@ def not_is(a, b):
""" """
>>> not_is(1, 2) >>> not_is(1, 2)
True True
>>> x = 1 >>> x = obj
>>> not_is(x, x) >>> not_is(x, x)
False False
""" """
......
...@@ -46,9 +46,13 @@ def compute(val): ...@@ -46,9 +46,13 @@ def compute(val):
def a(in_k, x1, x2, x3, x4, x5): def a(in_k, x1, x2, x3, x4, x5):
""" """
>>> import sys >>> import sys
>>> sys.setrecursionlimit(1350) >>> old_limit = sys.getrecursionlimit()
>>> sys.setrecursionlimit(1350 if not getattr(sys, 'pypy_version_info', None) else 2700)
>>> a(10, 1, -1, -1, 1, 0) >>> a(10, 1, -1, -1, 1, 0)
-67 -67
>>> sys.setrecursionlimit(old_limit)
""" """
k = [in_k] k = [in_k]
def b(): def b():
......
...@@ -12,9 +12,7 @@ __doc__ = u""" ...@@ -12,9 +12,7 @@ __doc__ = u"""
>>> short_stats['f_cdef'] >>> short_stats['f_cdef']
100 100
>>> short_stats['f_cpdef'] >>> short_stats['f_cpdef']
200 300
>>> short_stats['f_cpdef (wrapper)']
100
>>> short_stats['f_inline'] >>> short_stats['f_inline']
100 100
>>> short_stats['f_inline_prof'] >>> short_stats['f_inline_prof']
...@@ -50,9 +48,7 @@ __doc__ = u""" ...@@ -50,9 +48,7 @@ __doc__ = u"""
>>> short_stats['m_cdef'] >>> short_stats['m_cdef']
100 100
>>> short_stats['m_cpdef'] >>> short_stats['m_cpdef']
200 300
>>> short_stats['m_cpdef (wrapper)']
100
>>> try: >>> try:
... os.unlink(statsfile) ... os.unlink(statsfile)
...@@ -60,10 +56,10 @@ __doc__ = u""" ...@@ -60,10 +56,10 @@ __doc__ = u"""
... pass ... pass
>>> sorted(callees(s, 'test_profile')) #doctest: +NORMALIZE_WHITESPACE >>> sorted(callees(s, 'test_profile')) #doctest: +NORMALIZE_WHITESPACE
['f_cdef', 'f_cpdef', 'f_cpdef (wrapper)', 'f_def', ['f_cdef', 'f_cpdef', 'f_def',
'f_inline', 'f_inline_prof', 'f_inline', 'f_inline_prof',
'f_raise', 'f_raise',
'm_cdef', 'm_cpdef', 'm_cpdef (wrapper)', 'm_def', 'm_cdef', 'm_cpdef', 'm_def',
'withgil_prof'] 'withgil_prof']
>>> profile.runctx("test_generators()", locals(), globals(), statsfile) >>> profile.runctx("test_generators()", locals(), globals(), statsfile)
......
# tag: pstats
# cython: profile = True
# cython: binding = False
__doc__ = u"""
>>> import os, tempfile, cProfile as profile, pstats
>>> statsfile = tempfile.mkstemp()[1]
>>> profile.runctx("test_profile(100)", locals(), globals(), statsfile)
>>> s = pstats.Stats(statsfile)
>>> short_stats = dict([(k[2], v[1]) for k,v in s.stats.items()])
>>> short_stats['f_def']
100
>>> short_stats['f_cdef']
100
>>> short_stats['f_cpdef']
200
>>> short_stats['f_cpdef (wrapper)']
100
>>> short_stats['f_inline']
100
>>> short_stats['f_inline_prof']
100
>>> short_stats['f_noprof']
Traceback (most recent call last):
...
KeyError: 'f_noprof'
>>> short_stats['f_raise']
100
>>> short_stats['withgil_prof']
100
>>> short_stats['withgil_noprof']
Traceback (most recent call last):
...
KeyError: 'withgil_noprof'
>>> short_stats['nogil_prof']
Traceback (most recent call last):
...
KeyError: 'nogil_prof'
>>> short_stats['nogil_noprof']
Traceback (most recent call last):
...
KeyError: 'nogil_noprof'
>>> short_stats['f_raise']
100
>>> short_stats['m_def']
200
>>> short_stats['m_cdef']
100
>>> short_stats['m_cpdef']
200
>>> short_stats['m_cpdef (wrapper)']
100
>>> try:
... os.unlink(statsfile)
... except:
... pass
>>> sorted(callees(s, 'test_profile')) #doctest: +NORMALIZE_WHITESPACE
['f_cdef', 'f_cpdef', 'f_cpdef (wrapper)', 'f_def',
'f_inline', 'f_inline_prof',
'f_raise',
'm_cdef', 'm_cpdef', 'm_cpdef (wrapper)', 'm_def',
'withgil_prof']
>>> profile.runctx("test_generators()", locals(), globals(), statsfile)
>>> s = pstats.Stats(statsfile)
>>> short_stats = dict([(k[2], v[1]) for k,v in s.stats.items()])
>>> short_stats['generator']
3
>>> short_stats['generator_exception']
2
>>> short_stats['genexpr']
11
>>> sorted(callees(s, 'test_generators'))
['call_generator', 'call_generator_exception', 'generator_expr']
>>> list(callees(s, 'call_generator'))
['generator']
>>> list(callees(s, 'generator'))
[]
>>> list(callees(s, 'generator_exception'))
[]
>>> list(callees(s, 'generator_expr'))
['genexpr']
>>> list(callees(s, 'genexpr'))
[]
>>> def python_generator():
... yield 1
... yield 2
>>> def call_python_generator():
... list(python_generator())
>>> profile.runctx("call_python_generator()", locals(), globals(), statsfile)
>>> python_stats = pstats.Stats(statsfile)
>>> python_stats_dict = dict([(k[2], v[1]) for k,v in python_stats.stats.items()])
>>> profile.runctx("call_generator()", locals(), globals(), statsfile)
>>> cython_stats = pstats.Stats(statsfile)
>>> cython_stats_dict = dict([(k[2], v[1]) for k,v in cython_stats.stats.items()])
>>> python_stats_dict['python_generator'] == cython_stats_dict['generator']
True
>>> try:
... os.unlink(statsfile)
... except:
... pass
"""
cimport cython
def callees(pstats, target_caller):
pstats.calc_callees()
for (_, _, caller), callees in pstats.all_callees.items():
if caller == target_caller:
for (file, line, callee) in callees.keys():
if 'pyx' in file:
yield callee
def test_profile(long N):
cdef long i, n = 0
cdef A a = A()
for i from 0 <= i < N:
n += f_def(i)
n += f_cdef(i)
n += f_cpdef(i)
n += (<object>f_cpdef)(i)
n += f_inline(i)
n += f_inline_prof(i)
n += f_noprof(i)
n += nogil_noprof(i)
n += nogil_prof(i)
n += withgil_noprof(i)
n += withgil_prof(i)
n += a.m_def(i)
n += (<object>a).m_def(i)
n += a.m_cpdef(i)
n += (<object>a).m_cpdef(i)
n += a.m_cdef(i)
try:
n += f_raise(i+2)
except RuntimeError:
pass
return n
def f_def(long a):
return a
cdef long f_cdef(long a):
return a
cpdef long f_cpdef(long a):
return a
cdef inline long f_inline(long a):
return a
@cython.profile(True)
cdef inline long f_inline_prof(long a):
return a
@cython.profile(False)
cdef int f_noprof(long a):
return a
cdef long f_raise(long) except -2:
raise RuntimeError
@cython.profile(False)
cdef int withgil_noprof(long a) with gil:
return (a)
@cython.profile(True)
cdef int withgil_prof(long a) with gil:
return (a)
@cython.profile(False)
cdef int nogil_noprof(long a) nogil:
return a
@cython.profile(True)
cdef int nogil_prof(long a) nogil:
return a
cdef class A(object):
def m_def(self, long a):
return a
cpdef m_cpdef(self, long a):
return a
cdef m_cdef(self, long a):
return a
def test_generators():
call_generator()
call_generator_exception()
generator_expr()
def call_generator():
list(generator())
def generator():
yield 1
yield 2
def call_generator_exception():
try:
list(generator_exception())
except ValueError:
pass
def generator_exception():
yield 1
raise ValueError(2)
def generator_expr():
e = (x for x in range(10))
return sum(e)
__doc__ = u"""
>>> class1.plus1(1)
2
>>> class2.plus1(1)
2
>>> class3.plus1(1)
2
>>> class4.plus1(1)
2
>>> class4().plus1(1)
2
>>> class4.bplus1(1)
2
>>> class4().bplus1(1)
2
"""
cimport cython cimport cython
def f_plus(a):
return a + 1
class class1: class class1:
plus1 = f_plus u"""
>>> class1.plus1(1)
class class2(object): 2
plus1 = f_plus >>> class1().plus1(1)
2
cdef class class3: >>> class1.bplus1(1)
plus1 = f_plus 2
>>> class1().bplus1(1)
class class4: 2
"""
@staticmethod @staticmethod
def plus1(a): def plus1(a):
return a + 1 return a + 1
...@@ -49,14 +31,14 @@ def nested_class(): ...@@ -49,14 +31,14 @@ def nested_class():
>>> obj.plus1(1) >>> obj.plus1(1)
2 2
""" """
class class5(object): class class2(object):
def __new__(cls): # implicit staticmethod def __new__(cls): # implicit staticmethod
return object.__new__(cls) return object.__new__(cls)
@staticmethod @staticmethod
def plus1(a): def plus1(a):
return a + 1 return a + 1
return class5 return class2
cdef class BaseClass(object): cdef class BaseClass(object):
......
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