一文淺析Python如何構(gòu)建優(yōu)雅的異常體系
“程序里有兩種錯(cuò)誤:一種是你預(yù)料到的,一種是你沒預(yù)料到的。好的異常處理,就是讓這兩種錯(cuò)誤都無處遁形。”——每一位在生產(chǎn)事故復(fù)盤會(huì)上沉默過的開發(fā)者
一、引言:異常處理,被低估的編程藝術(shù)
我曾接手過一個(gè)遺留項(xiàng)目,全文搜索 except,發(fā)現(xiàn)幾乎每一處都寫著:
try:
do_something()
except Exception:
pass
那一刻,我感受到了一種特殊的絕望——不是因?yàn)榇a崩了,而是因?yàn)?strong>代碼永遠(yuǎn)不會(huì)崩,所有的錯(cuò)誤都被悄無聲息地吞掉了,系統(tǒng)帶著滿身隱患繼續(xù)運(yùn)行,直到某天以一種完全出乎意料的方式徹底爆發(fā)。
異常處理,是 Python 編程中最容易被"隨便寫寫"的部分,卻也是最能體現(xiàn)一個(gè)工程師成熟度的地方。本文將從哲學(xué)層面的"何時(shí)捕獲、何時(shí)傳播"講到工程層面的"如何設(shè)計(jì)自定義異常體系",用真實(shí)代碼和實(shí)戰(zhàn)案例,幫你建立一套完整的異常處理思維框架。
二、理解 Python 異常的底層邏輯
2.1 異常不是"錯(cuò)誤",它是"信號(hào)"
在 Python 中,異常(Exception)本質(zhì)上是一種控制流機(jī)制——當(dāng)程序遇到無法繼續(xù)正常執(zhí)行的情況時(shí),它拋出一個(gè)信號(hào),沿著調(diào)用棧向上傳播,直到被某處捕獲或?qū)е鲁绦蚪K止。
函數(shù) C 拋出異常
↑ 傳播
函數(shù) B(沒有捕獲)
↑ 傳播
函數(shù) A(捕獲并處理)
↑ 程序繼續(xù)
這個(gè)傳播機(jī)制是異常的核心價(jià)值:它把"發(fā)現(xiàn)問題的地方"和"處理問題的地方"解耦了。你不需要在每一層函數(shù)里都檢查返回值,只需要在合適的層級(jí)處理異常。
2.2 Python 異常層級(jí)一覽
BaseException
├── SystemExit # sys.exit() 觸發(fā),不應(yīng)被普通 except 捕獲
├── KeyboardInterrupt # Ctrl+C,同上
├── GeneratorExit # 生成器關(guān)閉
└── Exception # 所有"正常"異常的基類
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
├── IOError / OSError
├── RuntimeError
└── ... (還有數(shù)十種內(nèi)置異常)
一個(gè)關(guān)鍵認(rèn)知:永遠(yuǎn)不要用 except BaseException 或裸 except:,除非你明確知道自己在做什么——它會(huì)連 KeyboardInterrupt 和 SystemExit 也一并吞掉,導(dǎo)致程序無法被正常終止。
三、核心哲學(xué):何時(shí)捕獲,何時(shí)傳播
這是異常處理中最難回答、也最值得深思的問題。我的答案是一個(gè)判斷框架,分四個(gè)維度來思考:
3.1 你能"修復(fù)"這個(gè)異常嗎
能修復(fù) → 捕獲并處理
# 場(chǎng)景:讀取配置文件,文件不存在時(shí)使用默認(rèn)配置
def load_config(path: str) -> dict:
try:
with open(path, "r") as f:
return json.load(f)
except FileNotFoundError:
# 我們能處理這種情況:使用默認(rèn)值
logger.warning(f"配置文件 {path} 不存在,使用默認(rèn)配置")
return DEFAULT_CONFIG
except json.JSONDecodeError as e:
# 配置文件格式錯(cuò)誤,這是調(diào)用者的問題,重新拋出更有意義的異常
raise ConfigurationError(f"配置文件格式錯(cuò)誤: {e}") from e
不能修復(fù) → 讓它傳播(或轉(zhuǎn)換后傳播)
# 場(chǎng)景:數(shù)據(jù)庫(kù)寫入失敗
def save_user(user: User) -> None:
try:
db.execute("INSERT INTO users ...", user.to_dict())
except DatabaseConnectionError:
# 我無法修復(fù)數(shù)據(jù)庫(kù)連接問題,讓它向上傳播
# 但可以加上上下文信息
logger.error(f"保存用戶 {user.id} 失敗")
raise # 重新拋出原始異常,保留完整堆棧
3.2 "吞異常"是萬惡之源
# ? 極其危險(xiǎn)的寫法:異常被吞掉,程序悄悄出錯(cuò)
def get_user_age(user_id: int) -> int:
try:
user = db.get_user(user_id)
return user.age
except Exception:
pass # 發(fā)生了什么?沒人知道。
# ? 至少要記錄日志,讓問題有跡可查
def get_user_age(user_id: int) -> int | None:
try:
user = db.get_user(user_id)
return user.age
except UserNotFoundError:
logger.warning(f"用戶 {user_id} 不存在")
return None
except Exception:
logger.exception(f"獲取用戶 {user_id} 年齡時(shí)發(fā)生未預(yù)期錯(cuò)誤")
raise # 未知錯(cuò)誤,必須傳播
3.3 捕獲異常的"精確度原則"
異常捕獲的范圍應(yīng)該盡可能精確,就像外科手術(shù)一樣——切掉該切的,保留該保留的。
# ? 過于寬泛:一網(wǎng)打盡,隱患無窮
try:
result = complex_calculation(data)
save_to_database(result)
send_notification(result)
except Exception as e:
logger.error(f"出錯(cuò)了: {e}")
# ? 精確捕獲:每種異常獨(dú)立處理
try:
result = complex_calculation(data)
except (ValueError, TypeError) as e:
raise InvalidInputError(f"輸入數(shù)據(jù)格式錯(cuò)誤: {e}") from e
try:
save_to_database(result)
except DatabaseError as e:
logger.error(f"數(shù)據(jù)庫(kù)保存失敗,結(jié)果已緩存")
cache.store(result) # 降級(jí)處理
raise
try:
send_notification(result)
except NotificationError:
# 通知失敗不影響主流程,記錄即可
logger.warning("通知發(fā)送失敗,將在下次重試")
3.4 "異常邊界"思維
一個(gè)成熟的系統(tǒng)應(yīng)該有明確的異常邊界:在邊界內(nèi)部,異??梢宰杂蓚鞑?;在邊界處,對(duì)異常進(jìn)行統(tǒng)一處理(轉(zhuǎn)換、記錄、降級(jí))。
常見的異常邊界層級(jí):
- Web 框架層:將所有未處理異常轉(zhuǎn)換為 HTTP 錯(cuò)誤響應(yīng)
- Service 層:將底層技術(shù)異常(DB、網(wǎng)絡(luò))轉(zhuǎn)換為業(yè)務(wù)異常
- Task/Job 層:捕獲所有異常,記錄日志,決定重試或放棄
四、設(shè)計(jì)自定義異常體系
4.1 為什么需要自定義異常
內(nèi)置異常(如 ValueError、RuntimeError)是通用的,它們?nèi)狈I(yè)務(wù)語義。當(dāng)你的系統(tǒng)拋出 ValueError: invalid user id 時(shí),調(diào)用者很難判斷該如何應(yīng)對(duì);但如果拋出 UserNotFoundError,意圖立刻清晰。
自定義異常的三大價(jià)值:
- 語義明確:異常名本身就是文檔
- 精確捕獲:調(diào)用者可以只捕獲自己關(guān)心的異常類型
- 攜帶上下文:可以附加豐富的錯(cuò)誤信息和診斷數(shù)據(jù)
4.2 構(gòu)建分層異常體系
以一個(gè)電商系統(tǒng)為例,設(shè)計(jì)如下異常層級(jí):
# exceptions.py —— 電商系統(tǒng)異常體系
class AppError(Exception):
"""
應(yīng)用級(jí)基礎(chǔ)異常,所有自定義異常的根
攜帶錯(cuò)誤碼,便于 API 響應(yīng)和監(jiān)控告警
"""
def __init__(self, message: str, code: str = "APP_ERROR", details: dict = None):
super().__init__(message)
self.message = message
self.code = code
self.details = details or {}
def to_dict(self) -> dict:
"""轉(zhuǎn)換為 API 響應(yīng)格式"""
return {
"error": self.code,
"message": self.message,
"details": self.details
}
def __repr__(self) -> str:
return f"{self.__class__.__name__}(code={self.code!r}, message={self.message!r})"
# ─── 領(lǐng)域?qū)樱喊礃I(yè)務(wù)模塊劃分 ────────────────────────
class UserError(AppError):
"""用戶模塊異?;?""
pass
class UserNotFoundError(UserError):
"""用戶不存在"""
def __init__(self, user_id: int | str):
super().__init__(
message=f"用戶 {user_id} 不存在",
code="USER_NOT_FOUND",
details={"user_id": user_id}
)
self.user_id = user_id
class UserPermissionError(UserError):
"""用戶權(quán)限不足"""
def __init__(self, user_id: int, required_permission: str):
super().__init__(
message=f"用戶 {user_id} 缺少權(quán)限: {required_permission}",
code="PERMISSION_DENIED",
details={"user_id": user_id, "required": required_permission}
)
class OrderError(AppError):
"""訂單模塊異?;?""
pass
class OrderNotFoundError(OrderError):
def __init__(self, order_id: str):
super().__init__(
message=f"訂單 {order_id} 不存在",
code="ORDER_NOT_FOUND",
details={"order_id": order_id}
)
class InsufficientStockError(OrderError):
"""庫(kù)存不足"""
def __init__(self, product_id: str, requested: int, available: int):
super().__init__(
message=f"商品 {product_id} 庫(kù)存不足(需要 {requested},剩余 {available})",
code="INSUFFICIENT_STOCK",
details={
"product_id": product_id,
"requested": requested,
"available": available
}
)
class OrderStateError(OrderError):
"""訂單狀態(tài)流轉(zhuǎn)錯(cuò)誤"""
def __init__(self, order_id: str, current_state: str, expected_state: str):
super().__init__(
message=f"訂單 {order_id} 當(dāng)前狀態(tài)為 {current_state},無法執(zhí)行此操作(需要 {expected_state})",
code="INVALID_ORDER_STATE",
details={
"order_id": order_id,
"current_state": current_state,
"expected_state": expected_state
}
)
# ─── 基礎(chǔ)設(shè)施層:技術(shù)異常 ────────────────────────
class InfrastructureError(AppError):
"""基礎(chǔ)設(shè)施層異?;?""
pass
class DatabaseError(InfrastructureError):
def __init__(self, operation: str, cause: Exception = None):
super().__init__(
message=f"數(shù)據(jù)庫(kù)操作失敗: {operation}",
code="DATABASE_ERROR"
)
self.__cause__ = cause
class ExternalServiceError(InfrastructureError):
"""第三方服務(wù)調(diào)用失敗"""
def __init__(self, service_name: str, status_code: int = None):
super().__init__(
message=f"外部服務(wù) {service_name} 調(diào)用失敗",
code="EXTERNAL_SERVICE_ERROR",
details={"service": service_name, "status_code": status_code}
)
異常層級(jí)示意圖:
AppError
├── UserError
│ ├── UserNotFoundError
│ └── UserPermissionError
├── OrderError
│ ├── OrderNotFoundError
│ ├── InsufficientStockError
│ └── OrderStateError
└── InfrastructureError
├── DatabaseError
└── ExternalServiceError
4.3 在業(yè)務(wù)邏輯中使用異常體系
# order_service.py —— 異常體系的實(shí)際使用
class OrderService:
def create_order(self, user_id: int, items: list[dict]) -> Order:
"""創(chuàng)建訂單,展示完整的異常處理鏈路"""
# 1. 驗(yàn)證用戶
user = self._get_user_or_raise(user_id)
# 2. 檢查庫(kù)存(精確捕獲,各個(gè)擊破)
for item in items:
self._check_stock(item["product_id"], item["quantity"])
# 3. 創(chuàng)建訂單
try:
order = Order.create(user_id=user_id, items=items)
self.db.save(order)
return order
except Exception as e:
# 將底層異常轉(zhuǎn)換為業(yè)務(wù)異常,附加上下文
raise DatabaseError("創(chuàng)建訂單", cause=e) from e
def _get_user_or_raise(self, user_id: int) -> User:
"""獲取用戶,不存在則拋出語義明確的異常"""
user = self.db.find_user(user_id)
if user is None:
raise UserNotFoundError(user_id)
return user
def _check_stock(self, product_id: str, quantity: int) -> None:
"""檢查庫(kù)存,不足則拋出攜帶詳細(xì)信息的異常"""
product = self.db.find_product(product_id)
if product.stock < quantity:
raise InsufficientStockError(
product_id=product_id,
requested=quantity,
available=product.stock
)
def cancel_order(self, order_id: str, user_id: int) -> None:
"""取消訂單,演示狀態(tài)校驗(yàn)異常"""
order = self.db.find_order(order_id)
if order is None:
raise OrderNotFoundError(order_id)
if order.status != "pending":
raise OrderStateError(
order_id=order_id,
current_state=order.status,
expected_state="pending"
)
order.cancel()
self.db.save(order)
4.4 在 API 層統(tǒng)一處理異常
# api/exception_handlers.py —— FastAPI 統(tǒng)一異常處理
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
"""
統(tǒng)一處理所有業(yè)務(wù)異常,轉(zhuǎn)換為標(biāo)準(zhǔn) HTTP 響應(yīng)
"""
# 根據(jù)異常類型決定 HTTP 狀態(tài)碼
status_code_map = {
"USER_NOT_FOUND": 404,
"ORDER_NOT_FOUND": 404,
"PERMISSION_DENIED": 403,
"INSUFFICIENT_STOCK": 409,
"INVALID_ORDER_STATE": 422,
"DATABASE_ERROR": 503,
"EXTERNAL_SERVICE_ERROR": 502,
}
status_code = status_code_map.get(exc.code, 500)
# 服務(wù)端錯(cuò)誤記錄詳細(xì)日志
if status_code >= 500:
logger.error(f"服務(wù)端錯(cuò)誤: {exc!r}", exc_info=True)
else:
logger.info(f"業(yè)務(wù)異常: {exc!r}")
return JSONResponse(
status_code=status_code,
content=exc.to_dict()
)
@app.exception_handler(Exception)
async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
"""兜底處理:未被捕獲的異常"""
logger.critical(f"未處理的異常: {exc!r}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": "INTERNAL_ERROR", "message": "服務(wù)器內(nèi)部錯(cuò)誤"}
)
五、進(jìn)階技巧:讓異常處理更優(yōu)雅
5.1 使用raise ... from ...保留異常鏈
# ? 異常鏈:保留原始原因,同時(shí)提供業(yè)務(wù)上下文
try:
raw_data = json.loads(request_body)
except json.JSONDecodeError as e:
raise InvalidRequestError("請(qǐng)求體不是合法的 JSON 格式") from e
# 上面這行讓異常信息變?yōu)椋?
# InvalidRequestError: 請(qǐng)求體不是合法的 JSON 格式
# The above exception was the direct cause of the following exception:
# json.JSONDecodeError: ...(原始錯(cuò)誤保留)
# ? 丟失原始異常信息(調(diào)試時(shí)抓瞎)
try:
raw_data = json.loads(request_body)
except json.JSONDecodeError:
raise InvalidRequestError("請(qǐng)求體不是合法的 JSON 格式")
5.2 上下文管理器實(shí)現(xiàn)資源安全
# 自定義上下文管理器:事務(wù)管理
from contextlib import contextmanager
@contextmanager
def db_transaction(db_session):
"""
確保數(shù)據(jù)庫(kù)事務(wù)在異常時(shí)自動(dòng)回滾
"""
try:
yield db_session
db_session.commit()
logger.debug("事務(wù)提交成功")
except AppError:
db_session.rollback()
logger.warning("業(yè)務(wù)異常,事務(wù)已回滾")
raise # 業(yè)務(wù)異常繼續(xù)傳播
except Exception as e:
db_session.rollback()
logger.error("未知異常,事務(wù)已回滾", exc_info=True)
raise DatabaseError("事務(wù)執(zhí)行失敗") from e
finally:
db_session.close()
# 使用
with db_transaction(session) as txn:
txn.execute("UPDATE ...")
txn.execute("INSERT ...")
# 任何異常都會(huì)觸發(fā)回滾
5.3 重試機(jī)制:優(yōu)雅處理瞬時(shí)故障
import time
import functools
from typing import Type
def retry(
exceptions: tuple[Type[Exception], ...],
max_attempts: int = 3,
delay: float = 1.0,
backoff: float = 2.0
):
"""
裝飾器:對(duì)指定異常類型進(jìn)行自動(dòng)重試(指數(shù)退避)
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
attempt = 0
current_delay = delay
while attempt < max_attempts:
try:
return func(*args, **kwargs)
except exceptions as e:
attempt += 1
if attempt >= max_attempts:
logger.error(f"{func.__name__} 重試 {max_attempts} 次后仍失敗: {e}")
raise
logger.warning(
f"{func.__name__} 第 {attempt} 次失敗: {e},"
f"{current_delay:.1f}s 后重試"
)
time.sleep(current_delay)
current_delay *= backoff
return wrapper
return decorator
# 使用:對(duì)網(wǎng)絡(luò)請(qǐng)求、外部服務(wù)調(diào)用啟用重試
@retry(exceptions=(ExternalServiceError, ConnectionError), max_attempts=3, delay=0.5)
def call_payment_api(order_id: str, amount: float) -> dict:
response = requests.post(PAYMENT_API_URL, json={"order_id": order_id, "amount": amount})
if response.status_code != 200:
raise ExternalServiceError("payment-api", response.status_code)
return response.json()
5.4 避免異常處理的常見反模式
# 反模式一:用異??刂普A鞒蹋ㄐ阅懿?,語義混亂)
# ?
def find_user(user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return None # 這種情況應(yīng)該用 .filter().first()
# ?
def find_user(user_id):
return User.objects.filter(id=user_id).first()
# 反模式二:過于細(xì)碎的 try/except 塊
# ?
try:
a = int(input_a)
except ValueError:
a = 0
try:
b = int(input_b)
except ValueError:
b = 0
# ? 提取成函數(shù)
def safe_int(value: str, default: int = 0) -> int:
try:
return int(value)
except (ValueError, TypeError):
return default
a = safe_int(input_a)
b = safe_int(input_b)
# 反模式三:在 finally 中使用 return(會(huì)吞掉異常?。?
# ?
def dangerous():
try:
raise ValueError("出錯(cuò)了")
finally:
return 42 # 異常被吞掉,函數(shù)返回 42
# ? finally 只做清理,不做返回
def safe():
try:
raise ValueError("出錯(cuò)了")
finally:
cleanup() # 只清理資源
六、最佳實(shí)踐總結(jié)
經(jīng)過多年項(xiàng)目實(shí)戰(zhàn),我總結(jié)了異常處理的"七條準(zhǔn)則":
- 只捕獲你能處理的異常,其余的讓它傳播
- 捕獲越精確越好,
except Exception是最后手段 - 永遠(yuǎn)不要裸
except:或except BaseException - 吞掉異常必須有充分理由,并記錄日志
- 用
raise from保留異常鏈,別讓堆棧信息丟失 - 自定義異常要攜帶足夠的上下文信息
- 在系統(tǒng)邊界(API層、任務(wù)層)統(tǒng)一處理未捕獲異常
七、前沿視角:異常處理的演進(jìn)
隨著 Python 生態(tài)的演進(jìn),異常處理也在悄然升級(jí):
Python 3.11 的 ExceptionGroup:允許同時(shí)拋出多個(gè)異常,配合 except* 語法,專為 asyncio 并發(fā)場(chǎng)景設(shè)計(jì):
# Python 3.11+:并發(fā)任務(wù)的多異常處理
async def fetch_all(urls):
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(url)) for url in urls]
# 使用 except* 捕獲特定類型的并發(fā)異常
try:
await fetch_all(urls)
except* TimeoutError as eg:
print(f"超時(shí)的任務(wù)數(shù): {len(eg.exceptions)}")
except* ConnectionError as eg:
print(f"連接失敗的任務(wù)數(shù): {len(eg.exceptions)}")
Result 類型模式(函數(shù)式風(fēng)格,來自 Rust 的啟發(fā)):
from dataclasses import dataclass
from typing import Generic, TypeVar
T = TypeVar("T")
E = TypeVar("E", bound=Exception)
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Err(Generic[E]):
error: E
Result = Ok[T] | Err[E]
def safe_divide(a: float, b: float) -> Result:
if b == 0:
return Err(ZeroDivisionError("除數(shù)不能為零"))
return Ok(a / b)
# 調(diào)用者顯式處理兩種情況
match safe_divide(10, 0):
case Ok(value=v):
print(f"結(jié)果: {v}")
case Err(error=e):
print(f"計(jì)算失敗: {e}")
八、總結(jié)與互動(dòng)
回顧本文的核心思路:
- 哲學(xué)層面:異常是信號(hào),不是敵人;捕獲是承諾,不是逃避
- 判斷框架:能修復(fù)就捕獲,不能修復(fù)就傳播,永遠(yuǎn)不要吞掉
- 工程層面:分層異常體系讓代碼語義清晰,API 邊界統(tǒng)一兜底
- 進(jìn)階技巧:異常鏈、重試裝飾器、上下文管理器讓處理更優(yōu)雅
異常處理的最高境界,是讓閱讀代碼的人一眼就知道:這里可能出什么問題,出了問題會(huì)怎樣處理。 這不僅僅是技術(shù)的體現(xiàn),更是對(duì)團(tuán)隊(duì)協(xié)作和系統(tǒng)可維護(hù)性的深刻尊重。
到此這篇關(guān)于一文淺析Python如何構(gòu)建優(yōu)雅的異常體系的文章就介紹到這了,更多相關(guān)Python異常處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
運(yùn)用Python的webbrowser實(shí)現(xiàn)定時(shí)打開特定網(wǎng)頁(yè)
今天小編就為大家分享一篇運(yùn)用Python的webbrowser實(shí)現(xiàn)定時(shí)打開特定網(wǎng)頁(yè),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02
PyTorch之nn.ReLU與F.ReLU的區(qū)別介紹
這篇文章主要介紹了PyTorch之nn.ReLU與F.ReLU的區(qū)別介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-06-06
Pygame游戲開發(fā)之太空射擊實(shí)戰(zhàn)精靈的使用上篇
相信大多數(shù)8090后都玩過太空射擊游戲,在過去游戲不多的年代太空射擊自然屬于經(jīng)典好玩的一款了,今天我們來自己動(dòng)手實(shí)現(xiàn)它,在編寫學(xué)習(xí)中回顧過往展望未來,下面開始講解精靈的使用2022-08-08
解決Python中l(wèi)ist里的中文輸出到html模板里的問題
今天小編就為大家分享一篇解決Python中l(wèi)ist里的中文輸出到html模板里的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-12-12
Python Pyvis庫(kù)創(chuàng)建交互式網(wǎng)絡(luò)圖實(shí)例探究
這篇文章主要為大家介紹了Python Pyvis庫(kù)創(chuàng)建交互式網(wǎng)絡(luò)圖的實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Pycharm 設(shè)置默認(rèn)解釋器路徑和編碼格式的操作
這篇文章主要介紹了Pycharm 設(shè)置默認(rèn)解釋器路徑和編碼格式的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Django shell調(diào)試models輸出的SQL語句方法
今天小編就為大家分享一篇Django shell調(diào)試models輸出的SQL語句方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08

