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 enter và exit
- 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!