Những góc khuất thú vị về Python mà Developer thường bỏ qua

Những góc khuất thú vị về Python mà Developer thường bỏ qua
Please wait 0 seconds...
Scroll Down and click on Go to Link for destination
Congrats! Link is Generated
Những góc khuất thú vị về Python mà Developer thường bỏ qua
Những góc khuất thú vị về Python mà Developer thường bỏ qua

Python, với cú pháp đơn giản và cộng đồng lớn, là một trong những ngôn ngữ lập trình phổ biến nhất hiện nay. Tuy nhiên, ngay cả những lập trình viên Python dày dặn kinh nghiệm cũng có thể bỏ qua một số khía cạnh sâu sắc của ngôn ngữ này. Bài viết này sẽ khám phá một số kiến thức về bản chất của Python mà nhiều developer thường bỏ qua, giúp bạn có cái nhìn toàn diện hơn về ngôn ngữ này.

Cấp độ thấp của Python

  • CPython và VM: Mặc dù Python được biết đến với tính dễ đọc và trừu tượng cao, nhưng việc hiểu cách CPython, trình thông dịch Python chính thức, hoạt động và cách nó tương tác với Virtual Machine (VM) có thể giúp bạn tối ưu hóa mã và khắc phục sự cố hiệu quả hơn.
  • Bytecode: Tìm hiểu về bytecode, dạng trung gian giữa mã nguồn python mà mã máy, sẽ giúp bạn hiểu rõ hơn quá trình thực thi chương trình và cũng như cách các công cụ như PyPy và CPython hoạt động.

Python là một ngôn ngữ "pass-by-object-reference"

  • Khi bạn truyền biến vào hàm, Python không truyền giá trị hay địa chỉ, mà truyền reference đến object.
  • Điều này giải thích tại sao list, dict có thể bị thay đổi trong hàm, còn string hay number thì lại không.
def modify_list(lst):
    lst.append(4)  # lst reference cùng object với original list
    lst = [7,8,9]  # lst giờ reference tới một list mới
    lst.append(10) # thay đổi list mới, không ảnh hưởng original

original = [1,2,3]
modify_list(original)
print(original)  # [1,2,3,4] - chỉ append(4) affect original

Tất cả mọi thứ trong Python đều là object

  • Ngay cả hàm cũng là object của class function
  • Class cũng là object của class type
  • Bạn có thể gán hàm cho biến, truyền hàm vào hàm khác
# Functions là objects
def say_hello(name):
    return f"Hello {name}"

# Có thể gán function cho biến
greet = say_hello
print(greet("Alice"))  # "Hello Alice"

# Function có attributes
say_hello.custom_attr = "I'm an attribute"
print(say_hello.custom_attr)

# Function trong list
function_list = [len, sum, str.upper]
for func in function_list:
    print(func.__name__)  # in ra tên function

# First-class functions
def apply_operation(func, value):
    return func(value)

print(apply_operation(len, "hello"))  # 5
print(apply_operation(str.upper, "hello"))  # "HELLO"

# Classes là objects của type
class MyClass:
    pass

print(type(MyClass))  # <class 'type'>
print(isinstance(MyClass, type))  # True

# Tạo class động bằng type
DynamicClass = type('DynamicClass', (), {
    'x': 1,
    'say_hi': lambda self: "Hi!"
})

obj = DynamicClass()
print(obj.x)  # 1
print(obj.say_hi())  # "Hi!"

# Methods là objects
class Example:
    def method(self):
        pass

print(Example.method)  # unbound method
print(Example().method)  # bound method

# Modules là objects
import math
print(type(math))  # <class 'module'>

# Even types là objects
print(type(int))  # <class 'type'>
print(type(type))  # <class 'type'>

Global Interpreter Lock (GIL)

  • GIL giới hạn chỉ cho phép một thread Python chạy tại một thời điểm
  • Điều này ảnh hưởng đến hiệu năng của chương trình đa luồng trên CPU đa nhân
  • Tuy nhiên GIL không ảnh hưởng đến I/O bound tasks
import threading
import time
from multiprocessing import Process

# CPU-bound task bị ảnh hưởng bởi GIL
def cpu_heavy(n):
    for i in range(n):
        _ = i ** 2

# So sánh single vs multi-threading cho CPU-bound task
def single_thread():
    start = time.time()
    cpu_heavy(10**7)
    cpu_heavy(10**7)
    return time.time() - start

def multi_thread():
    start = time.time()
    t1 = threading.Thread(target=cpu_heavy, args=(10**7,))
    t2 = threading.Thread(target=cpu_heavy, args=(10**7,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    return time.time() - start

print(f"Single thread time: {single_thread()}")
print(f"Multi thread time: {multi_thread()}")  # Không nhanh hơn nhiều vì GIL

# I/O-bound task không bị ảnh hưởng nhiều bởi GIL
def io_heavy():
    time.sleep(1)  # Giả lập I/O operation

def single_io():
    start = time.time()
    io_heavy()
    io_heavy()
    return time.time() - start

def multi_io():
    start = time.time()
    t1 = threading.Thread(target=io_heavy)
    t2 = threading.Thread(target=io_heavy)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    return time.time() - start

print(f"Single I/O time: {single_io()}")  # ~2 seconds
print(f"Multi I/O time: {multi_io()}")    # ~1 second - nhanh hơn!

# Multiprocessing để bypass GIL
def multi_process():
    start = time.time()
    p1 = Process(target=cpu_heavy, args=(10**7,))
    p2 = Process(target=cpu_heavy, args=(10**7,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    return time.time() - start

print(f"Multi process time: {multi_process()}")  # Nhanh hơn nhờ bypass GIL

# Tác động của GIL đến async programming
import asyncio

async def async_io():
    await asyncio.sleep(1)  # Không block GIL

async def main():
    tasks = [async_io() for _ in range(2)]
    await asyncio.gather(*tasks)

# Chạy concurrent I/O operations với asyncio
start = time.time()
asyncio.run(main())
print(f"Async I/O time: {time.time() - start}")  # ~1 second

Name binding và namespace

  • Khi bạn gắn giá trị cho biến, Python thực ra đang bind tên biến với một object
  • Python có các namespace khác nhau: local, enclosing, global, built-in (LEGB rule)
  • Điều này ảnh hưởng đến cách Python tìm kiếm biến
x = 10  # global namespace

def outer():
    x = 20  # enclosing namespace
    def inner():
        x = 30  # local namespace
        print(f"Local x: {x}")  # 30
    inner()
    print(f"Enclosing x: {x}")  # 20

print(f"Global x: {x}")  # 10

Khi muốn modify biến global/enclosing:

x = 0
def modify():
    global x  # declare để modify global
    x += 1

def outer():
    y = 0
    def inner():
        nonlocal y  # declare để modify enclosing
        y += 1

Generator không lưu toàn bộ sequence trong bộ nhớ

  • Generator tạo ra các giá trị theo yêu cầu (lazy evaluation)
  • Rất hiệu quả khi làm việc với dữ liệu lớn
  • Comprehension có thể generator bằng cách dùng ()
# Generator function
def number_gen(n):
    i = 0
    while i < n:
        yield i
        i += 1

# Generator expression
squared = (x*x for x in range(1000000))  # không tốn memory

# Generator với send()
def counter():
    i = 0
    while True:
        val = yield i  # có thể nhận giá trị từ bên ngoài
        if val is not None:
            i = val
        else:
            i += 1

c = counter()
next(c)  # start generator
c.send(10)  # gửi giá trị vào generator

Method resolution oorder (MRO)

  • Quyết định tìm kiếm phương thức kế thừa đa cấp
  • Python sử dụng thuật toán C3 linearization
  • Có thể xem MRO bằng ClassName.mro
class A: pass
class B: pass
class C(A, B): pass
class D(B, A): pass
# class E(C, D): pass  # Raises TypeError - MRO conflict

print(C.__mro__)  # Thứ tự: C -> A -> B -> object

Garbage collection

  • Python sử dụng reference counting và generational garbage collection
  • Vòng tròn reference có thể gây memory leak
  • weakref module giúp tránh vòng tròn reference
import gc
import weakref

# Vòng tròn reference
class Node:
    def __init__(self):
        self.next = None

a = Node()
b = Node()
a.next = b
b.next = a  # tạo circular reference

# Giải pháp với weakref
class Better_Node:
    def __init__(self):
        self.next = None
        self._ref = None

    @property
    def ref(self):
        return self._ref() if self._ref else None

    @ref.setter
    def ref(self, node):
        self._ref = weakref.ref(node)

Context managers và with statement

  • Không chỉ dùng để đóng file
  • Có thể tự tạo context manager bằng enterexit
  • Rất hữu ích cho resource management
from contextlib import contextmanager
import time

class Timer:
    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        self.end = time.time()
        print(f"Time taken: {self.end - self.start}s")

# Hoặc dùng decorator
@contextmanager
def timer():
    start = time.time()
    yield
    end = time.time()
    print(f"Time taken: {end - start}s")

# Sử dụng
with Timer():
    # code block
    pass

Decorator là higher-order functions:

  • Decorator có thể nhận và trả về functions
  • Có thể stack nhiều decorator
  • Class có thể được dùng làm decorator
def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end-start}s")
        return result
    return wrapper

# Class decorator
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

Duck typing và EAFP:

  • Python không kiểm tra kiểu dữ liệu mà kiểm tra khả năng
  • EAFP (Easier to Ask for Forgiveness than Permission) là phong cách lập trình Python
  • Try/except thường tốt hơn kiểm tra điều kiện trước
# Không tốt (LBYL - Look Before You Leap)
if hasattr(obj, 'run') and callable(obj.run):
    obj.run()

# Tốt hơn (EAFP)
try:
    obj.run()
except AttributeError:
    # handle missing method
    pass

# Duck typing example
class Duck:
    def quack(self): print("Quack!")
    def walk(self): print("Walking like a duck!")

class Person:
    def quack(self): print("Imitation quack")
    def walk(self): print("Walking like human!")

def duck_test(thing):
    thing.quack()
    thing.walk()
    # Không quan tâm thing là gì, chỉ cần có methods cần thiết

Slots có thể tối ưu memory:

  • slots giới hạn attributes của class
  • Giúp tiết kiệm bộ nhớ và tăng tốc độ truy cập attribute
  • Nhưng làm mất tính linh hoạt của dynamic attributes
class WithoutSlots:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class WithSlots:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Memory comparison
import sys
normal = WithoutSlots(1, 2)
slotted = WithSlots(1, 2)
print(sys.getsizeof(normal.__dict__))  # Có __dict__
print(sys.getsizeof(slotted))  # Không có __dict__, nhẹ hơn

Metaclass là factory của class:

  • Metaclass tạo và tùy chỉnh class
  • Có thể dùng để validate, modify, hoặc enhance classes
  • type là metaclass mặc định
class ValidationMeta(type):
    def __new__(cls, name, bases, attrs):
        # Validate attributes trước khi tạo class
        for key, value in attrs.items():
            if key.startswith('__'): continue
            if not callable(value) and not isinstance(value, (int, str, float)):
                raise TypeError(f"{key} must be int, str, or float")
        return super().__new__(cls, name, bases, attrs)

class ValidatedClass(metaclass=ValidationMeta):
    x = 1  # OK
    y = "string"  # OK
    # z = [1,2,3]  # Raises TypeError

Descriptors điều khiển attribute access:

  • property là một descriptor phổ biến
  • Có thể tự tạo descriptor với get, set, delete
  • Dùng để implement managed attributes
class Positive:
    def __init__(self):
        self._value = None

    def __get__(self, obj, objtype):
        return self._value

    def __set__(self, obj, value):
        if value < 0:
            raise ValueError("Must be positive!")
        self._value = value

class Circle:
    radius = Positive()  # Dùng descriptor để validate

    @property  # Built-in descriptor
    def area(self):
        return 3.14 * self.radius ** 2

circle = Circle()
circle.radius = 5  # OK
# circle.radius = -5  # Raises ValueError

Triết lý thiết kế của Python

  • Zen of Python: Làm quen với "Zen of Python" (nhập "import this" trong Python console) để hiểu rõ các nguyên tắc thiết kế cơ bản của ngôn ngữ này.
  • Tính đọc được của mã: Tìm hiểu về các nguyên tắc viết mã đẹp, rõ ràng và dễ bảo trì để tạo ra các dự án Python chất lượng cao.
  • Tính khả năng mở rộng: Khám phá cách tạo ra các extension module bằng C hoặc C++ để tăng tốc độ thực thi các phần mã quan trọng.
➜  ~ python3
Python 3.10.12 (main, Nov  6 2024, 20:22:13) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Đăng nhận xét

Tham gia cuộc trò chuyện