ZetCode

Python __get__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨 Python 的 __get__ 方法,它是描述器的核心特殊方法。我们将介绍基本用法、属性实现、方法绑定和实际示例。

基本定义

__get__ 方法是 Python 描述器协议的一部分。当访问作为描述器的属性时,会调用它。描述器允许自定义属性访问。

主要特性:它接受三个参数(selfinstanceowner),返回属性值,并用于实现属性、方法和其他属性访问模式。

基本描述器实现

这是一个简单的描述器,展示了 __get__ 的实际应用。它记录属性访问,同时保持正常的行为。

basic_get.py
class LoggedAccess:
    def __init__(self, value):
        self.value = value
    
    def __get__(self, instance, owner):
        print(f"Accessing {self.value} from {instance}")
        return self.value

class MyClass:
    attr = LoggedAccess(42)

obj = MyClass()
print(obj.attr)  # Prints access message and returns 42

这个例子演示了基本的描述器模式。当访问 obj.attr 时,Python 会使用实例和所有者类调用 LoggedAccess.__get__

当通过类而不是实例访问时,instance 参数为 None。owner 参数是定义描述器的类。

实现属性

property 内置函数是使用描述器实现的。下面是如何使用 __get__ 创建类似属性的描述器。

property_descriptor.py
class MyProperty:
    def __init__(self, getter):
        self.getter = getter
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return self.getter(instance)

class Circle:
    def __init__(self, radius):
        self.radius = radius
    
    @MyProperty
    def diameter(self):
        return self.radius * 2

circle = Circle(5)
print(circle.diameter)  # 10

这个自定义属性描述器存储 getter 函数,并在访问属性时调用它。instance is None 检查处理类访问。

@MyProperty 装饰器的作用与 @property 类似,展示了 Python 的属性系统是如何构建在描述器之上的。

使用 __get__ 进行方法绑定

Python 使用 __get__ 来实现方法绑定。下面是如何模拟方法绑定行为。

method_binding.py
class Method:
    def __init__(self, func):
        self.func = func
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        from functools import partial
        return partial(self.func, instance)

class MyClass:
    def __init__(self, value):
        self.value = value
    
    @Method
    def show(self):
        print(f"Value: {self.value}")

obj = MyClass(10)
obj.show()  # Value: 10

这个例子展示了 Python 如何将方法绑定到实例。当访问方法时,__get__ 返回一个 partial 函数,并将实例作为第一个参数绑定。

来自 functoolspartial 函数创建一个新的函数,其中实例已预先绑定,模拟 Python 的方法绑定。

缓存属性描述器

这是一个实用的描述器,它在首次访问后缓存计算的属性,从而优化昂贵计算的性能。

cached_property.py
class CachedProperty:
    def __init__(self, func):
        self.func = func
        self.cache_name = f"_cache_{func.__name__}"
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        if not hasattr(instance, self.cache_name):
            setattr(instance, self.cache_name, self.func(instance))
        return getattr(instance, self.cache_name)

class Data:
    def __init__(self, data):
        self.data = data
    
    @CachedProperty
    def processed_data(self):
        print("Processing data...")
        return [x * 2 for x in self.data]

d = Data([1, 2, 3])
print(d.processed_data)  # Processes and prints
print(d.processed_data)  # Returns cached value

这个描述器将计算的值存储在实例的命名空间中。后续访问返回缓存的值,而不是重新计算。

缓存名称是动态生成的,以避免冲突。这种模式对于首次访问后不会改变的昂贵计算非常有用。

类型检查描述器

描述器可以强制执行属性访问的类型检查。此示例确保属性始终具有特定类型。

type_checking.py
class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]
    
    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"Expected {self.expected_type}")
        instance.__dict__[self.name] = value

class Person:
    name = Typed("name", str)
    age = Typed("age", int)
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
# p.age = "thirty"  # Raises TypeError

这个描述器将值存储在实例的 __dict__ 中,同时强制执行类型约束。__get__ 方法检索存储的值。

描述器在实例的字典中维护实际数据,同时通过描述器协议控制访问。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 Python 教程