第一章:屬性訪問的基礎原理

1.1 屬性訪問的起源與Python演進

屬性訪問的概念源於1990年代的Smalltalk動態屬性系統,它強調對象的統一接口。Python的@property由Guido van Rossum在Python 2.2中通過描述符協議正式引入,當時旨在簡化getter/setter的 boilerplate代碼。到Python 3起,property成為內置裝飾器,支持f-string集成和異步擴展。近年來,Python 3.7的dataclasses自動生成屬性;在3.10的結構化模式匹配下,屬性解包更流暢;3.12的解釋器加速則降低了屬性調用的納秒級延遲。這些演進讓屬性訪問從輔助機制轉向核心範式,尤其在微框架和AI模型時代。

從函數視角,屬性訪問是“動態屬性函數”:它將方法偽裝為屬性,在讀寫時觸發函數執行。這統一了數據與計算的接口,促進封裝。

1.2 基本語法:@property的透明儀式

屬性訪問的核心是@property裝飾器,它將方法轉化為讀屬性:

class Circle:
    def __init__(self, radius: float):
        self._radius = radius  # 私有存儲
    
    @property
    def radius(self) -> float:
        """getter:讀屬性,驗證正值。"""
        if self._radius < 0:
            raise ValueError("半徑必須正")
        return self._radius
    
    @radius.setter
    def radius(self, value: float):
        """setter:寫屬性,驗證並更新。"""
        if value < 0:
            raise ValueError("半徑必須正")
        self._property = value
    
    @property
    def area(self) -> float:
        """只讀屬性:計算面積。"""
        import math
        return math.pi * self.radius ** 2

# 使用如普通屬性
c = Circle(5.0)
print(c.radius)     # 5.0,觸發getter
print(c.area)       # 78.53981633974483,計算屬性
c.radius = 10.0     # 觸發setter
print(c.radius)     # 10.0
try:
    c.radius = -1    # ValueError
except ValueError as e:
    print(e)        # 半徑必須正

這裏,c.radius隱式調用getter。@property僅需一個方法,setter/deleter可選。屬性訪問支持繼承覆蓋。

私有約定:_radius強調封裝,property提供控制。

class Sphere(Circle):
    @property
    def volume(self) -> float:
        """擴展:體積計算。"""
        return (4/3) * 3.14159 * self.radius ** 3
    
    @Circle.radius.setter  # 覆蓋基類setter
    def radius(self, value: float):
        super().radius.fset(self, value * 1.1)  # 擴展驗證

s = Sphere(5.0)
print(s.volume)  # 523.5987755982989
s.radius = 10.0  # 實際存儲11.0
print(s.radius)  # 11.0

屬性執行順序:在繼承中,按MRO從派生類到基類查找,支持多繼承協作。

1.3 描述符的綁定魔力:property的底層守護

property是描述符,實現__get__、set、__delete__綁定實例:

class PropertyDemo:
    def __init__(self, name: str):
        self._name = name
    
    def _get_name(self):
        return self._name.upper()
    
    def _set_name(self, value: str):
        if not value:
            raise ValueError("名稱不能為空")
        self._name = value
    
    name = property(_get_name, _set_name, doc="名稱屬性")
    
    # 等價於裝飾器形式
    @property
    def age(self):
        return self._age
    
    @age.deleter
    def age(self):
        del self._age

demo = PropertyDemo("Alice")
print(demo.name)  # ALICE,__get__
demo.name = "Bob" # __set__
print(demo.name)  # BOB
del demo.name     # __delete__

property對象封裝getter/setter/deleter。手動創建支持複雜綁定。高級用法:property作為代理,委託驗證邏輯。

1.4 參數處理:屬性訪問的隱式擴展

屬性訪問無顯式參數,但setter可接收值:

class Config:
    def __init__(self):
        self._settings = {}
    
    @property
    def host(self) -> str:
        return self._settings.get('host', 'localhost')
    
    @host.setter
    def host(self, value: str):
        if not value:
            raise ValueError("主機不能為空")
        self._settings['host'] = value
    
    @property
    def debug(self) -> bool:
        return self._settings.get('debug', False)
    
    @debug.setter
    def debug(self, value: bool):
        self._settings['debug'] = bool(value)

cfg = Config()
print(cfg.host)    # localhost
cfg.host = "127.0.0.1"
print(cfg.host)    # 127.0.0.1
cfg.debug = True
print(cfg.debug)   # True

默認值陷阱:getter中get()避免KeyError。屬性訪問的防禦:setter驗證類型/範圍。

class SafeConfig:
    @property
    def port(self) -> int:
        return self._port
    
    @port.setter
    def port(self, value):
        if not isinstance(value, int) or not 1 <= value <= 65535:
            raise ValueError("端口無效")
        self._port = value

這體現了屬性的智能:透明訪問,隱式函數。

1.5 繼承中的屬性訪問:super()的屬性交響

繼承時,屬性支持覆蓋與鏈式調用:

class Vehicle:
    def __init__(self, speed: int):
        self._speed = speed
    
    @property
    def speed(self) -> int:
        return self._speed
    
    @speed.setter
    def speed(self, value: int):
        self._speed = max(0, value)

class Car(Vehicle):
    def __init__(self, speed: int, fuel: float):
        super().__init__(speed)
        self._fuel = fuel
    
    @property
    def speed(self) -> int:
        return super().speed * 1.2  # 擴展getter
    
    @speed.setter
    def speed(self, value: int):
        super().speed.fset(self, value)  # 調用基類setter
        self._fuel -= value * 0.01  # 擴展邏輯

c = Car(50, 100.0)
print(c.speed)  # 60
c.speed = 100
print(c.speed, c._fuel)  # 120 0.0(簡化)

super()在property中通過fget/fset訪問,支持多繼承。無super()將忽略基類邏輯。

多繼承示例:

class Electric:
    @property
    def battery(self) -> int:
        return self._battery
    
    @battery.setter
    def battery(self, value: int):
        self._battery = min(100, max(0, value))

class Hybrid(Electric, Vehicle):
    @property
    def speed(self) -> int:
        return super(Electric, self).speed  # 顯式MRO
    
    @speed.setter
    def speed(self, value: int):
        super().speed.fset(self, value)

h = Hybrid(60, 80)
print(h.battery)  # 假設0

C3線性化確保屬性無歧義。

第二章:屬性訪問與函數的互動機制

2.1 屬性訪問作為高階函數:動態屬性的閉包捕獲

property可模擬高階,捕獲狀態返回屬性:

class DynamicProperty:
    def __init__(self, factory):
        self.factory = factory
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return self.factory(obj)

class Configurator:
    def __init__(self, env: str = "prod"):
        self._env = env
    
    env_prop = DynamicProperty(lambda obj: obj._env.upper())

cfg = Configurator("dev")
print(cfg.env_prop)  # DEV,動態getter

融合閉包:屬性注入動態持久性。

2.2 裝飾屬性訪問:元函數的屬性增強

用裝飾器包裝property:

def validated_property(getter):
    def validator(self):
        value = getter(self)
        if value < 0:
            raise ValueError("負值無效")
        return value
    return property(validator)

class ValidatedClass:
    def __init__(self, val: int):
        self._val = val
    
    @validated_property
    @property
    def value(self):
        return self._val

v = ValidatedClass(10)
print(v.value)  # 10
try:
    v = ValidatedClass(-5)
    print(v.value)
except ValueError as e:
    print(e)  # 負值無效

裝飾器不干擾綁定,適用於屬性日誌/驗證。

2.3 生成器屬性訪問:惰性屬性流動

property集成生成器延遲屬性:

class LazyProperty:
    def __init__(self, func):
        self.func = func
        self.__doc__ = func.__doc__
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        value = self.func(obj)
        setattr(obj, self.func.__name__, value)
        return value

class DataClass:
    @LazyProperty
    def heavy_data(self):
        """生成器:模擬昂貴計算。"""
        print("計算中...")
        return [i**2 for i in range(1000)]

d = DataClass()
print(len(d.heavy_data))  # 計算中... \n 1000,緩存後快速

生成器property保持惰性,節省資源。

2.4 lambda嵌入屬性訪問:微型屬性行為

lambda簡化property:

class QuickClass:
    def __init__(self, x: int):
        self._x = x
    
    @property
    def doubled(self) -> int:
        return (lambda y: y * 2)(self._x)
    
    @property
    def conditional(self) -> str:
        return "Even" if self._x % 2 == 0 else (lambda: "Odd")()

q = QuickClass(4)
print(q.doubled)    # 8
print(q.conditional)  # Even

適合內聯屬性。

2.5 部分應用:functools.partial與屬性訪問

from functools import partial

class PartialProperty:
    def __init__(self, base_func, *args, **kwargs):
        self.base = partial(base_func, *args, **kwargs)
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return self.base(obj)

def compute_power(obj, exp=2):
    return obj._val ** exp

class PowerClass:
    def __init__(self, val: int):
        self._val = val
    
    square = PartialProperty(compute_power, exp=2)
    cube = PartialProperty(compute_power, exp=3)

p = PowerClass(3)
print(p.square)  # 9
print(p.cube)    # 27

partial增強屬性複用。

第三章:動態屬性訪問:運行時門衞鑄造

3.1 setattr的動態屬性綁定

運行時添加property:

class DynamicClass:
    def __init__(self, name: str):
        self._name = name

def get_name(self):
    return self._name

def set_name(self, value):
    self._name = value

DynamicClass.name = property(get_name, set_name)
d = DynamicClass("Dynamic")
print(d.name)  # Dynamic
d.name = "New"
print(d.name)  # New

使用types安全綁定:

import types

def safe_get_age(self):
    return self._age if hasattr(self, '_age') else 0

def safe_set_age(self, value):
    if value < 0:
        raise ValueError
    self._age = value

DynamicClass.age = property(safe_get_age, safe_set_age)
print(DynamicClass().age)  # 0

動態擴展屬性接口。

3.2 exec的字符串屬性注入

source = '''
def get_dynamic(self):
    return self._dynamic.upper()

def set_dynamic(self, value):
    self._dynamic = value
'''

local_ns = {}
exec(source, globals(), local_ns)
DynamicClass.dynamic = property(local_ns['get_dynamic'], local_ns['set_dynamic'])

d = DynamicClass("exec")
print(d.dynamic)  # EXEC
d.dynamic = "new"
print(d.dynamic)  # NEW

結合ast解析安全:

import ast

class SafePropertyInjector(ast.NodeVisitor):
    def visit_FunctionDef(self, node):
        if node.name.startswith('get_'):
            # 驗證self參數
            if node.args.args[0].arg != 'self':
                raise ValueError("getter需self")
        self.generic_visit(node)

tree = ast.parse(source)
injector = SafePropertyInjector()
injector.visit(tree)

防惡意注入。

3.3 元類的屬性訪問攔截

元類重寫__getattribute__控制屬性:

class ValidatingMeta(type):
    def __getattribute__(cls, name):
        if name.startswith('prop_'):
            print(f"訪問屬性: {name}")
        return super().__getattribute__(name)

class ValidClass(metaclass=ValidatingMeta):
    def __init__(self, value: int):
        self._value = value
    
    @property
    def prop_value(self):
        if self._value < 0:
            raise ValueError("正值")
        return self._value

print(ValidClass(10).prop_value)  # 訪問屬性: prop_value \n 10

元類使屬性“自省”。

3.4 工廠函數的屬性訪問生成器

def property_factory(getter_func, setter_func=None):
    return property(getter_func, setter_func)

def make_getter(base):
    def getter(self):
        return self._base + base
    return getter

AddFive = property_factory(make_getter(5))
class BehaviorClass:
    def __init__(self, v):
        self._base = v

BehaviorClass.added = AddFive
b = BehaviorClass(10)
print(b.added)  # 15

動態注入屬性。

第四章:屬性訪問的特殊擴展

4.1 __new__與屬性訪問的協同

property輔助__new__預分配:

class CachedProperty:
    def __init__(self, func):
        self.func = func
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        value = self.func(obj)
        setattr(obj, self.func.__name__, value)
        return value

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    @CachedProperty
    def config(self):
        return {"mode": "singleton"}

s = Singleton()
print(s.config)  # {'mode': 'singleton'},惰性

property管理全局屬性。

4.2 __init_subclass__的子類屬性註冊

class Registry(type):
    props = {}
    
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if hasattr(cls, 'special_prop'):
            Registry.props[cls.__name__] = cls.special_prop

class BaseClass(metaclass=Registry):
    @property
    def special_prop(self):
        return "Base"

class SubClass(BaseClass):
    @property
    def special_prop(self):
        return "Sub"

print(Registry.props['SubClass']().special_prop)  # 需實例:Sub

基類註冊子類屬性。

4.3 異步屬性訪問:async的透明協程

import asyncio

class AsyncProperty:
    def __init__(self, func):
        self.func = func
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(self.func(obj))

class AsyncClass:
    async def _get_async_data(self):
        await asyncio.sleep(0.1)
        return "Async Data"
    
    async_data = AsyncProperty(_get_async_data)

# 注意:同步上下文中運行
a = AsyncClass()
print(a.async_data)  # Async Data

異步property,IO友好。

4.4 上下文管理器中的屬性訪問

from contextlib import contextmanager

class ContextProperty:
    def __init__(self, func):
        self.func = func
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        with contextmanager(self.func)(obj):
            return obj._temp_value

@contextmanager
def managed_value(obj):
    obj._temp_value = "Managed"
    yield obj

class ContextClass:
    managed = ContextProperty(managed_value)
    
    def __init__(self):
        self._temp_value = None

with ContextClass() as ctx:  # 需調整為with語句使用
    print(ctx.managed)  # Managed

property驅動上下文屬性。

第五章:設計模式中的屬性訪問應用

5.1 觀察者模式:屬性的變更通知

class ObservableProperty:
    def __init__(self):
        self._value = None
        self.observers = []
    
    def __get__(self, obj, objtype=None):
        return self._value
    
    def __set__(self, obj, value):
        old = self._value
        self._value = value
        for obs in self.observers:
            obs.update(old, value)
    
    def add_observer(self, obs):
        self.observers.append(obs)

class Subject:
    value = ObservableProperty()
    
    def add_observer(self, obs):
        self.value.add_observer(obs)

class Observer:
    def update(self, old, new):
        print(f"從 {old} 變到 {new}")

s = Subject()
o = Observer()
s.add_observer(o)
s.value = 10  # 從 None 變到 10

property實現觀察。

5.2 代理模式:屬性的動態代理

class ProxyProperty:
    def __init__(self, target, attr_name):
        self.target = target
        self.attr_name = attr_name
    
    def __get__(self, obj, objtype=None):
        return getattr(self.target, self.attr_name)
    
    def __set__(self, obj, value):
        setattr(self.target, self.attr_name, value)

class ProxyClass:
    def __init__(self, target):
        self._target = target
        self.proxied_val = ProxyProperty(target, '_val')

class Target:
    def __init__(self, val):
        self._val = val

t = Target(42)
p = ProxyClass(t)
print(p.proxied_val)  # 42
p.proxied_val = 100
print(t._val)  # 100

property代理訪問。

5.3 訪問者模式:屬性的遍歷訪問

class VisitorProperty:
    def __init__(self, visitor):
        self.visitor = visitor
    
    def __get__(self, obj, objtype=None):
        return self.visitor.visit(obj)

class Element:
    @property
    def accept(self):
        return VisitorProperty(self)  # 需定義visitor

class Visitor:
    def visit(self, elem):
        return f"Visited {elem._data}"

class ConcreteElement(Element):
    def __init__(self, data):
        self._data = data

e = ConcreteElement("test")
v = Visitor()
e.accept = v.accept  # 動態
print(e.accept)  # Visited test

property支持訪問者。

5.4 狀態模式:屬性的狀態切換

class StateProperty:
    def __init__(self, states):
        self.states = states
        self.current = None
    
    def __get__(self, obj, objtype=None):
        return self.current
    
    def __set__(self, obj, state_name):
        if state_name in self.states:
            self.current = self.states[state_name]
        else:
            raise ValueError("無效狀態")

class Context:
    state = StateProperty({
        'idle': lambda: "Idle",
        'running': lambda: "Running"
    })

c = Context()
c.state = 'running'
print(c.state())  # Running

property管理狀態。

第六章:性能調優與基準剖析

6.1 屬性訪問開銷測量

import timeit

class SimpleProp:
    def __init__(self, val):
        self._val = val
    
    @property
    def value(self):
        return self._val

class ComplexProp:
    def __init__(self, val):
        self._val = val
    
    @property
    def value(self):
        temp = self._val ** 2
        return temp + len(str(temp))

setup = "from __main__ import SimpleProp, ComplexProp; obj = SimpleProp(10)"
print(timeit.timeit("obj.value", setup, number=1000000))  # ~0.05s
setup_complex = "from __main__ import ComplexProp; obj = ComplexProp(10)"
print(timeit.timeit("obj.value", setup_complex, number=1000000))  # ~0.15s

複雜property增加延遲。

6.2 緩存對屬性訪問的優化

class CachedProp:
    _cache = {}
    
    @property
    def value(self):
        key = id(self)
        if key not in self._cache:
            self._cache[key] = self._compute()
        return self._cache[key]
    
    def _compute(self):
        return sum(i for i in range(100))

c = CachedProp()
print(c.value)  # 計算
print(c.value)  # 緩存

實例級緩存加速。

6.3 lru_cache在屬性訪問中的應用

from functools import lru_cache

class LRUProp:
    @lru_cache(maxsize=128)
    @property
    def fib(self):
        n = self._n
        if n < 2:
            return n
        return self.fib + self.fib(n-1) + self.fib(n-2)  # 需調整為方法

# 更好:用方法
    def fib_method(self, n):
        if n < 2:
            return n
        return self.fib_method(n-1) + self.fib_method(n-2)
fib_prop = property(LRUProp.fib_method)

避免遞歸陷阱,用@lru_cache。

6.4 JIT加速:PyPy下的屬性訪問

PyPy JIT使ComplexProp.value ~0.08s,獲益近2倍。固定簽名利於JIT。

6.5 批量屬性訪問:視圖優化

class BatchProp:
    @property
    def batch_values(self):
        return [self._val * i for i in range(10)]

print(timeit.timeit("obj.batch_values", "obj = BatchProp(); obj._val=5", number=1000))

列表推導批量屬性。

第七章:真實世界案例剖析

7.1 ORM框架:Django的屬性字段

from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}" if hasattr(self, 'first_name') else self.name
    
    @property
    def is_active(self):
        return self.status == 'active'

# 查詢
user = User.objects.get(id=1)
print(user.full_name)  # 計算屬性

property簡化模型屬性。

7.2 配置管理:Pydantic的驗證屬性

from pydantic import BaseModel, validator

class Config(BaseModel):
    host: str = "localhost"
    port: int = 80
    
    @validator('port')
    def validate_port(cls, v):
        if not 1 <= v <= 65535:
            raise ValueError('端口無效')
        return v
    
    @property
    def url(self):
        return f"{self.host}:{self.port}"

cfg = Config(port=8080)
print(cfg.url)  # localhost:8080

property集成驗證。

7.3 GUI框架:Tkinter的綁定屬性

import tkinter as tk

class App:
    def __init__(self):
        self.root = tk.Tk()
        self._width = 800
    
    @property
    def width(self):
        return self._width
    
    @width.setter
    def width(self, value):
        self._width = value
        self.root.geometry(f"{value}x600")

app = App()
app.width = 1000  # 更新窗口
app.root.mainloop()

property驅動UI綁定。

7.4 科學計算:NumPy的視圖屬性

import numpy as np

class Vector:
    def __init__(self, data: np.ndarray):
        self._data = data
    
    @property
    def magnitude(self):
        return np.linalg.norm(self._data)
    
    @property
    def normalized(self):
        norm = self.magnitude
        return self._data / norm if norm != 0 else self._data

v = Vector(np.array([3, 4]))
print(v.magnitude)     # 5.0
print(v.normalized)    # [0.6 0.8]