1# cython: binding=True
2# mode: run
3# tag: cyfunction
4
5import sys
6IS_PY2 = sys.version_info[0] < 3
7IS_PY3 = sys.version_info[0] >= 3
8IS_PY34 = sys.version_info > (3, 4, 0, 'beta', 3)
9
10
11def inspect_isroutine():
12    """
13    >>> inspect_isroutine()
14    True
15    """
16    import inspect
17    return inspect.isroutine(inspect_isroutine)
18
19
20def inspect_isfunction():
21    """
22    >>> inspect_isfunction()
23    False
24    False
25    """
26    import inspect, types
27    print isinstance(inspect_isfunction, types.FunctionType)
28    return inspect.isfunction(inspect_isfunction)
29
30
31def inspect_isbuiltin():
32    """
33    >>> inspect_isbuiltin()
34    False
35    False
36    """
37    import inspect, types
38    print isinstance(inspect_isfunction, types.BuiltinFunctionType)
39    return inspect.isbuiltin(inspect_isbuiltin)
40
41
42def inspect_signature(a, b, c=123, *, d=234):
43    """
44    >>> sig = inspect_signature(1, 2)
45    >>> if IS_PY34: list(sig.parameters)
46    ... else: ['a', 'b', 'c', 'd']
47    ['a', 'b', 'c', 'd']
48    >>> if IS_PY34: sig.parameters['c'].default == 123
49    ... else: True
50    True
51    >>> if IS_PY34: sig.parameters['d'].default == 234
52    ... else: True
53    True
54    """
55    import inspect
56    return inspect.signature(inspect_signature) if IS_PY34 else None
57
58
59# def test___signature__(a, b, c=123, *, d=234):
60#     """
61#     >>> sig = test___signature__(1, 2)
62#     >>> if IS_PY34: list(sig.parameters)
63#     ... else: ['a', 'b', 'c', 'd']
64#     ['a', 'b', 'c', 'd']
65#     >>> if IS_PY34: sig.parameters['c'].default == 123
66#     ... else: True
67#     True
68#     >>> if IS_PY34: sig.parameters['d'].default == 234
69#     ... else: True
70#     True
71#     """
72#     return inspect_signature.__signature__ if IS_PY34 else None
73
74
75def test_dict():
76    """
77    >>> test_dict.foo = 123
78    >>> test_dict.__dict__
79    {'foo': 123}
80    >>> test_dict.__dict__ = {'bar': 321}
81    >>> test_dict.__dict__
82    {'bar': 321}
83    >>> test_dict.func_dict
84    {'bar': 321}
85    """
86
87def test_name():
88    """
89    >>> test_name.__name__
90    'test_name'
91    >>> test_name.func_name
92    'test_name'
93    >>> test_name.__name__ = 123 #doctest:+ELLIPSIS
94    Traceback (most recent call last):
95    TypeError: __name__ must be set to a ... object
96    >>> test_name.__name__ = 'foo'
97    >>> test_name.__name__
98    'foo'
99    """
100
101def test_doc():
102    """
103    >>> del test_doc.__doc__
104    >>> test_doc.__doc__
105    >>> test_doc.__doc__ = 'docstring'
106    >>> test_doc.__doc__
107    'docstring'
108    >>> test_doc.func_doc
109    'docstring'
110    """
111
112
113def test_hash():
114    """
115    >>> d = {test_hash: 123}
116    >>> test_hash in d
117    True
118    >>> d[test_hash]
119    123
120    >>> hash(test_hash) == hash(test_hash)
121    True
122    """
123
124
125def test_closure():
126    """
127    >>> test_closure.func_closure is None
128    True
129    """
130
131def test_globals():
132    """
133    >>> test_globals.func_globals is not None
134    True
135    >>> 'test_globals' in test_globals.func_globals or test_globals.func_globals
136    True
137    >>> 'test_name' in test_globals.func_globals or test_globals.func_globals
138    True
139    >>> 'not there' not in test_globals.func_globals or test_globals.func_globals
140    True
141    >>> try: test_globals.func_globals = {}
142    ... except (AttributeError, TypeError): pass
143    ... else: assert 0, 'FAILED'
144    """
145
146def test_reduce():
147    """
148    >>> import pickle
149    >>> pickle.loads(pickle.dumps(test_reduce))()
150    'Hello, world!'
151    """
152    return 'Hello, world!'
153
154def test_method(self):
155    return self
156
157class BindingTest:
158    """
159    >>> BindingTest.test_method = test_method
160    >>> BindingTest.test_method() #doctest:+ELLIPSIS
161    Traceback (most recent call last):
162    TypeError: ...
163    >>> BindingTest().test_method()
164    <BindingTest instance>
165    """
166    def __repr__(self):
167        return '<BindingTest instance>'
168
169
170def codeof(func):
171    if IS_PY3:
172        return func.__code__
173    else:
174        return func.func_code
175
176def varnamesof(func):
177    code = codeof(func)
178    varnames = code.co_varnames
179    if sys.version_info < (2,5):
180        pos = {'a':0, 'x':1, 'b':2, 'l':3, 'm':4}
181        varnames = tuple(sorted(varnames, key=pos.__getitem__))
182    return varnames
183
184def namesof(func):
185    code = codeof(func)
186    names = code.co_names
187    if sys.version_info < (2,5):
188        names = ()
189    return names
190
191def cy_no_arg():
192    l = m = 1
193def cy_one_arg(a):
194    l = m = 1
195def cy_two_args(x, b):
196    l = m = 1
197def cy_default_args(x=1, b=2):
198    l = m = 1
199
200def test_code():
201    """
202    >>> def no_arg(): l = m = 1
203    >>> def one_arg(a): l = m = 1
204    >>> def two_args(x, b): l = m = 1
205    >>> def default_args(x=1, b=2): l = m = 1
206
207    >>> codeof(no_arg).co_argcount
208    0
209    >>> codeof(cy_no_arg).co_argcount
210    0
211    >>> print(codeof(no_arg).co_name)
212    no_arg
213    >>> print(codeof(cy_no_arg).co_name)
214    cy_no_arg
215    >>> namesof(no_arg)
216    ()
217    >>> codeof(cy_no_arg).co_names
218    ()
219    >>> varnamesof(no_arg)
220    ('l', 'm')
221    >>> codeof(cy_no_arg).co_varnames
222    ('l', 'm')
223
224    >>> codeof(one_arg).co_argcount
225    1
226    >>> codeof(cy_one_arg).co_argcount
227    1
228    >>> print(codeof(one_arg).co_name)
229    one_arg
230    >>> print(codeof(cy_one_arg).co_name)
231    cy_one_arg
232    >>> namesof(one_arg)
233    ()
234    >>> codeof(cy_one_arg).co_names
235    ()
236    >>> varnamesof(one_arg)
237    ('a', 'l', 'm')
238    >>> codeof(cy_one_arg).co_varnames
239    ('a', 'l', 'm')
240
241    >>> codeof(two_args).co_argcount
242    2
243    >>> codeof(cy_two_args).co_argcount
244    2
245    >>> namesof(two_args)
246    ()
247    >>> codeof(cy_two_args).co_names
248    ()
249    >>> varnamesof(two_args)
250    ('x', 'b', 'l', 'm')
251    >>> codeof(cy_two_args).co_varnames
252    ('x', 'b', 'l', 'm')
253
254    >>> codeof(default_args).co_argcount
255    2
256    >>> codeof(cy_default_args).co_argcount
257    2
258    >>> namesof(default_args)
259    ()
260    >>> codeof(cy_default_args).co_names
261    ()
262    >>> varnamesof(default_args)
263    ('x', 'b', 'l', 'm')
264    >>> codeof(cy_default_args).co_varnames
265    ('x', 'b', 'l', 'm')
266    """
267
268
269def test_annotations(a: "test", b: "other" = 2, c: 123 = 4) -> "ret":
270    """
271    >>> isinstance(test_annotations.__annotations__, dict)
272    True
273    >>> sorted(test_annotations.__annotations__.items())
274    [('a', 'test'), ('b', 'other'), ('c', 123), ('return', 'ret')]
275
276    >>> def func_b(): return 42
277    >>> def func_c(): return 99
278    >>> inner = test_annotations(1, func_b, func_c)
279    >>> sorted(inner.__annotations__.items())
280    [('return', 99), ('x', 'banana'), ('y', 42)]
281
282    >>> inner.__annotations__ = {234: 567}
283    >>> inner.__annotations__
284    {234: 567}
285    >>> inner.__annotations__ = None
286    >>> inner.__annotations__
287    {}
288    >>> inner.__annotations__ = 321
289    Traceback (most recent call last):
290    TypeError: __annotations__ must be set to a dict object
291    >>> inner.__annotations__
292    {}
293
294    >>> inner = test_annotations(1, func_b, func_c)
295    >>> sorted(inner.__annotations__.items())
296    [('return', 99), ('x', 'banana'), ('y', 42)]
297    >>> inner.__annotations__['abc'] = 66
298    >>> sorted(inner.__annotations__.items())
299    [('abc', 66), ('return', 99), ('x', 'banana'), ('y', 42)]
300
301    >>> inner = test_annotations(1, func_b, func_c)
302    >>> sorted(inner.__annotations__.items())
303    [('return', 99), ('x', 'banana'), ('y', 42)]
304    """
305    def inner(x: "banana", y: b()) -> c():
306        return x,y
307    return inner
308
309
310def add_one(func):
311    "Decorator to add 1 to the last argument of the function call"
312    def inner(*args):
313        args = args[:-1] + (args[-1] + 1,)
314        return func(*args)
315    return inner
316
317@add_one
318def test_decorated(x):
319    """
320    >>> test_decorated(0)
321    1
322    """
323    return x
324
325@add_one
326@add_one
327def test_decorated2(x):
328    """
329    >>> test_decorated2(0)
330    2
331    """
332    return x
333
334
335cdef class TestDecoratedMethods:
336    @add_one
337    def test(self, x):
338        """
339        >>> TestDecoratedMethods().test(0)
340        1
341        """
342        return x
343
344    @add_one
345    @add_one
346    def test2(self, x):
347        """
348        >>> TestDecoratedMethods().test2(0)
349        2
350        """
351        return x
352
353    def test_calls(self, x):
354        """
355        >>> TestDecoratedMethods().test_calls(2)
356        25
357        """
358        return self.test(x) + self.test2(x*10)
359
360
361cdef class TestUnboundMethodCdef:
362    """
363    >>> C = TestUnboundMethodCdef
364    >>> IS_PY2 or (C.meth is C.__dict__["meth"])
365    True
366    """
367    def meth(self): pass
368
369
370class TestUnboundMethod:
371    """
372    >>> C = TestUnboundMethod
373    >>> IS_PY2 or (C.meth is C.__dict__["meth"])
374    True
375    """
376    def meth(self): pass
377
378
379cdef class TestOptimisedBuiltinMethod:
380    """
381    >>> obj = TestOptimisedBuiltinMethod()
382    >>> obj.append(2)
383    3
384    >>> obj.call(2)
385    4
386    >>> obj.call(3, obj)
387    5
388    """
389    def append(self, arg):
390        print(arg+1)
391
392    def call(self, arg, obj=None):
393        (obj or self).append(arg+1)  # optimistically optimised => uses fast fallback method call
394