目录

第十五章:魔术方法与属性

本章目标

完成本章学习后,你将能够:

常用魔术方法

字符串表示

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __str__(self):
        """面向用户的字符串"""
        return f"Point({self.x}, {self.y})"
 
    def __repr__(self):
        """面向开发者的字符串,应该能重建对象"""
        return f"Point({self.x!r}, {self.y!r})"
 
    def __format__(self, format_spec):
        """格式化"""
        if format_spec == "polar":
            r = (self.x ** 2 + self.y ** 2) ** 0.5
            theta = math.atan2(self.y, self.x)
            return f"(r={r:.2f}, θ={theta:.2f})"
        return str(self)
 
p = Point(3, 4)
print(str(p))   # Point(3, 4)
print(repr(p))  # Point(3, 4)
print(f"{p:polar}")

比较方法

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def __eq__(self, other):
        """等于=="""
        if not isinstance(other, Person):
            return NotImplemented
        return self.age == other.age
 
    def __lt__(self, other):
        """小于<"""
        if not isinstance(other, Person):
            return NotImplemented
        return self.age < other.age
 
    # 使用functools.total_ordering自动补全其他比较
    def __le__(self, other): ...
    def __gt__(self, other): ...
    def __ge__(self, other): ...
    def __ne__(self, other): ...
 
from functools import total_ordering
 
@total_ordering
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
 
    def __eq__(self, other):
        return self.score == other.score
 
    def __lt__(self, other):
        return self.score < other.score
 
# 现在可以使用所有比较运算符
s1, s2 = Student("A", 90), Student("B", 85)
print(s1 > s2)  # True

属性装饰器

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
 
    @property
    def celsius(self):
        """Getter"""
        return self._celsius
 
    @celsius.setter
    def celsius(self, value):
        """Setter"""
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value
 
    @celsius.deleter
    def celsius(self):
        """Deleter"""
        del self._celsius
 
    @property
    def fahrenheit(self):
        """计算属性"""
        return self._celsius * 9/5 + 32
 
    @fahrenheit.setter
    def fahrenheit(self, value):
        self._celsius = (value - 32) * 5/9
 
# 使用
t = Temperature(25)
print(t.celsius)      # 访问属性(调用getter)
t.celsius = 30        # 设置属性(调用setter)
print(t.fahrenheit)   # 77.0(计算属性)

描述符

class Validator:
    """描述符基类"""
    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = f"_{name}"
 
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, None)
 
    def __set__(self, obj, value):
        self.validate(value)
        setattr(obj, self.private_name, value)
 
    def validate(self, value):
        raise NotImplementedError
 
class Range(Validator):
    """范围验证描述符"""
    def __init__(self, min, max):
        self.min = min
        self.max = max
 
    def validate(self, value):
        if not (self.min <= value <= self.max):
            raise ValueError(f"{self.name} must be between {self.min} and {self.max}")
 
class String(Validator):
    """字符串验证描述符"""
    def __init__(self, minlen=0, maxlen=None):
        self.minlen = minlen
        self.maxlen = maxlen
 
    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f"{self.name} must be a string")
        if len(value) < self.minlen:
            raise ValueError(f"{self.name} too short")
        if self.maxlen and len(value) > self.maxlen:
            raise ValueError(f"{self.name} too long")
 
class Person:
    age = Range(0, 150)
    name = String(minlen=1, maxlen=50)
 
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
p = Person("Alice", 25)
# p.age = 200  # ValueError

容器方法

class MyList:
    def __init__(self):
        self._data = []
 
    def __len__(self):
        return len(self._data)
 
    def __getitem__(self, index):
        return self._data[index]
 
    def __setitem__(self, index, value):
        self._data[index] = value
 
    def __delitem__(self, index):
        del self._data[index]
 
    def __contains__(self, item):
        return item in self._data
 
    def __iter__(self):
        return iter(self._data)
 
    def append(self, item):
        self._data.append(item)

可调用对象

class Counter:
    def __init__(self):
        self.count = 0
 
    def __call__(self, increment=1):
        self.count += increment
        return self.count
 
c = Counter()
print(c())      # 1
print(c())      # 2
print(c(5))     # 7
 
# 带参数的类装饰器
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
 
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Call {self.count} of {self.func.__name__}")
        return self.func(*args, **kwargs)
 
@CountCalls
def say_hello():
    print("Hello!")

本章练习

1. 实现一个Vector类,支持所有算术运算 2. 实现Temperature类,支持摄氏、华氏、开尔文转换 3. 创建Range描述符验证数值范围 4. 实现自定义列表类,支持切片 5. 创建可调用类作为装饰器

下一章:第十六章:元类