Python notes

  1. How to check the version and path of python?
     import sys
     print(sys.version) #python version
     print(sys.executable) #python path
    
  2. How to speed up calculations which cannot avoid using ‘for’ loop?
    Numba may be a good option.
     from numba import jit
     @jit(nopython=True)
     def my_function(): #expect to take numpy arrays as arguments
    
  3. Trick of unpacking
    Use underscore (_) for unneeded values and use asterisk (*) for multiple values.

  4. What does ‘python -m xx’ do?
    Call module/script ‘xx’ from sys.path. Following ‘xx’ one can provide arguments for this module/script.

  5. How to create different variable names while in a loop?
    Use a dict or list. Not a good idea to use exec/globals().

  6. Time running time in python
    There are different approaches, using modules such as time, datetime, timeit. Following is one of the approaches.
     import timeit
     start = timeit.default_timer()
     #Your statements here
     stop = timeit.default_timer()
     print('Time: ', stop - start)
    
  7. What is the benefit of using generator? How to use it?
    Save memory and time. Fetch value only when needed.
     def function_name(nums):
         for i in nums:
             yield i*i
     generator=function_name(nums)
     # or using list comprehension
     generator=(i*i for i in nums)
    
  8. What is the LEGB rule in terms of scope? Some common mistakes when working with functions.
    LEGB stands for Local, Enclosing, Global, Builtin, which determines the order in which Python looks up names. Following are some common mistakes.
     # Case 1: reference before assignment
     x=1
     def fun1():
       x=x
       print(x)
     # fun1 is wrong since x is considered as local variable because of assignment statement. fun2 works.
     def fun2():
       print(x)
     # Case 2: nexted definition != nested function call
     def fun3():
       print(x)
     def fun4():
       x=1
       fun3()
     fun4() #this will return error since there is no x outside the scope of def fun3()
     # Case 3: improper use of default mutable argument
     def fun5(i, arg=[]):
       arg.append(i)
       return arg
     # In this example, arg will grow everytime you call fun5(i) since the defalut argument is only defined when the    function is defined and it is mutable. Following is the correct way.
     def fun6(i, arg=None):
       if arg==None:
         arg=[]
       arg.append(i)
       return arg
    
  9. What does *arg, **kwarg do?
    When defined in functions, *arg and **kwarg allow a vairable number of arguments and a variable number of key-value pairs to pass to a function. When used in calling a function, * and ** unpack a tuple and a dictionary, respectively.

  10. How to set path?
    Hardcoding is not good. Following are two potential ways.
    #using os.path
    import os.path
    folder_ = os.path.join("folderA", "folderB")
    file_ = os.path.join(folder_, "fileC")
    #using pathlib
    from pathlib import Path
    folder_ = Path("folderA/folderB/")
    file_ = folder_/"fileC"
    
  11. How to import modules not in current directory?
    One way is to add module path to sys.path and the other way is to add PYTHONPATH to system variable.

  12. When to use ‘is’, ‘is not’ and when to use ‘==’, ‘!=’
    When comparing the values of two objects, using == and !=. When comparing whether two variables refer to the same object in memory, use is and is not. The main use for is and is not is to compare with None, which is interned at a specific address. Other objects that are interned by default are True, False, small integers and simple strings.

  13. Why does max() sometimes return nan and sometimes ignores it?
    The reason is that max() works by taking the first value as the “max seen so far”, and then checking each other value to see if it is bigger than the “max seen so far”. But nan is defined so that comparisons with it always return False. Max methods from different modules behave differently, so be careful.

  14. What is the difference between iterable, iterator and generator?
    Iterator is a subclass of iterable, and generator is a subclass of iterator. Iterable has __iter__ method and iterator/generator has __next__ method.

  15. Syntax of decorator
    #using function decorator
    def decorator_function(original_function):
        def wrapper_function(*args, **kwargs):
            print('wrapper executed this before {}'.format(original_dunction.__name__))
            return original_function(*args, **kwargs)
        return wrapper_function
    #then use one of the two
    def original_function():
        pass
    original_function=decorator_function(original_function)
    
    @decorator_function
    def original_function():
        pass
    #using class decorator
    class decorator_class(object):
        def __init__(self, original_function):
            self.original_function = original_function
        def __call__(self, *args, **kwargs):
            print('call method before {}'.format(self.original_function.__name__))
            self.original_function(*args, **kwargs)
    #then use one of the two
    def original_function():
        pass
    original_function=decorator_class(original_function)
    
    @decorator_class
    def original_function():
        pass
    
  16. How to customize comparison in sorted() or list.sort()
    #to customize objects to compare, use function(such as lambda) or operator module (examples below)
    from operator import itemgetter, attrgetter
        sorted(tuple_to_sort, key=itemgetter(2))
        sorted(object_to_sort, key=attrgetter('name'))
    #to change 1-to-1 comparison rules, either define the rules in a class (either define a class by building from scratch or decorating a function).
    class reverse_numeric_class:
        def __init__(self, obj):
            self.obj=obj
        def __lt__(self, other):
            return self.obj>other.obj
    sorted(nums, key=reverse_numeric_class)
        
    from functools import cmp_to_key
    @cmp_to_key
    def reverse_numeric_function(x, y):
        return y - x
    sorted(nums, key=reverse_numeric_function)
    
  17. Utilities from functools package
    #reduce
    from functools import reduce #reduce(function, iterable[, initializer])
    d={"a":{"b":{"c":4}}}
    l=["a","b","c"]
    from operator import getitem #getitem(x,y)->x[y]
    reduce(getitem, l, d) #return 4
    #lru_cache
    from functools import lru_cache
    @lru_cache(None) #memorization
    def my_function():
    
  18. How to make a list of multiple same items
    #to make each element independent (list, dic, set)
    a=[[] for i in range(5)]
    a[0].append(1) # return [[1], [], [], [], []]
    a[0] is a[1] # return False
    #to make copies of same object
    a=[[]]*5
    a[0].append(1) # return [[1], [1], [1], [1], [1]]
    a[0] is a[1] # return True
    #for immutables, both approaches work the same
    
  19. Shallow copy and deep copy
    import copy
    a=[[1,2,3],[4,5,6]]
    #the followings are shallow copies (the top level)
    a[:], a[:][:], a.copy(), copy.copy(a)
    #the following are deep copies
    [ele[:] for ele in a] #works for this double-level case
    copy.deepcopy(a) #applies to any level
    
  20. How to break out of multiple loops?
    #refactor the nested loop into a function
    def fun():
        for i in range(n):
            for j in range(n):
                if A[i][j]:
                    #do something
                    return
    #use for-else clause
    for i in range(n):
        for j in range(n):
            if A[i][j]:
                #do something
                break
        else:
            continue
        break