
Python 写多了会慢慢发现,语法本身藏着不少好用的东西,但不是每个人都会去翻文档把它们挖出来。下面整理了 30 个从基础到进阶的技巧,每条附带可直接跑的代码,按需取用。
往期阅读>>>
Python 20 个文本分析的库:效率提升 10 倍的秘密武器
Python 自动化管理Jenkins的15个实用脚本,提升效率
App2Docker:如何无需编写Dockerfile也可以创建容器镜像
Python 自动化识别Nginx配置并导出为excel文件,提升Nginx管理效率
Python 3.8 引入的 := 可以在表达式里直接赋值,省掉一次单独的赋值语句:
# 传统写法lines = []whileTrue:line = input()ifnotline:breaklines.append(line)# 使用海象运算符lines = []while (line := input()):lines.append(line)
类型提示不影响运行,但 IDE 的补全和 mypy 的静态检查都依赖它。写库或者多人协作时特别省事:
fromtypingimportOptional, List, Tupledefprocess_data(data: List[int],threshold: Optional[int] = None) ->Tuple[List[int], int]:"""处理数据并返回结果和计数"""ifthresholdisNone:threshold = 0filtered = [xforxindataifx>threshold]returnfiltered, len(filtered)
用字符串拼路径是老写法,pathlib 更直观,也跨平台:
frompathlibimportPathdata_dir = Path("data")data_dir.mkdir(exist_ok=True)file_path = data_dir/"output.txt"file_path.write_text("Hello, Python!")content = file_path.read_text()print(content)
访问字典里不存在的键会直接抛 KeyError,用 get 可以给个默认值兜底:
user_preferences = {"theme": "dark", "language": "zh"}theme = user_preferences.get("theme", "light")font_size = user_preferences.get("font_size", 14) # 返回默认值 14
需要给字典里每个新键自动初始化一个默认值时,defaultdict 比手动判断 if key not in d 简洁很多:
fromcollectionsimportdefaultdicttext = "apple banana apple orange banana apple"word_count = defaultdict(int)forwordintext.split():word_count[word] += 1print(dict(word_count)) # {'apple': 3, 'banana': 2, 'orange': 1}
遍历时需要索引,不用 range(len(...)),直接用 enumerate:
fruits = ["apple", "banana", "orange", "grape"]fori, fruitinenumerate(fruits, start=1): # 从 1 开始计数print(f"{i}. {fruit}")
多个列表要一起遍历,用 zip 比手动管索引清楚:
names = ["Alice", "Bob", "Charlie"]scores = [85, 92, 78]subjects = ["Math", "Science", "English"]forname, score, subjectinzip(names, scores, subjects):print(f"{name} got {score} in {subject}")# 顺手转成字典result = dict(zip(names, scores))print(result) # {'Alice': 85, 'Bob': 92, 'Charlie': 78}
列表推导式可以嵌套,生成二维结构时比双层 for 循环紧凑:
multiplication_table = [[i*jforjinrange(1, 6)] foriinrange(1, 6)]forrowinmultiplication_table:print(row)# [1, 2, 3, 4, 5]# [2, 4, 6, 8, 10]# ...
判断序列里是否有满足条件的元素,或者全部满足:
numbers = [2, 4, 6, 8, 10]all_even = all(n%2 == 0forninnumbers)print(f"All even: {all_even}") # Truehas_large = any(n>5forninnumbers)print(f"Has number > 5: {has_large}") # True
sorted 的 key 参数接收 lambda,可以同时按多个字段排序:
students = [ {"name": "Alice", "score": 85, "age": 20}, {"name": "Bob", "score": 92, "age": 19}, {"name": "Charlie", "score": 85, "age": 21}, {"name": "David", "score": 78, "age": 20},]# 分数降序,年龄升序sorted_students = sorted(students,key=lambdax: (-x["score"], x["age"]))forstudentinsorted_students:print(f"{student['name']}: score={student['score']}, age={student['age']}")
f-string 的格式化能力比很多人用到的强得多:
name = "Python"version = 3.11price = 0.0print(f"Language: {name:>10}") # 右对齐,宽度 10print(f"Version: {version:.2f}") # 保留 2 位小数print(f"Price: ${price:,.2f}") # 千位分隔符print(f"Hex: {255:#06x}") # 十六进制,宽度 6,前导 0print(f"Binary: {42:08b}") # 二进制,宽度 8,前导 0
标准库里的 itertools 提供了不少实用的迭代工具,排列组合、无限计数都有:
importitertoolscounter = itertools.count(start=10, step=2)print(next(counter)) # 10print(next(counter)) # 12print(next(counter)) # 14letters = ['A', 'B', 'C']combinations = list(itertools.combinations(letters, 2))print(combinations) # [('A', 'B'), ('A', 'C'), ('B', 'C')]permutations = list(itertools.permutations(letters, 2))print(permutations) # [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
用 @contextmanager 装饰器可以把普通函数变成上下文管理器,不需要写 __enter__/__exit__:
fromcontextlibimportcontextmanagerimporttime@contextmanagerdeftimer(label: str):start = time.time()try:yieldfinally:end = time.time()print(f"{label}: {end - start:.4f} seconds")withtimer("Calculation"):result = sum(i**2foriinrange(1000000))print(f"Result: {result}")
主要用来存数据的类,用 @dataclass 比手写 __init__/__repr__/__eq__ 省很多:
fromdataclassesimportdataclass, fieldfromtypingimportListfromdatetimeimportdatetime@dataclass(order=True)classProduct:name: strprice: floatcategory: str = "General"tags: List[str] = field(default_factory=list)created_at: datetime = field(default_factory=datetime.now)defdisplay_price(self) ->str:returnf"${self.price:.2f}"product1 = Product("Laptop", 999.99, "Electronics", ["tech", "portable"])product2 = Product("Book", 29.99, "Education")print(product1)print(f"Formatted price: {product1.display_price()}")print(f"product1 > product2: {product1 > product2}")
functools.partial 可以预先固定函数的某些参数,生成一个新的可调用对象:
fromfunctoolsimportpartialdefpower(base: float, exponent: float) ->float:returnbase**exponentsquare = partial(power, exponent=2)cube = partial(power, exponent=3)print(f"Square of 5: {square(5)}") # 25.0print(f"Cube of 3: {cube(3)}") # 27.0
对于重复计算成本高的函数,加一个 @lru_cache 就能自动缓存结果:
fromfunctoolsimportlru_cacheimporttime@lru_cache(maxsize=128)deffibonacci(n: int) ->int:ifn<2:returnnreturnfibonacci(n-1) +fibonacci(n-2)start = time.time()result = fibonacci(35)print(f"fibonacci(35) = {result}")print(f"Time taken: {time.time() - start:.4f} seconds")start = time.time()result = fibonacci(35)print(f"Cached call time: {time.time() - start:.6f} seconds")
比普通元组多字段名,比 dataclass 更轻量,适合只读数据结构:
fromtypingimportNamedTupleclassPoint3D(NamedTuple):x: floaty: floatz: floatdefdistance_from_origin(self) ->float:return (self.x**2+self.y**2+self.z**2) **0.5p1 = Point3D(1.0, 2.0, 3.0)p2 = Point3D(4.0, 5.0, 6.0)print(f"Point 1: {p1}")print(f"Distance from origin: {p1.distance_from_origin():.2f}")print(f"Points are equal: {p1 == p2}")
有多层配置(默认值、用户配置、环境变量)时,ChainMap 比手动合并字典更清晰,优先级也更好控制:
fromcollectionsimportChainMapdefault_config = {"theme": "light", "language": "en", "debug": False}user_config = {"theme": "dark", "font_size": 14}env_config = {"debug": True}# 优先级:env_config > user_config > default_configconfig = ChainMap(env_config, user_config, default_config)print(f"Theme: {config['theme']}") # 来自 user_configprint(f"Language: {config['language']}") # 来自 default_configprint(f"Debug: {config['debug']}") # 来自 env_config
:= 在 for 循环里结合 re.match 特别好用,省掉了先 match 再判断 if match 的步骤:
importrelines = ["Error: File not found","Warning: Low disk space","Info: Process started","Error: Permission denied",]errors = []forlineinlines:ifmatch := re.match(r"Error: (.+)", line):errors.append(match.group(1))print(f"Errors found: {errors}")# 列表推导式里也能用data = [1, 2, 3, 4, 5]processed = [n_squaredforxindataif (n_squared := x**2) >10]print(f"Squares > 10: {processed}")
Python 3.5+ 的扩展解包,合并列表、字典都很方便:
first, *middle, last = [1, 2, 3, 4, 5]print(f"First: {first}, Middle: {middle}, Last: {last}")# 合并字典(Python 3.9+)dict1 = {"a": 1, "b": 2}dict2 = {"b": 3, "c": 4}merged = dict1|dict2print(f"Merged dict: {merged}")# 函数参数解包defconnect(host, port, timeout=30):print(f"Connecting to {host}:{port} (timeout: {timeout})")server_config = {"host": "example.com", "port": 8080, "timeout": 60}connect(**server_config)# 合并列表list1 = [1, 2, 3]list2 = [4, 5, 6]combined = [*list1, *list2]print(f"Combined list: {combined}")
__slots__ 减少内存占用需要创建大量实例的类,加 __slots__ 可以显著减少每个实例的内存开销:
classPlayer:__slots__ = ('name', 'score', 'level')def__init__(self, name: str, score: int = 0, level: int = 1):self.name = nameself.score = scoreself.level = leveldeflevel_up(self):self.level += 1self.score += 100players = [Player(f"Player{i}", i*100, i//10+1) foriinrange(1000)]importsysplayer_instance = Player("Test")print(f"Memory size of Player instance: {sys.getsizeof(player_instance)} bytes")
用 @property 可以把方法包装成属性访问,在赋值时加验证逻辑也很自然:
classCircle:def__init__(self, radius: float):self._radius = radius@propertydefradius(self) ->float:returnself._radius@radius.setterdefradius(self, value: float):ifvalue<= 0:raiseValueError("Radius must be positive")self._radius = value@propertydefdiameter(self) ->float:return2*self._radius@diameter.setterdefdiameter(self, value: float):self._radius = value/2@propertydefarea(self) ->float:return3.14159*self._radius**2circle = Circle(5.0)print(f"Radius: {circle.radius}")print(f"Area: {circle.area:.2f}")circle.diameter = 20.0print(f"New radius after setting diameter: {circle.radius}")
@staticmethod 不依赖实例或类,纯粹是挂在类名下的工具函数;@classmethod 拿到的是类本身,常用作替代构造函数:
classTemperatureConverter:@staticmethoddefcelsius_to_fahrenheit(celsius: float) ->float:returncelsius*9/5+32@staticmethoddeffahrenheit_to_celsius(fahrenheit: float) ->float:return (fahrenheit-32) *5/9@classmethoddeffrom_string(cls, temp_str: str):value, unit = temp_str.split()value = float(value)ifunit.upper() == 'C':returncls(value, 'C')elifunit.upper() == 'F':celsius = cls.fahrenheit_to_celsius(value)returncls(celsius, 'C')else:raiseValueError(f"Unknown unit: {unit}")def__init__(self, value: float, unit: str = 'C'):self.celsius = valueifunit.upper() == 'C'elseself.fahrenheit_to_celsius(value)@propertydeffahrenheit(self) ->float:returnself.celsius_to_fahrenheit(self.celsius)def__str__(self):returnf"{self.celsius:.1f}°C ({self.fahrenheit:.1f}°F)"print(f"37°C in Fahrenheit: {TemperatureConverter.celsius_to_fahrenheit(37):.1f}")temp1 = TemperatureConverter(25, 'C')print(f"Temperature 1: {temp1}")temp2 = TemperatureConverter.from_string("98.6 F")print(f"Temperature 2: {temp2}")
用 ABC 定义接口,强制子类实现指定方法,防止漏掉:
fromabcimportABC, abstractmethodfromtypingimportListclassDataProcessor(ABC):@abstractmethoddefload_data(self, source: str) ->List[dict]:pass@abstractmethoddefprocess(self, data: List[dict]) ->List[dict]:pass@abstractmethoddefsave_result(self, data: List[dict], destination: str) ->bool:passdefrun_pipeline(self, source: str, destination: str) ->bool:try:data = self.load_data(source)processed_data = self.process(data)returnself.save_result(processed_data, destination)exceptExceptionase:print(f"Pipeline failed: {e}")returnFalseclassCSVProcessor(DataProcessor):defload_data(self, source: str) ->List[dict]:print(f"Loading CSV data from {source}")return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]defprocess(self, data: List[dict]) ->List[dict]:importdatetimeforitemindata:item["processed_at"] = datetime.datetime.now().isoformat()returndatadefsave_result(self, data: List[dict], destination: str) ->bool:print(f"Saving processed data to {destination}")returnTrueprocessor = CSVProcessor()success = processor.run_pipeline("input.csv", "output.csv")print(f"Pipeline completed: {success}")
I/O 密集型任务(比如同时请求多个接口)用异步可以大幅提升吞吐:
importasyncioimportaiohttpimporttimefromtypingimportListasyncdeffetch_url(session: aiohttp.ClientSession, url: str) ->str:asyncwithsession.get(url) asresponse:returnawaitresponse.text()asyncdeffetch_multiple_urls(urls: List[str]):asyncwithaiohttp.ClientSession() assession:tasks = [fetch_url(session, url) forurlinurls]results = awaitasyncio.gather(*tasks, return_exceptions=True)returnresultsasyncdefmain():urls = ["https://httpbin.org/delay/1","https://httpbin.org/delay/2","https://httpbin.org/delay/1", ]start_time = time.time()results = awaitfetch_multiple_urls(urls)print(f"Fetched {len(urls)} URLs in {time.time() - start_time:.2f} seconds")fori, resultinenumerate(results):ifisinstance(result, Exception):print(f"URL {i+1} failed: {result}")else:print(f"URL {i+1} response length: {len(result)}")# asyncio.run(main())print("Note: Async code requires asyncio.run() to execute")
描述符可以把验证逻辑抽出来复用,不用在每个属性的 setter 里重复写:
classValidatedAttribute:def__init__(self, min_value=None, max_value=None, allowed_values=None):self.min_value = min_valueself.max_value = max_valueself.allowed_values = allowed_valuesself.name = Nonedef__set_name__(self, owner, name):self.name = namedef__get__(self, obj, objtype=None):ifobjisNone:returnselfreturnobj.__dict__.get(self.name)def__set__(self, obj, value):self._validate(value)obj.__dict__[self.name] = valuedef_validate(self, value):ifself.min_valueisnotNoneandvalue<self.min_value:raiseValueError(f"{self.name} must be >= {self.min_value}")ifself.max_valueisnotNoneandvalue>self.max_value:raiseValueError(f"{self.name} must be <= {self.max_value}")ifself.allowed_valuesisnotNoneandvaluenotinself.allowed_values:raiseValueError(f"{self.name} must be one of {self.allowed_values}")classProduct:price = ValidatedAttribute(min_value=0)quantity = ValidatedAttribute(min_value=0, max_value=1000)category = ValidatedAttribute(allowed_values=["Electronics", "Books", "Clothing"])def__init__(self, name: str, price: float, quantity: int, category: str):self.name = nameself.price = priceself.quantity = quantityself.category = category@propertydeftotal_value(self) ->float:returnself.price*self.quantitytry:laptop = Product("Laptop", 999.99, 10, "Electronics")print(f"Total value: ${laptop.total_value:.2f}")laptop.price = -100# 触发 ValueErrorexceptValueErrorase:print(f"Validation error: {e}")try:invalid_product = Product("Test", 50, 2000, "Unknown")exceptValueErrorase:print(f"Validation error: {e}")
元类是 Python 里比较深的特性,最常见的实际用途之一是实现单例:
classSingletonMeta(type):_instances = {}def__call__(cls, *args, **kwargs):ifclsnotincls._instances:cls._instances[cls] = super().__call__(*args, **kwargs)returncls._instances[cls]classDatabaseConnection(metaclass=SingletonMeta):def__init__(self, connection_string: str):self.connection_string = connection_stringself._connected = Falseprint(f"Initializing connection to {connection_string}")defconnect(self):ifnotself._connected:print("Establishing connection...")self._connected = Truereturnself._connecteddefexecute_query(self, query: str):ifnotself._connected:raiseRuntimeError("Not connected to database")returnf"Result of: {query}"db1 = DatabaseConnection("mysql://localhost:3306/mydb")db1.connect()db2 = DatabaseConnection("mysql://localhost:3306/mydb") # 不会重新初始化print(f"db1 is db2: {db1 is db2}") # Trueprint(db1.execute_query("SELECT * FROM users"))print(db2.execute_query("SELECT * FROM products"))
装饰器可以叠加使用,下面是两个实用的例子——计时和自动重试:
importtimeimportfunctoolsfromtypingimportCallable, Anydefretry(max_attempts: int = 3, delay: float = 1.0):defdecorator(func: Callable) ->Callable:@functools.wraps(func)defwrapper(*args, **kwargs) ->Any:last_exception = Noneforattemptinrange(1, max_attempts+1):try:print(f"Attempt {attempt}/{max_attempts}...")returnfunc(*args, **kwargs)exceptExceptionase:last_exception = eifattempt<max_attempts:print(f"Failed: {e}. Retrying in {delay}s...")time.sleep(delay)raiselast_exceptionreturnwrapperreturndecoratordeftimer(func: Callable) ->Callable:@functools.wraps(func)defwrapper(*args, **kwargs) ->Any:start_time = time.time()result = func(*args, **kwargs)print(f"{func.__name__} took {time.time() - start_time:.4f} seconds")returnresultreturnwrapper@timer@retry(max_attempts=2, delay=0.5)defunstable_operation(should_fail: bool = True):ifshould_fail:raiseRuntimeError("Operation failed!")return"Success"try:result = unstable_operation(should_fail=True)exceptExceptionase:print(f"Operation failed with: {e}")
标记废弃 API 或者给用户提示非致命问题,用 warnings 比直接 print 更规范——调用方可以选择过滤或捕获:
importwarningsimportdatetimedefprocess_data(data, use_old_method=False):ifuse_old_method:warnings.warn("Old method is deprecated and will be removed in v2.0. ""Use the default method instead.",DeprecationWarning,stacklevel=2 )returnf"Old result: {len(data)}"returnf"New result: {sum(data)}"defcheck_expiry(expiry_date):today = datetime.date.today()ifexpiry_date<today:warnings.warn(f"Product expired on {expiry_date}", UserWarning)returnFalseelif (expiry_date-today).days<= 7:warnings.warn(f"Product will expire in {(expiry_date - today).days} days",UserWarning )returnTruewarnings.simplefilter("always")result1 = process_data([1, 2, 3], use_old_method=True)print(f"Result: {result1}")today = datetime.date.today()check_expiry(today-datetime.timedelta(days=30))check_expiry(today+datetime.timedelta(days=3))check_expiry(today+datetime.timedelta(days=100))# 忽略 DeprecationWarningwarnings.simplefilter("ignore", category=DeprecationWarning)result2 = process_data([1, 2, 3], use_old_method=True)print(f"Result (no warning): {result2}")
调试阶段用 print 没问题,但正式项目里 logging 能分级别、写文件、按大小轮转,维护起来方便得多:
importloggingimportlogging.handlersfrompathlibimportPathdefsetup_logging(name: str,log_file: str = "app.log",console_level=logging.INFO,file_level=logging.DEBUG):log_path = Path(log_file)log_path.parent.mkdir(parents=True, exist_ok=True)logger = logging.getLogger(name)logger.setLevel(logging.DEBUG)iflogger.handlers:returnloggerformatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s' )console_handler = logging.StreamHandler()console_handler.setLevel(console_level)console_handler.setFormatter(formatter)logger.addHandler(console_handler)file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=10*1024*1024, backupCount=5 )file_handler.setLevel(file_level)file_handler.setFormatter(formatter)logger.addHandler(file_handler)returnloggerclassDataProcessor:def__init__(self, name: str):self.logger = setup_logging(f"DataProcessor.{name}")self.name = nameself.logger.info(f"DataProcessor '{name}' initialized")defprocess(self, data):self.logger.debug(f"Starting to process {len(data)} items")ifnotdata:self.logger.warning("Empty data provided")return []try:result = []fori, iteminenumerate(data):processed = self._transform(item)result.append(processed)if (i+1) %10 == 0:self.logger.info(f"Processed {i + 1}/{len(data)} items")self.logger.info(f"Successfully processed {len(data)} items")returnresultexceptExceptionase:self.logger.error(f"Error processing data: {e}", exc_info=True)raisedef_transform(self, item):ifnotisinstance(item, (int, float)):self.logger.warning(f"Unexpected type: {type(item)}")ifitem == 0:self.logger.error("Cannot process zero value")raiseValueError("Zero value not allowed")returnitem*2processor = DataProcessor("TestProcessor")test_data = list(range(1, 25))test_data.append(0)try:result = processor.process(test_data)print(f"Result length: {len(result)}")exceptExceptionase:print(f"Processing failed: {e}")logger = setup_logging("Demo")logger.debug("debug message")logger.info("info message")logger.warning("warning message")logger.error("error message")logger.critical("critical message")
以上 30 条覆盖了日常开发里比较常踩到的点。随时能用上在写库或者需要长期维护的项目里更有价值。不需要全部记住,遇到对应场景时翻出来用就行。
