Function
Pass by reference
            # pass by reference
def f2(m, im):
    print('----------------Inside function-------------')
    print(id(m), m) # 140426914615040 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    print(id(im), im) # 140426908592720 10
    m.append(100);
    im = 100;
    print(id(m), m) # 140426914615040 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
    print(id(im), im) # 140426908784080 100
    print('---------------Leave function--------------')

a = 10; # 38097024 10
b = list(range(10)) # 13977378998840, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,]
print(id(a), a) # 140426908592720 10
print(id(b), b) # 140426914615040 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

f2(b, a);
print(id(a), a) # 140426908592720 10
print(id(b), b) # 140426914615040 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
			
Default arguments
def f1(p1, p2 = 10):
    print(p1, p2)

f1(1) # 1 10
			
Keyword arguments
def f3(p1 = 1, p2 = 2):
    print(p1, p2)
 
f3(p2 = 10, p1 = 100) # 100 10

# use keyword-only arguments
# * separate positional/keyword arguments and keyword-only arguments
def f2(p1, p2 = 10, *, p3 = 100):
    print(p1, p2, p3)

# f2(1, 2, 1000) # error, pass positional argument to a keyword-only argument
f2(1, 2, p3 = 1000)
			
Variable-length arguments
def f4(*vartuple):
    print('Lenght: ', len(vartuple)) # 4
    print(type(vartuple), vartuple) # (1, 2, 3, 'end'), vartuple is a tuple
    for i in vartuple:
        print(i)
 
f4(1, 2, 3, 'end')
			
* in Function Calls
  • expands list or tuple into separate elements
  • def f(a, b):
        print(a, b)
    
    l = [10, 20] # singularize list or tuple
    f(*l) # 10 20
    			
    Arbitary Key Parameters
  • pass parameters and their values as a dict to function
  • def f(**args):
        print(args)
        for k in args.keys():
            print(k, args[k])
    
    f(inFile = 'input', outFile = 'output')
    			
    ** in Function Calls
  • pass keyworded variables as a dict to function
  • def f(**args):
        print(args)
        for k in args.keys():
            print(k, args[k])
    
    d = {'inFile':'input', 'outFile':'output'} # singularize dict
    f(**d)
    			
    def f(inFile, outFile):
        print(inFile, outFile)
    
    d = {'inFile':'input', 'outFile':'output'}
    
    f(**d)
                
    Lambda
    #!/usr/bin/python
     
    #lambda
    sum = lambda arg1, arg2: arg1+arg2
     
    print(sum(10, 20)) #30
     
    #map
    def t(arg):
        return arg*10
     
    l = [1, 2, 3, 4]
    r = map(t, l) # [10, 20, 30, 40], use function
    
    r = map(lambda x: x*10, l) #[10, 20, 30, 40], using lambda
     
    #filter
    r = filter(lambda x: x%2 == 0, l) # [2, 4]
     
    #reduce
    from functools import reduce
    r = reduce(lambda x, y: x+y, l) # 10
    			
    Return
  • return multiple values, which are saved in a tuple
  • def getNums():
        return 1, 2
     
    a, b = getNums()
    print(a, b) # 1, 2
     
    _, b = getNums() # use _ to hold a place
    print(b) # 2
     
    t = getNums()
    print(t) # (1, 2), t is a tuple
    			
    def getNum_1():
        return 1 # return an integer
    
    def getNum_2():
        return # return nothing
    
    def getNum_3():
        return None # return None
    
    def getNum_4():
        pass # no return will return None
    
    def main():
        if getNum_1() is None:
            print(getNum_1()) # not print
        if getNum_2() is None:
            print(getNum_2()) # None
        if getNum_3() is None:
            print(getNum_3()) # None
        if getNum_4() is None:
            print(getNum_4()) # None
    
    if __name__ == '__main__':
        main()
    			
    Recursive
    # recursive
    def fib(n):
        """Doc String
        Args:
            n (int), integer number
        Return:
            int, fib number
        """
        if n <= 1:
            return 1
        else:
            return n*fib(n-1)
    
    print(fib(10))
    			
    Functools
  • functools.partial, is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature
  • from functools import partial
    
    def f(a, b):
        print(a, b)
    
    p = partial(f, b = 10)
    p(1) # 1 10, pass 1 to a, p() only need one argument
    			
  • functools.update_wrapper, to allow access to the original function for introspection and other purposes
  • from functools import partial, update_wrapper
    
    def f(a, b):
        """ Docstring for f ..."""
        print(a, b)
    
    p = partial(f, b = 10)
    p(1) # 1 10, pass 1 to a, p() only need one argument
    #print(p.__doc__, p.__name__) # partial object does not have __name__ or __doc__ attributes by default
    
    update_wrapper(p, f) # copies or adds attributes from the original function to the partial object
    print(p.__doc__, p.__name__)
    			
  • functools.partialmethod, returns a callable ready to be used as an unbound method of an object
  • from functools import partialmethod
    
    def getInfo(self, a, b):
        """a partial function in class"""
        print('  called standalone with:', (self, a, b))
        if self is not None:
            print('  self.attr =', self.attr)
    
    
    class MyClass:
        "Demonstration class for functools"
    
        def __init__(self):
            self.attr = 'instance attribute'
    
        method = partialmethod(getInfo, b = 10)
    
    c = MyClass()
    c.method(1)
    			
  • functools.wraps, updating the properties of a wrapped callable when used in a decorator
    # use decorator
    
    def d(f):
        def decorated(a=10, b=100):
            return f(a, b=b)
        return decorated
    
    @d
    def f(a, b):
        """ Docstring for f ..."""
        print(a, b)
    
    dwrapper = d(f)
    dwrapper() # 10 100
    print(dwrapper.__doc__, dwrapper.__name__) # None decorated
    
    f(b = 1) # 10 1
    			
    # use wraps() to define a decorator
    
    from functools import wraps
    
    def d(f):
        @wraps(f)
        def decorated(a=10, b=100):
            return f(a, b=b)
        return decorated
    
    def f(a, b):
        """ Docstring for f ..."""
        print(a, b)
    
    dwrapper = d(f)
    dwrapper() # 10 100
    print(dwrapper.__doc__, dwrapper.__name__) # Docstring for f ... f
    			
  • functools.lru_cache, wraps a function in a least-recently-used cache, subsequent calls with the same arguments will fetch the value from the cache instead of calling the function
  • from functools import lru_cache
    
    @lru_cache()
    def f(a, b):
        print('%d, %d' % (a, b))
        return a * b
    
    f(1, 10)
    print(f.cache_info()) # print out the cache information
    
    f.cache_clear() # clear cache
    			
  • Six rich class comparison functions, __lt__, __le__, __gt__, __ge__, __eq__, __ne__
  • class Vehicle(object):
        def __init__(self, year: int):
            self._year = year
    
        @property
        def year(self) -> int:
            return self._year
    
        @year.setter
        def year(self, y : int) -> None:
            self._year = y
    
        def __eq__(self, other):
            return self._year == other._year
    
        def __lt__(self, other):
            return self._year < other._year
    
    v1 = Vehicle(1999)
    v2 = Vehicle(2022)
    
    print(v1.__lt__(v2)) # True
    print(v1.__le__(v2)) # NotImplemented
    print(v1.__gt__(v2)) # NotImplemented
    print(v1.__ge__(v2)) # NotImplemented
    print(v1.__eq__(v2)) # False
    print(v1.__ne__(v2)) # True
                
  • functools.total_ordering Provides rich class comparison methods that help in comparing classes without explicitly defining the six rich comparison function
  • At least one of the comparison methods must be defined from lt(less than), le(less than or equal to), gt(greater than) or ge(greater than or equal to)
  • The eq function must also be defined
  • from functools import total_ordering
    
    @total_ordering
    class Vehicle(object):
        def __init__(self, year: int):
            self._year = year
    
        @property
        def year(self) -> int:
            return self._year
    
        @year.setter
        def year(self, y : int) -> None:
            self._year = y
    
        def __eq__(self, other):
            return self._year == other._year
    
        def __lt__(self, other):
            return self._year < other._year
    
    v1 = Vehicle(1999)
    v2 = Vehicle(2022)
    
    print(v1.__lt__(v2)) # True
    print(v1.__le__(v2)) # NotImplemented
    print(v1.__gt__(v2)) # NotImplemented
    print(v1.__ge__(v2)) # NotImplemented
    print(v1.__eq__(v2)) # False
    print(v1.__ne__(v2)) # True
                
    Overload
  • Python does not have real function overload, either use overloading module or isinstance to check and handle different type of inputs
  • def info(m):
        if isinstance(m, str):
            return 'Str: '+m
        elif isinstance(m, int):
            return 'Int: '+str(m)
        else:
            raise TypeError('Take str or int types ...')
    
    print(info(100))
    print(info('Hello'))
    print(info(3.14)) # raise error
    			
    # use default value to work as overloaded function
    def info(a, b = None):
        if b is None:
            return a
        return a+b
    
    print(info(1, 2)) # 3
    print(info(1)) # 1
    			
    Function Attribute
    def func():
        print('Hello World!')
    
    # create an attribute for the function
    func.record = 'initial record'
    
    #['__annotations__', ..., __str__', '__subclasshook__', 'record']
    print(dir(func))
    
                
    def func():
        func.call += 1
        print('Hello World!')
    
    # count how many times the function is called
    func.call = 0
    
    func()
    func()
    
    print(func.call) # 2
                
    Reference