# mode: run # ticket: t5 # A small number of extra tests checking: # 1) this works correctly with pure-Python-mode decorators - methodmangling_pure.py. # 2) this works correctly with cdef classes - methodmangling_cdef.pyx # 3) with "error_on_unknown_names" - methodmangling_unknown_names.py class CyTest(object): """ >>> cy = CyTest() >>> '_CyTest__private' in dir(cy) True >>> cy._CyTest__private() 8 >>> '__private' in dir(cy) False >>> '_CyTest__x' in dir(cy) True >>> '__x' in dir(cy) False >>> cy._CyTest__y 2 >>> '_CyTest___more_than_two' in dir(cy) True >>> '___more_than_two' in dir(cy) False >>> '___more_than_two_special___' in dir(cy) True """ __x = 1 ___more_than_two = 3 ___more_than_two_special___ = 4 def __init__(self): self.__y = 2 def __private(self): return 8 def get(self): """ >>> CyTest().get() (1, 1, 8) """ return self._CyTest__x, self.__x, self.__private() def get_inner(self): """ >>> CyTest().get_inner() (1, 1, 8) """ def get(o): return o._CyTest__x, o.__x, o.__private() return get(self) class CyTestSub(CyTest): """ >>> cy = CyTestSub() >>> '_CyTestSub__private' in dir(cy) True >>> cy._CyTestSub__private() 9 >>> '_CyTest__private' in dir(cy) True >>> cy._CyTest__private() 8 >>> '__private' in dir(cy) False >>> '_CyTestSub__x' in dir(cy) False >>> '_CyTestSub__y' in dir(cy) True >>> '_CyTest__x' in dir(cy) True >>> '__x' in dir(cy) False """ __y = 2 def __private(self): return 9 def get(self): """ >>> CyTestSub().get() (1, 2, 2, 9) """ return self._CyTest__x, self._CyTestSub__y, self.__y, self.__private() def get_inner(self): """ >>> CyTestSub().get_inner() (1, 2, 2, 9) """ def get(o): return o._CyTest__x, o._CyTestSub__y, o.__y, o.__private() return get(self) class _UnderscoreTest(object): """ >>> ut = _UnderscoreTest() >>> '__x' in dir(ut) False >>> '_UnderscoreTest__x' in dir(ut) True >>> ut._UnderscoreTest__x 1 >>> ut.get() 1 >>> ut._UnderscoreTest__UnderscoreNested().ret1() 1 >>> ut._UnderscoreTest__UnderscoreNested.__name__ '__UnderscoreNested' >>> ut._UnderscoreTest__prop 1 """ __x = 1 def get(self): return self.__x class __UnderscoreNested(object): def ret1(self): return 1 @property def __prop(self): return self.__x class C: error = """Traceback (most recent call last): ... TypeError: """ __doc__ = """ >>> instance = C() Instance methods have their arguments mangled >>> instance.method1(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> instance.method1(_C__arg=1) 1 >>> instance.method2(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> instance.method2(_C__arg=1) 1 Works when optional argument isn't passed >>> instance.method2() None Where args are in the function's **kwargs dict, names aren't mangled >>> instance.method3(__arg=1) # doctest: 1 >>> instance.method3(_C__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... KeyError: Lambda functions behave in the same way: >>> instance.method_lambda(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> instance.method_lambda(_C__arg=1) 1 Class methods - have their arguments mangled >>> instance.class_meth(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> instance.class_meth(_C__arg=1) 1 >>> C.class_meth(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> C.class_meth(_C__arg=1) 1 Static methods - have their arguments mangled >>> instance.static_meth(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> instance.static_meth(_C__arg=1) 1 >>> C.static_meth(__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} >>> C.static_meth(_C__arg=1) 1 Functions assigned to the class don't have their arguments mangled >>> instance.class_assigned_function(__arg=1) 1 >>> instance.class_assigned_function(_C__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} Functions assigned to an instance don't have their arguments mangled >>> instance.instance_assigned_function = free_function2 >>> instance.instance_assigned_function(__arg=1) 1 >>> instance.instance_assigned_function(_C__arg=1) # doctest: +IGNORE_EXCEPTION_DETAIL {error} Locals are reported as mangled >>> list(sorted(k for k in instance.get_locals(1).keys())) ['_C__arg', 'self'] """.format(error=error) def method1(self, __arg): print(__arg) def method2(self, __arg=None): # __arg is optional print(__arg) def method3(self, **kwargs): print(kwargs['__arg']) method_lambda = lambda self, __arg: __arg def get_locals(self, __arg): return locals() @classmethod def class_meth(cls, __arg): print(__arg) @staticmethod def static_meth(__arg, dummy_arg=None): # dummy_arg is to mask https://github.com/cython/cython/issues/3090 print(__arg) def free_function1(x, __arg): print(__arg) def free_function2(__arg, dummy_arg=None): # dummy_arg is to mask https://github.com/cython/cython/issues/3090 print(__arg) C.class_assigned_function = free_function1 __global_arg = True _D__arg1 = None _D__global_arg = False # define these because otherwise Cython gives a compile-time error # while Python gives a runtime error (which is difficult to test) def can_find_global_arg(): """ >>> can_find_global_arg() True """ return __global_arg def cant_find_global_arg(): """ Gets _D_global_arg instead >>> cant_find_global_arg() False """ class D: def f(self): return __global_arg return D().f() class CMultiplyNested: def f1(self, __arg, name=None, return_closure=False): """ >>> inst = CMultiplyNested() >>> for name in [None, '__arg', '_CMultiplyNested__arg', '_D__arg']: ... try: ... print(inst.f1(1,name)) ... except TypeError: ... print("TypeError") # not concerned about exact details ... # now test behaviour is the same in closures ... closure = inst.f1(1, return_closure=True) ... try: ... if name is None: ... print(closure(2)) ... else: ... print(closure(**{ name: 2})) ... except TypeError: ... print("TypeError") 2 2 TypeError TypeError TypeError TypeError 2 2 """ class D: def g(self, __arg): return __arg if return_closure: return D().g if name is not None: return D().g(**{ name: 2 }) else: return D().g(2) def f2(self, __arg1): """ This finds the global name '_D__arg1' It's tested in this way because without the global Python gives a runtime error and Cython a compile error >>> print(CMultiplyNested().f2(1)) None """ class D: def g(self): return __arg1 return D().g() def f3(self, arg, name): """ >>> inst = CMultiplyNested() >>> inst.f3(1, None) 2 >>> inst.f3(1, '__arg') # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... TypeError: >>> inst.f3(1, '_CMultiplyNested__arg') 2 """ def g(__arg, dummy=1): return __arg if name is not None: return g(**{ name: 2}) else: return g(2) def f4(self, __arg): """ >>> CMultiplyNested().f4(1) 1 """ def g(): return __arg return g() def f5(self, __arg): """ Default values are found in the outer scope correcly >>> CMultiplyNested().f5(1) 1 """ def g(x=__arg): return x return g() def f6(self, __arg1): """ This will find the global name _D__arg1 >>> print(CMultiplyNested().f6(1)) None """ class D: def g(self, x=__arg1): return x return D().g() def f7(self, __arg): """ Lookup works in generator expressions >>> list(CMultiplyNested().f7(1)) [1] """ return (__arg for x in range(1)) class __NameWithDunder: """ >>> __NameWithDunder.__name__ '__NameWithDunder' """ pass class Inherits(__NameWithDunder): """ Compile check that it can find the base class >>> x = Inherits() """ pass def regular_function(__x, dummy=None): # as before, dummy stops Cython creating a 1 arg, non-keyword call return __x class CallsRegularFunction: def call(self): """ >>> CallsRegularFunction().call() 1 """ return regular_function(__x=1) # __x shouldn't be mangled as an argument elsewhere