Python 中「是否為字串」看似簡單,卻藏著 str/bytes/unicode (Py2)、UserString、自訂類別等陷阱。本文對比三種判斷策略,推薦現代最佳實踐。
# Python 3:str 唯一字串型別
def is_str(obj):
return isinstance(obj, str)
# Python 2:basestring 涵蓋 str/unicode
def is_str_py2(obj):
return isinstance(obj, basestring)
def is_string_like(obj):
try:
obj.lower() + obj + ' ' # 嘗試字串操作
except Exception:
return False
else:
return True
UserString、pathlib.Path、自訂類別lower+__add__ 的物件皆通過)
from collections import UserString
s = UserString('abc')
print(isinstance(s, str)) # False (Py3)
print(is_string_like(s)) # True
# 實際應用:pathlib.Path 也通過鴨子檢查
from pathlib import Path
print(is_string_like(Path('/tmp'))) # True (支援 __str__、lower 等)
from collections.abc import Sequence
def is_str_abc(obj):
return isinstance(obj, str) or (isinstance(obj, Sequence) and hasattr(obj, 'lower'))
| 型別 | Python 2 | Python 3 |
|---|---|---|
| 位元組字串 | str | bytes |
| Unicode 字串 | unicode | str |
| 共同基類 | basestring | 無 (直接用 str) |
| UserString 位置 | UserString.UserString | collections.UserString |
isinstance(obj, str) (Py3) 或 isinstance(obj, basestring) (Py2)。配合 typing.Union[str, bytes] 註解。is_string_like,或更精確的 hasattr(obj, '__str__') + 試轉換。os.fspath(obj) (Py3.6+) 自動轉換 Path/字串/bytes。type(obj) == str(不支援繼承)、obj.__class__.__name__ == 'str'(脆弱)。
# LBYL (Look Before You Leap) - 非 Python 風格
if hasattr(obj, 'lower'):
result = obj.lower()
# EAFP (Easier to Ask Forgiveness than Permission) - Python 風格
try:
result = obj.lower()
except AttributeError:
handle_non_string(obj)
「嘗試轉換,失敗再處理」比「先檢查型別」更符合 Python 動態本質,也能自然支援未來擴充的類字串物件。