====== 第十五章:魔术方法与属性 ======
===== 本章目标 =====
完成本章学习后,你将能够:
* 掌握常用的魔术方法
* 理解属性装饰器
* 实现描述符
* 定制类的行为
===== 常用魔术方法 =====
==== 字符串表示 ====
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. 创建可调用类作为装饰器
下一章:[[python_course:chapter16|第十六章:元类]]