8 minute read

Learning Resources

Websites:

Books:

  • Python Crash Course: cơ bản, dễ hiểu
  • Automate the Boring Stuff with Python: cơ bản, dễ hiểu, nhiều ví dụ về ứng dụng tự động hoá của Python.

Advantages of Python:

  • Readability
  • Easy to learn
  • Huge community and supports.

Why Python is slow ?

  • “It’s the GIL (Global Interpreter Lock)”
  • “It’s because its interpreted and not compiled”
  • “It’s because its a dynamically typed language”

Reference: https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b

Some good practice tips

The code explains itself.

a = 10_00_000

a, b, *_ = (1, 2, 3, 4)

# Use:
a = dict.get('key', '')
# instead of 
a = '' if 'key' in dict else dict['key']


x = lambda a, b : a * b
# x(5, 6) -> 30

x = lambda a : a*3 + 3
# x(3) -> 12



# map() function
def square_it_func(a):
    return a * a
x = map(square_it_func, [1, 4, 7])
# print(x) -> '[1, 16, 49]'

def multiplier_func(a, b):
    return a * b
x = map(multiplier_func, [1, 4, 7], [2, 5, 8])
# print(x) -> '[2, 20, 56]'



# filter() function
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

# Function that filters out all numbers which are odd
def filter_odd_numbers(num):
    return True if num % 2 == 0 else False

filtered_numbers = filter(filter_odd_numbers, numbers)
# -> [2, 4, 6, 8, 10, 12, 14] 

Itertools: The Python Itertools module is a collection of tools for handling iterators. An iterator is a data type that can be used in a for loop including lists, tuples, and dictionaries.

from itertools import *

# Easy joining of two lists into a list of tuples
for i in izip([1, 2, 3], ['a', 'b', 'c']):
    print i
# ('a', 1)
# ('b', 2)
# ('c', 3)

# The count() function returns an interator that 
# produces consecutive integers, forever. This 
# one is great for adding indices next to your list 
# elements for readability and convenience
for i in izip(count(1), ['Bob', 'Emily', 'Joe']):
    print i
# (1, 'Bob')
# (2, 'Emily')
# (3, 'Joe')    

# The dropwhile() function returns an iterator that returns 
# all the elements of the input which come after a certain 
# condition becomes false for the first time. 
def check_for_drop(x):
    print 'Checking: ', x
    return (x > 5)

for i in dropwhile(should_drop, [2, 4, 6, 8, 10, 12]):
    print 'Result: ', i

# Checking: 2
# Checking: 4
# Result: 6
# Result: 8
# Result: 10
# Result: 12


# The groupby() function is great for retrieving bunches
# of iterator elements which are the same or have similar 
# properties

a = sorted([1, 2, 1, 3, 2, 1, 2, 3, 4, 5])
for key, value in groupby(a):
    print(key, value), end=' ')
    
# (1, [1, 1, 1])
# (2, [2, 2, 2]) 
# (3, [3, 3]) 
# (4, [4]) 
# (5, [5]) 

Generators: Generator functions allow you to declare a function that behaves like an iterator, i.e. it can be used in a for loop. -> simplifies code and memory efficient than loop.

# a generator that yields items instead of returning a list
def f
   num = 0
   while num < n:
       yield num
       num += 1

sum_of_first_n = sum(firstn(1000000))

We can turn a list comprehension into a generator expression by:

# list comprehension
doubles = [2*n for n in range(50)]

# generator expressino
doubles = (2*n for n in range(50))

Command line arguments

sys.argv

return a list of command line arguments

sys Module

sys.executable

return string: absolute path to the Python interpreter.

sys.exit()

exit from Python, can take an optional argument (exit status).

sys.exit(0)

sys.platform

Use to check what OS is currently in used.

Assert Statement

Assertion condition:

  • True: program continue to run
  • False: Assertion stops the program and gives AssertionError. Syntax:
    assert <condition>
    assert <condition>, <error message>
    

Documenting your code with Docstrings

“Code is more often read than written.” - Guido Van Rossum

Python Docstrings giúp ta thêm phần documentation cho các module, function, class và method.

Khác với comments, docstring phải mô tả “làm cái gì”.

Có thể sử dụng Google style guide (dễ nhìn nhất) làm docstring format.

Đọc thêm về

Nguyên tắc xây dựng docstrings

  • Được khởi tạo bởi multiline comments “”” ngay dưới class, method, function
  • Mọi function đều nên có docstring.
  • Dòng đầu tiên nên là short description.
  • Mỗi dòng nên cách ra để dễ nhìn.

Ví dụ

def my_function():
    """Do nothing, but document it.

    Args:
        n: the number to get the square root of.
    Returns:
        the square root of n.
    Raises:
        TypeError: if n is not a number.
        ValueError: if n is negative.
    """
    pass
>>> help(mymodule.MyClass)
>>> help(mymodule.my_function)
>>> print(my_function.__doc__)

Tham khảo

Type Hints

Python có tính chất dynamically-typed (kiểu biến không cần xác định rõ). Type Hints (thư viên typing) được tạo ra để giúp giải quyết vấn đề này.

  1. Đọc code dễ dàng hơn
def send_request(request_data : Any,
                 headers: Optional[Dict[str, str]],
                 user_id: Optional[UserId] = None,
                 as_json: bool = True):
    ...

def hello(name: str) -> None:
    print(f'hello {name}')
  1. IDE hiểu tốt hơn:

    Khi biết được kiểu biến thì IDE sẽ đưa ra được gợi ý chính xác hơn, và đưa ra cảnh báo nếu sai kiểu.

Tham khảo

Python CLI

Để hiểu cách sử dụng argparse thì bạn hãy đọc 2 bài đầu tiên dưới phần Reference, nhất là bài của RealPython.

Mình có viết ví dụ về cách sử dụng tại repo minhdq99hp/sample-code

Có một số thư viện hỗ trợ việc viết CLI cho python (VD: click), trong đó argparse là một cách standard nhất để viết.

  • An argument is a single part of a command line, delimited by blanks.
  • An option is a particular type of argument (or a part of an argument) that can modify the behavior of the command line.
  • A parameter is a particular type of argument that provides additional information to a single option or command.

argparse

Đọc bài viết này.

Ngoài ra, mình cũng viết file argparse_example.py tại repo minhdq99hp/sample-code

Định nghĩa

The subprocess mudle allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes.

Có các hàm:

  • call
  • check_call
  • check_output

Về bản chất, subprocess sử dụng class Popen để tạo và quản lý process. Khi có nhu cầu sử dụng phức tạp hơn thì phải sử dụng Popen. VD: kill process.

Code tham khảo mình để trong repo minhdq99hp/sample-code.

References

  • https://docs.python.org/2/library/subprocess.html

Python High Performance

1. Benchmarking and Profiling

Phát hiện phần bị chạy chậm trong phần mềm (bottleneck) là một công việc quan trọng khi muốn tối ưu code.

Profiling là một kỹ thuật giúp xác định được bottlenecks. Profiler là một chương trình chạy code và quan sát xem mỗi function chạy mất bao lâu, giúp xác định ra vị trí chạy chậm.

Khi thiết kế chương trình hiệu suất cao (performance-intensive), bước đầu tiên là viết code chưa cần quan tâm đến tối ưu vội.

“Premature optimization is the root of all evil” - Donald Knuth

Một số nguyên tắc cần phải nhớ khi tối ưu code:

  • Make it run: đảm bảo code chạy ra kết quả đúng trước khi tối ưu.
  • Make it right: đảm bảo thiết kế chương trình chắc chắn, có hệ thống.
  • Make it fast: sau đó, tối ưu những phần chạy chậm trước.

Writing tests and benchmarks

  • Test: Trong quá trình tối ưu, chún ta sẽ phải viết lại code, vì thế phải tạo ra các hàm test để tránh mất thời gian vào broken code.
  • Benchmark: Tạo hàm benchmark để đánh giá performance của code.

Timing your benchmarks

Cách 1: dùng lệnh time của Unix.

time python simul.py

sẽ trả về thông tin:

real    0m7.998s
user    0m8.094s
sys     0m0.221s

Với:

  • real: Thời gian thực tế để chạy chương trình.
  • user: Thời gian của các CPUs để tính toán
  • sys: Thời gian của các CPUs để thực hiện các system-related tasks. VD: memory allocation.

Chú ý:

  • user+sys có thể nhiều hơn real do đa nhân chạy song song.
  • Để đo chính xác hơn thì benchmark nên chạy đủ lâu, để quá trình setup ngắn hơn execution.

Cách 2: Module timeit

Module timeit chạy đoạn code trong vòng lặp n lần, rồi thực hiện quá trình này r lần (thường r là 3) rồi trả về giá trị tốt nhất. Vì vậy, timeit phù hợp để tính toán thời gian chính xác một phần nhỏ của chương trình.

Chú ý:

  • timeit có thể dùng dưới dạng module hoặc magic command trong Jupyter Notebook.

Finding bottlenecks with cProfile

python -m cProfile simul.py

se in ra danh sách thời gian chạy của từng phần trong code.

cProfile output có 5 cột: ncalls, tottime, cumtime, percall, filename:lineno:

Metric quan trọng nhất là tottime, thời gian chạy thực tế trong function body, không bao gồm sub-calls.

KCachegrind là một công cụ GUI hỗ trợ profiling. Dùng pyprof2calltree để convert output của cProfile sáng KCachegrind… (đọc thêm)

Profile line by line with line_profiler

Sau khi xác định được function cần tối ưu thì sử dụng line_profiler để check xem thời gian chạy của các dòng. (đọc thêm)

Optimizing Code

Có một số cách để tối ưu, cách tốt nhất là thay đổi thuật toán.

Module dis (disassemble): liệt kê các instructions.

Profiling memory usage with memory_profiler

Tương tự, memory_profiler thống kê lượng bộ nhớ bị chiếm qua từng dòng. (đọc thêm)

Cách để tối ưu memory: sử dụng __slot__

Performance tuning tips for pure Python code

Khi tối ưu code Python, tốt nhất nên nhìn vào standard library, nơi chứa nhiều modules đã được tối ưu, viết bằng C.

Module collections cung cấp nhiều data containers phù hợp để giải quyết một số trường hợp.

2. Fast Array Operations with Numpy

Reference

  • Book: Python High Performance Programming

Contributors

Comments