在動態語言 Python 中,檢查變數是否已定義是常見需求(如配置檔載入、選參數處理、動態屬性存取)。本文系統整理四種核心方法、適用範圍、效能差異、最佳實務與陷阱避坑指南。
| 方法 | 適用作用域 | 回傳型別 | 效能 | 推薦場景 |
|---|---|---|---|---|
| 'var' in locals() | 區域 | bool | 快 | 函式內檢查區域變數 |
| 'var' in globals() | 全域 | bool | 快 | 模組層級檢查全域變數 |
| 'var' in vars(obj) | 物件屬性 | bool | 快 | 檢查物件/類別屬性 |
| try/except NameError | 任意 | 值/例外 | 較慢(例外開銷) | 不確定作用域、需取得值 |
| hasattr(obj, 'attr') | 物件屬性 | bool | 快 | 檢查物件屬性(含繼承) |
| getattr(obj, 'attr', default) | 物件屬性 | 值/default | 快 | 安全取得屬性值(首選) |
def demo():
x = 10
print('x' in locals()) # True
print('y' in locals()) # False
# 注意:locals() 在函式內回傳副本(CPython 實作細節),寫入不影響實際區域變數
locals()['z'] = 20
# print(z) # NameError! 實際未定義
demo()
GLOBAL_CONFIG = {"debug": True}
def check_config():
if 'GLOBAL_CONFIG' in globals():
print("全域配置存在:", globals()['GLOBAL_CONFIG'])
# 可動態新增全域變數
globals()['RUNTIME_FLAG'] = True
check_config()
print(RUNTIME_FLAG) # True
class Config:
def __init__(self):
self.host = "localhost"
self.port = 8080
cfg = Config()
print('host' in vars(cfg)) # True
print('timeout' in vars(cfg)) # False
# vars() 無參數等同 locals()
def func():
a = 1
print(vars() == locals()) # True
def get_var_safely(var_name, default=None):
"""通用安全取得變數值"""
try:
return eval(var_name)
except NameError:
return default
# 或直接在程式碼中
try:
value = some_variable
print(f"已定義: {value}")
except NameError:
print("未定義")
value = "default"
# config_default.py
DEBUG = False
PORT = 8080
HOST = "0.0.0.0"
# config_local.py (可選,不提交版控)
# DEBUG = True
# PORT = 3000
# main.py
from config_default import *
# 嘗試載入本地覆蓋
try:
from config_local import *
except ImportError:
pass
print(DEBUG, PORT, HOST) # 使用本地值或預設值
import os
import sys
# 環境變數檢查
database_url = os.getenv('DATABASE_URL')
if not database_url:
# 回退預設
database_url = "sqlite:///app.db"
# 命令列參數檢查
verbose = '--verbose' in sys.argv
config_file = 'config.yaml'
if '--config' in sys.argv:
idx = sys.argv.index('--config')
if idx + 1 < len(sys.argv):
config_file = sys.argv[idx + 1]
class Settings:
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def get(self, key, default=None):
"""安全取得屬性"""
return getattr(self, key, default)
def has(self, key):
"""檢查屬性存在"""
return hasattr(self, key)
settings = Settings(debug=True, port=8080)
print(settings.get('debug')) # True
print(settings.get('timeout', 30)) # 30 (預設值)
print(settings.has('port')) # True
# 僅在特定條件定義變數
import sys
if sys.platform == 'win32':
PLATFORM_CMD = 'cmd.exe'
else:
PLATFORM_CMD = '/bin/bash'
# 後續代碼安全使用
print(PLATFORM_CMD) # 保證已定義
import timeit
# 定義測試變數
test_var = 42
# 方法 1: in locals()
t1 = timeit.timeit("'test_var' in locals()", globals=globals(), number=100000)
# 方法 2: try/except
t2 = timeit.timeit("""
try:
test_var
except NameError:
pass
""", globals=globals(), number=100000)
# 方法 3: getattr (需物件)
obj = type('', (), {'test_var': 42})()
t3 = timeit.timeit("hasattr(obj, 'test_var')", globals=globals(), number=100000)
t4 = timeit.timeit("getattr(obj, 'test_var', None)", globals=globals(), number=100000)
print(f"in locals(): {t1:.4f}s")
print(f"try/except: {t2:.4f}s")
print(f"hasattr: {t3:.4f}s")
print(f"getattr: {t4:.4f}s")
in locals(): 0.012s (最快)
hasattr: 0.015s
getattr: 0.016s
try/except: 0.045s (例外開銷大,但未定義時才拋出)
| 陷阱 | 錯誤代碼 | 正確做法 |
|---|---|---|
| locals() 寫入不生效 | locals()['x'] = 1 | 直接賦值 x = 1 或用 globals() |
| 檢查全域用 locals() | 'x' in locals() (模組層級) | 模組層級 locals()==globals(),但語意用 globals() |
| vars() 無參數在類別方法中 | class A: def f(self): print(vars()) | vars() 等同 locals(),用 vars(self) 取實例屬性 |
| hasattr 捕捉所有例外 | hasattr 會吞沒 property/descriptor 內部錯誤 | Python 3.2+ 已修正,僅捕捉 AttributeError |
| eval 注入風險 | eval(user_input) | 絕不 eval 使用者輸入,用 getattr/字典查找 |
需要檢查變數是否定義
│
├─ 知道確切作用域?
│ ├─ 區域變數 (函式內) → 'var' in locals()
│ ├─ 全域變數 (模組層級) → 'var' in globals()
│ └─ 物件屬性 → hasattr(obj, 'attr') / getattr(obj, 'attr', default)
│
└─ 不確定作用域 / 需同時取得值
├─ 簡單腳本、除錯 → try/except NameError
└─ 庫代碼、效能敏感 → 明確作用域查找 (避免例外開銷)
Python 檢查變數定義的核心方法:區域用 locals()、全域用 globals()、物件屬性用 hasattr()/getattr()、通用捕捉用 try/except NameError。最佳實務:優先用 getattr(obj, key, default) 安全取值,避免例外控制流;明確作用域查找效能最佳;vars() 僅用於物件 __dict__ 檢查。理解各方法作用域、效能、副作用差異,能寫出更健壯、高效的動態代碼。