Python腳本構(gòu)建輕量級監(jiān)控告警體系(郵件/釘釘/企業(yè)微信)
前言:為什么監(jiān)控告警是“好用”的底線?
我們花了大量時間讓機器人的任務能夠準時運行——無論是通過APScheduler、Windows任務計劃還是cron。但是,“任務準時啟動了”和“任務正確完成了”是兩件完全不同的事情。
在生產(chǎn)環(huán)境中,你永遠無法假設(shè)腳本每次運行都一帆風順。網(wǎng)絡抖動、API限流、數(shù)據(jù)庫連接池耗盡、磁盤寫滿、第三方服務故障……任何一個意外都可能導致任務失敗。而最糟糕的體驗不是任務失敗本身,而是幾天后才發(fā)現(xiàn)任務失敗了——數(shù)據(jù)缺失、報表未發(fā)、緩存未更新,修復成本指數(shù)級上升。
監(jiān)控與告警,正是填補“任務啟動”與“任務成功”之間鴻溝的關(guān)鍵技術(shù)。它讓機器人具備了自我感知能力:當自己出問題時,能夠主動“呼救”,而不是沉默地躺倒。
本文將系統(tǒng)性地介紹如何為Python腳本構(gòu)建一個輕量級但完善的監(jiān)控告警體系。我們會從最基礎(chǔ)的返回碼檢查開始,逐步深入到帶重試的監(jiān)控裝飾器、日志結(jié)構(gòu)化,最后實現(xiàn)通過郵件、釘釘機器人和企業(yè)微信機器人發(fā)送告警。讀完這篇文章,你將能夠為任何Python自動化任務套上一層可靠的“生命體征監(jiān)測儀”。
一、監(jiān)控的基本思想:從“執(zhí)行”到“驗證”
1.1 任務的“健康”定義
對于一個定時腳本,什么叫“正常運行”?我們需要定義三個層次的健康標準:
| 層級 | 含義 | 檢測方式 |
|---|---|---|
| 存活 | 進程沒有被殺死,能夠啟動 | 進程檢查、心跳文件 |
| 執(zhí)行 | 任務完成了主要邏輯,沒有拋出異常 | try-except捕獲 |
| 正確 | 任務不僅跑完了,而且結(jié)果符合預期(如數(shù)據(jù)條數(shù)正確、API響應成功) | 斷言/業(yè)務校驗 |
存活是操作系統(tǒng)或進程管理器關(guān)心的;執(zhí)行是腳本內(nèi)部try塊能覆蓋的;正確則需要我們在腳本中顯式編寫業(yè)務校驗邏輯。
1.2 監(jiān)控數(shù)據(jù)的來源:日志是金礦
所有監(jiān)控告警系統(tǒng)都離不開日志。一個好的日志應該包含:
- 任務名稱/ID
- 開始時間、結(jié)束時間、耗時
- 關(guān)鍵步驟的狀態(tài)(成功/失敗/部分成功)
- 錯誤類型和堆棧(僅失敗時)
- 業(yè)務指標(如處理記錄數(shù)、API調(diào)用次數(shù))
結(jié)構(gòu)化日志(如JSON格式)比純文本更容易被自動化工具解析。Python的logging模塊配合python-json-logger可以輕松實現(xiàn)。
二、腳本內(nèi)監(jiān)控:從被動捕獲到主動探測
2.1 基礎(chǔ)異常捕獲與返回碼
最原始的監(jiān)控方式:在腳本主入口捕獲所有異常,并根據(jù)結(jié)果返回不同的退出碼(exit code)。
# script_with_exit_code.py
import sys
import logging
logging.basicConfig(level=logging.INFO)
def main():
# 業(yè)務邏輯
pass
if __name__ == "__main__":
try:
main()
print("SUCCESS")
sys.exit(0) # 0 表示成功
except Exception as e:
logging.exception("任務執(zhí)行失敗")
sys.exit(1) # 非0表示失敗
上層調(diào)度器(如APScheduler、cron)可以根據(jù)退出碼判斷任務是否成功。但這種方式太粗糙——你只能知道“失敗了”,卻不知道失敗的原因、發(fā)生在哪個步驟。
2.2 帶監(jiān)控裝飾器的增強方案
更優(yōu)雅的方式是使用裝飾器統(tǒng)一包裝監(jiān)控邏輯,包括執(zhí)行時間記錄、重試、告警觸發(fā)。
import functools
import time
import logging
from typing import Callable, Any
logger = logging.getLogger(__name__)
def monitor_job(job_name: str,
alert_on_failure: bool = True,
max_retries: int = 0,
retry_delay: int = 5):
"""
監(jiān)控任務執(zhí)行的裝飾器
:param job_name: 任務名稱,用于標識
:param alert_on_failure: 失敗時是否觸發(fā)告警
:param max_retries: 失敗后自動重試次數(shù)
:param retry_delay: 重試間隔(秒)
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(*args, **kwargs) -> Any:
start_time = time.time()
last_exception = None
for attempt in range(max_retries + 1):
try:
result = func(*args, **kwargs)
elapsed = time.time() - start_time
logger.info(f"任務[{job_name}] 執(zhí)行成功,耗時 {elapsed:.2f}秒")
# 可選:成功時也可以發(fā)送通知(對于關(guān)鍵任務)
# if notify_on_success:
# send_success_alert(job_name, elapsed)
return result
except Exception as e:
last_exception = e
elapsed = time.time() - start_time
logger.error(f"任務[{job_name}] 執(zhí)行失敗 (嘗試 {attempt+1}/{max_retries+1}): {str(e)}")
if attempt < max_retries:
time.sleep(retry_delay)
else:
# 重試耗盡,觸發(fā)告警
if alert_on_failure:
send_failure_alert(job_name, e, elapsed)
raise # 重新拋出異常
# 不會執(zhí)行到這里
return None
return wrapper
return decorator
# 使用示例
@monitor_job("每日數(shù)據(jù)同步", alert_on_failure=True, max_retries=2, retry_delay=10)
def sync_data():
# 業(yè)務邏輯,可能拋出異常
pass
這個裝飾器解決了三個問題:自動計時、自動重試、失敗告警觸發(fā)點。實際使用時,你可以在send_failure_alert函數(shù)中集成郵件、釘釘、企業(yè)微信等具體通知方式。
2.3 業(yè)務正確性校驗:斷言與健康檢查
有些任務雖然沒拋異常,但結(jié)果可能不符合預期。例如:爬蟲明明執(zhí)行完了,但抓到的數(shù)據(jù)條數(shù)為0;數(shù)據(jù)庫清理任務執(zhí)行了,但應該刪除的記錄一條都沒刪。
這時需要在業(yè)務邏輯末尾加入顯式校驗:
def sync_orders():
orders = fetch_orders_from_api()
if len(orders) == 0:
# 這不是異常,但業(yè)務上屬于異常情況
raise BusinessWarning("未獲取到任何訂單,可能API變更或權(quán)限失效")
inserted = db.insert_many(orders)
if inserted < len(orders) * 0.9:
raise BusinessWarning(f"插入成功率過低: {inserted}/{len(orders)}")
# 一切正常
return {"total": len(orders), "inserted": inserted}
將BusinessWarning定義為自定義異常,但監(jiān)控裝飾器同樣能捕獲并觸發(fā)告警。
三、告警通道實戰(zhàn):郵件、釘釘、企業(yè)微信
當任務失敗時,我們需要將信息推送到相關(guān)人員能立即看到的地方。以下是三種最常用的告警通道的Python實現(xiàn)。
3.1 郵件告警(適合傳統(tǒng)團隊或正式報告)
郵件告警適合非即時但需要留檔的場景。使用Python內(nèi)置的smtplib即可。
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header
import traceback
def send_email_alert(subject: str, body: str, to_emails: list):
"""發(fā)送郵件告警"""
smtp_server = "smtp.example.com" # 替換為實際SMTP服務器
smtp_port = 465 # SSL端口
sender_email = "alerts@yourdomain.com"
sender_password = "your_password"
msg = MIMEMultipart()
msg['From'] = sender_email
msg['To'] = ", ".join(to_emails)
msg['Subject'] = Header(subject, 'utf-8')
msg.attach(MIMEText(body, 'plain', 'utf-8'))
try:
with smtplib.SMTP_SSL(smtp_server, smtp_port) as server:
server.login(sender_email, sender_password)
server.sendmail(sender_email, to_emails, msg.as_string())
print("郵件告警發(fā)送成功")
except Exception as e:
print(f"郵件發(fā)送失敗: {e}")
def send_failure_alert(job_name, exception, elapsed):
subject = f"[告警] 任務失敗: {job_name}"
body = f"""
任務名稱: {job_name}
執(zhí)行耗時: {elapsed:.2f}秒
失敗時間: {time.strftime('%Y-%m-%d %H:%M:%S')}
異常類型: {type(exception).__name__}
異常信息: {str(exception)}
詳細堆棧:
{traceback.format_exc()}
"""
send_email_alert(subject, body, ["ops@yourcompany.com", "oncall@yourcompany.com"])
注意:不要在代碼中硬編碼密碼,應使用環(huán)境變量或密鑰管理服務(如HashiCorp Vault、AWS Secrets Manager)。
3.2 釘釘機器人告警(國內(nèi)團隊首選)
釘釘群機器人是非常流行的告警渠道,免費且實時。步驟如下:
- 在釘釘群中添加“自定義機器人”,獲得webhook URL。
- 設(shè)置安全選項(推薦加簽或IP白名單)。
- 使用Python發(fā)送POST請求。
import requests
import json
import time
import hmac
import hashlib
import base64
from urllib.parse import quote_plus
def send_dingtalk_alert(job_name, exception, elapsed, webhook_url, secret=None):
"""發(fā)送釘釘機器人告警(支持加簽)"""
headers = {'Content-Type': 'application/json'}
# 如果開啟了加簽,需要生成時間戳和簽名
timestamp = str(round(time.time() * 1000))
sign = ""
if secret:
string_to_sign = f"{timestamp}\n{secret}"
hmac_code = hmac.new(
secret.encode('utf-8'),
string_to_sign.encode('utf-8'),
digestmod=hashlib.sha256
).digest()
sign = base64.b64encode(hmac_code).decode('utf-8')
webhook_url = f"{webhook_url}×tamp={timestamp}&sign={sign}"
# 構(gòu)造消息內(nèi)容(Markdown格式)
markdown_text = f"""## ?? 任務失敗告警
- **任務名稱**: {job_name}
- **執(zhí)行耗時**: {elapsed:.2f}秒
- **異常類型**: {type(exception).__name__}
- **異常信息**: {str(exception)}
- **詳情**: 請查看完整日志
"""
payload = {
"msgtype": "markdown",
"markdown": {
"title": f"任務失敗: {job_name}",
"text": markdown_text
},
"at": {
"atMobiles": ["13800000000"], # 可選:@指定手機號
"isAtAll": False
}
}
response = requests.post(webhook_url, headers=headers, data=json.dumps(payload), timeout=5)
if response.status_code != 200:
print(f"釘釘告警發(fā)送失敗: {response.text}")
# 使用示例(webhook_url從環(huán)境變量獲?。?
# send_dingtalk_alert("數(shù)據(jù)同步", Exception("連接超時"), 30.5, webhook_url, secret)
3.3 企業(yè)微信機器人告警(適合使用企業(yè)微信的公司)
企業(yè)微信機器人同樣通過webhook發(fā)送,支持文本、markdown、圖文等格式。
def send_wecom_alert(job_name, exception, elapsed, webhook_url):
"""發(fā)送企業(yè)微信機器人告警"""
headers = {'Content-Type': 'application/json'}
# 企業(yè)微信markdown消息長度限制4096字節(jié)
content = f"""## <font color="warning">任務失敗告警</font>
> **任務名稱**: {job_name}
> **執(zhí)行耗時**: {elapsed:.2f}秒
> **異常類型**: {type(exception).__name__}
> **異常信息**: {str(exception)}
> 請及時處理:[查看日志](https://your-log-platform.com)
"""
payload = {
"msgtype": "markdown",
"markdown": {
"content": content
}
}
response = requests.post(webhook_url, headers=headers, json=payload, timeout=5)
if response.status_code != 200:
print(f"企業(yè)微信告警發(fā)送失敗: {response.text}")
3.4 統(tǒng)一告警接口:便于切換和擴展
在實際項目中,你可能需要支持多種告警渠道,甚至動態(tài)切換。建議封裝一個統(tǒng)一的告警接口:
from abc import ABC, abstractmethod
class AlertChannel(ABC):
@abstractmethod
def send(self, job_name: str, exception: Exception, elapsed: float):
pass
class DingTalkChannel(AlertChannel):
def __init__(self, webhook_url: str, secret: str = None):
self.webhook_url = webhook_url
self.secret = secret
def send(self, job_name: str, exception: Exception, elapsed: float):
# 調(diào)用上面的 send_dingtalk_alert 函數(shù)
send_dingtalk_alert(job_name, exception, elapsed, self.webhook_url, self.secret)
class EmailChannel(AlertChannel):
def __init__(self, smtp_config: dict, to_emails: list):
self.smtp_config = smtp_config
self.to_emails = to_emails
def send(self, job_name: str, exception: Exception, elapsed: float):
# 郵件發(fā)送邏輯
pass
# 使用配置
channels = [
DingTalkChannel(os.getenv("DINGTALK_WEBHOOK"), os.getenv("DINGTALK_SECRET")),
EmailChannel(smtp_config, ["admin@example.com"])
]
def send_failure_alert(job_name, exception, elapsed):
for channel in channels:
try:
channel.send(job_name, exception, elapsed)
except Exception as e:
logger.error(f"告警通道發(fā)送失敗: {e}")
這樣,當需要增加新的告警方式(如飛書、Slack、Telegram)時,只需實現(xiàn)AlertChannel接口即可。
四、集成到APScheduler:完整的監(jiān)控閉環(huán)
有了上述監(jiān)控裝飾器和告警通道,我們將其無縫集成到APScheduler中,形成生產(chǎn)級的任務監(jiān)控體系。
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.executors.pool import ThreadPoolExecutor
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 告警通道配置(使用環(huán)境變量)
alert_channels = [
DingTalkChannel(os.getenv("DINGTALK_WEBHOOK"), os.getenv("DINGTALK_SECRET")),
# 可以再加郵件通道
]
def send_failure_alert(job_name, exception, elapsed):
for ch in alert_channels:
try:
ch.send(job_name, exception, elapsed)
except Exception as e:
logger.error(f"告警發(fā)送失敗: {e}")
# 監(jiān)控裝飾器(復用之前的實現(xiàn))
def monitored_job(job_name):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
try:
result = func(*args, **kwargs)
logger.info(f"Job {job_name} succeeded in {time.time()-start:.2f}s")
return result
except Exception as e:
elapsed = time.time() - start
logger.exception(f"Job {job_name} failed: {e}")
send_failure_alert(job_name, e, elapsed)
raise
return wrapper
return decorator
# 實際業(yè)務任務
@monitored_job("daily_data_sync")
def daily_data_sync():
# 業(yè)務邏輯
pass
@monitored_job("cleanup_temp_files")
def cleanup_temp_files():
pass
# APScheduler配置
jobstores = {
'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')
}
executors = {
'default': ThreadPoolExecutor(max_workers=10)
}
scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, timezone='Asia/Shanghai')
# 添加任務
scheduler.add_job(daily_data_sync, 'cron', hour=2, minute=0, id='daily_sync')
scheduler.add_job(cleanup_temp_files, 'cron', hour=3, minute=0, id='cleanup')
scheduler.start()
這樣,每個任務執(zhí)行失敗時都會自動觸發(fā)釘釘/郵件告警,同時保留了APScheduler本身的持久化和重試能力(注意:APScheduler自帶的任務重試機制與裝飾器重試可能會重復,建議只使用其中一種)。
五、高級監(jiān)控策略:心跳、健康檢查與分級告警
5.1 心跳機制:檢測“任務沒有跑”
上述監(jiān)控只覆蓋了“任務啟動了但失敗了”的場景。但如果調(diào)度器本身掛了,或者任務因為某種原因根本沒有被觸發(fā)(例如cron服務停止),你什么告警都收不到——這反而更危險。
心跳機制是解決此問題的經(jīng)典方案:每個任務在成功執(zhí)行后,向某個外部系統(tǒng)寫入一個時間戳(如Redis、數(shù)據(jù)庫或文件)。另一個獨立的監(jiān)控進程(可以是另一個輕量級腳本)定期檢查這個時間戳,如果距離上次心跳超過預期周期+容忍閾值,則發(fā)出“任務遺漏”告警。
# 在任務成功結(jié)束時寫入心跳
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
def report_heartbeat(job_name):
r.set(f"heartbeat:{job_name}", int(time.time()))
@monitored_job("daily_sync")
def daily_sync():
# ... 業(yè)務邏輯
report_heartbeat("daily_sync")
獨立的監(jiān)控腳本(每5分鐘運行一次):
def check_heartbeats():
expected_intervals = {
"daily_sync": 86400, # 期望每天一次
"hourly_clean": 3600
}
now = time.time()
for job_name, interval in expected_intervals.items():
last_beat = r.get(f"heartbeat:{job_name}")
if last_beat is None:
send_alert(f"任務{(diào)job_name}從未執(zhí)行過")
else:
elapsed = now - int(last_beat)
if elapsed > interval + 3600: # 容忍1小時
send_alert(f"任務{(diào)job_name}心跳超時,已{elapsed//3600}小時未執(zhí)行")
這個檢查腳本本身也需要被監(jiān)控(可以用系統(tǒng)級cron或任務計劃程序運行)。
5.2 分級告警:避免告警疲勞
如果失敗就告警,夜間一個臨時網(wǎng)絡抖動可能導致所有人被叫醒。因此需要分級策略:
| 級別 | 觸發(fā)條件 | 通知方式 | 接收人 |
|---|---|---|---|
| INFO | 任務成功但有輕微異常(如重試后成功) | 日志,不推送 | 無 |
| WARN | 任務失敗但已自動重試成功 | 僅記錄到監(jiān)控系統(tǒng) | 值班看板 |
| ERROR | 任務最終失敗 | 釘釘/企微群消息 | 開發(fā)組 |
| CRITICAL | 連續(xù)失敗3次或心跳丟失 | 電話/短信 | 運維主管 |
實現(xiàn)時可以在監(jiān)控裝飾器中增加失敗計數(shù)緩存(如Redis計數(shù)器),連續(xù)失敗達到閾值后才升級告警。
def send_escalated_alert(job_name, exception, elapsed):
# 從Redis獲取連續(xù)失敗次數(shù)
key = f"fail_count:{job_name}"
fail_count = r.incr(key)
r.expire(key, 3600) # 1小時衰減
if fail_count >= 3:
# 發(fā)送嚴重告警(釘釘+短信)
send_dingtalk_alert(job_name, exception, elapsed, webhook, at_mobiles=['manager'])
send_sms_alert(...)
elif fail_count >= 1:
# 普通告警
send_dingtalk_alert(job_name, exception, elapsed, webhook)
5.3 結(jié)構(gòu)化日志與ELK集成
當任務數(shù)量增多,告警信息需要結(jié)合上下文日志排查。建議所有腳本輸出結(jié)構(gòu)化日志(JSON格式),然后通過Filebeat或Fluentd發(fā)送到ELK(Elasticsearch, Logstash, Kibana)或Loki。告警消息中攜帶日志查詢鏈接,讓接收者一鍵查看完整堆棧。
import json_logging
import logging
json_logging.init_non_web(enable_json=True)
logger = logging.getLogger(__name__)
# 之后的所有日志都會輸出JSON格式
logger.info("任務開始", extra={"job_name": "sync", "step": "fetch"})
在告警消息中加上Kibana鏈接:https://kibana.yourcompany.com/app/discover#/?_a=(query:(language:kuery,query:'job_name:"daily_sync" AND level:"ERROR"'))
六、部署監(jiān)控腳本的注意事項
6.1 監(jiān)控腳本本身的可靠性
監(jiān)控系統(tǒng)不能成為新的單點故障。建議:
- 將心跳檢查腳本也加入調(diào)度,并由上級監(jiān)控(如systemd或Windows服務)守護。
- 重要告警通道(如短信)應有備用渠道(例如同時使用釘釘和郵件)。
- 避免告警風暴:同一任務在短時間內(nèi)失敗多次,只發(fā)送第一條告警,后續(xù)靜默一段時間。
6.2 配置管理
所有webhook URL、密碼、SMTP配置都應該通過環(huán)境變量或配置中心(如Consul、etcd)注入,不要硬編碼在代碼倉庫中。示例:
import os
DINGTALK_WEBHOOK = os.getenv("DINGTALK_WEBHOOK")
if not DINGTALK_WEBHOOK:
raise ValueError("請設(shè)置環(huán)境變量 DINGTALK_WEBHOOK")
6.3 測試告警通道
在部署到生產(chǎn)前,務必進行告警注入測試:臨時讓任務拋出一個異常,確認團隊能收到告警,且消息內(nèi)容包含足夠的信息(時間、任務名、異常堆棧)。
七、總結(jié)與最佳實踐清單
我們從最基礎(chǔ)的異常捕獲開始,逐步構(gòu)建了一個包含自動重試、分級告警、心跳檢測和結(jié)構(gòu)化日志的完整監(jiān)控體系。核心要點總結(jié)如下:
| 層次 | 技術(shù)方案 | 解決的問題 |
|---|---|---|
| 進程級 | 系統(tǒng)守護(systemd/supervisord) | 進程崩潰后自動重啟 |
| 任務級 | 監(jiān)控裝飾器 + 重試 | 捕獲執(zhí)行失敗并重試 |
| 業(yè)務級 | 顯式斷言/校驗 | 檢測“靜默錯誤” |
| 告警級 | 釘釘/企微/郵件通道 | 及時通知相關(guān)人員 |
| 心跳級 | 獨立的心跳檢查腳本 | 檢測任務完全未執(zhí)行 |
| 可觀測性 | 結(jié)構(gòu)化日志 + ELK | 快速定位失敗根因 |
最佳實踐清單(上線前對照檢查)
- 每個關(guān)鍵任務都有
try-except或監(jiān)控裝飾器包裝。 - 配置了至少一種實時告警通道(釘釘/企微)。
- 告警消息包含:任務名稱、失敗時間、異常類型、異常信息、日志查詢鏈接。
- 實現(xiàn)了連續(xù)失敗抑制(避免告警風暴)。
- 為核心任務配置了心跳監(jiān)控。
- 所有敏感配置通過環(huán)境變量注入。
- 測試過告警通道的可用性。
- 監(jiān)控腳本本身被上級進程守護。
以上就是Python腳本構(gòu)建輕量級監(jiān)控告警體系(郵件/釘釘/企業(yè)微信)的詳細內(nèi)容,更多關(guān)于Python監(jiān)控告警系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!
- docker-compose搭建prometheus+grafana+釘釘告警
- zabbix?6.0?使用?JavaScript?發(fā)出釘釘告警的方法
- SpringBoot-Admin實現(xiàn)微服務監(jiān)控+健康檢查+釘釘告警
- Zabbix 配置釘釘告警功能的實現(xiàn)代碼
- SpringBoot AOP實現(xiàn)釘釘+企業(yè)微信雙渠道異常告警
- C#調(diào)用釘釘API發(fā)送通知的實現(xiàn)示例
- Java對接釘釘考勤記錄的完整步驟
- Python實現(xiàn)釘釘自動化完整指南
- 基于Python實現(xiàn)開發(fā)釘釘通知機器人
- Golang接入釘釘通知的示例代碼
- Python腳本如何實現(xiàn)監(jiān)控服務器日志異常并通過釘釘推送告警?
相關(guān)文章
Python 用__new__方法實現(xiàn)單例的操作
這篇文章主要介紹了Python 用__new__方法實現(xiàn)單例的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Python2和Python3讀取文本文件的區(qū)別及說明
這篇文章主要介紹了Python2和Python3讀取文本文件的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
python函數(shù)參數(shù),名稱空間,以及函數(shù)嵌套
這篇文章主要給大家介紹python函數(shù)參數(shù)、名稱空間、以及函數(shù)嵌套的相關(guān)資料,想具體了解的小伙伴請和小編一起進入下面文章內(nèi)容吧2021-10-10

