python类与对象强化笔记

第 8 章 类与对象

8.1 修改实例的字符串表示

# 修改实例的字符串表示 即修改打印实例所产生的输出 让输出更有意义
# 即通过修改__repr__() 和 __str__()方法
class Pair: 
    def __init__(self, x, y): 
        self.x = x 
        self.y = y 
    
    # 返回对象的“代码表示”字符串
    # 即可以通过 eval(repr(obj)) 重新构造出原对象
    # 如果没有!r 系统会默认使用!s 导致无法使用eval()重建对象
    def __repr__(self): 
        return 'Pair({0.x!r}, {0.y!r})'.format(self) 
    
    # 返回对象的“可读字符串”表示
    def __str__(self): 
        return '({0.x!s}, {0.y!s})'.format(self)

p = Pair(3, 4)
print(p)           # 输出: (3, 4),调用 __str__
print(repr(p))     # 输出: Pair(3, 4),调用 __repr__

# 使用 {0!r} 进行占位时,默认会调用对应对象的 __repr__() 方法来返回字符串
print('p is {0!r}'.format(p))  # 输出: p is Pair(3, 4)
# 使用 {0} 进行占位时,默认会调用对应对象的 __str__() 方法来返回字符串
print('p is {0}'.format(p))    # 输出: p is (3, 4)

# 如果没有为一个类定义 __str__() 方法,Python 会使用 __repr__() 的输出作为替代

占位符的使用

def __repr__(self):
    return 'Pair({0.x!r}, {0.y!r})'.format(self)

# {0} 是 .format() 中的位置参数索引,指的是.format() 括号中传入的第一个参数(索引从0开始)
# {0.x} 就是访问 self.x 属性
# !r 是一个 转换标志,表示对这个值使用 repr() 函数进行转换。
# !s 表示使用 str() 转换对应的值,即调用对象的 __str__() 方法
# 在 .format() 中,不加转换标志时,默认是 !s

# 旧式操作符
# %r 在字符串格式化中就等价于 !r 的作用
def __repr__(self): 
	return 'Pair(%r, %r)' % (self.x, self.y)

8.2 自定义字符串的输出格式

# 通过在类中定义_format__()实现自定义字符串的输出形式
_formats = { 
    'ymd' : '{d.year}-{d.month}-{d.day}', 
    'mdy' : '{d.month}/{d.day}/{d.year}', 
    'dmy' : '{d.day}/{d.month}/{d.year}' 
}
class Date: 
    def __init__(self, year, month, day): 
        self.year = year 
        self.month = month 
        self.day = day 
    # 自定义格式化输出方法
    # 使用format(obj) 或使用 '{}:{}'.format(d) 时,就会调用这个方法
    def __format__(self, code): 
    	if code == '': 
        	code = 'ymd' 
    	fmt = _formats[code] 
    	return fmt.format(d=self)


d = Date(2012, 12, 21)

print(format(d))                      # 输出: 2012-12-21
print(format(d, 'mdy'))               # 输出: 12/21/2012
print('The date is {:ymd}'.format(d)) # 输出: The date is 2012-12-21
print('The date is {:mdy}'.format(d)) # 输出: The date is 12/21/2012
# :mdy 是传给 __format__() 方法的格式码 code
from datetime import date
# 对格式化代码的解释完全取决于类本身
d = date(2012, 12, 21)

print(format(d))                      # 输出: '2012-12-21'
print(format(d, '%A, %B %d, %Y'))    # 输出: 'Friday, December 21, 2012'
print('The end is {:%d %b %Y}. Goodbye'.format(d))
# 输出: 'The end is 21 Dec 2012. Goodbye'

8.3 让对象支持上下文管理协议

from socket import socket, AF_INET, SOCK_STREAM
# socket:用于创建网络连接的类
# AF_INET:表示 IPv4 地址族
# SOCK_STREAM:表示 TCP 协议(流式套接字)
from functools import partial

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        # 要连接的目标地址
        self.address = address
        # 地址族
        self.family = family
        # 套接字类型
        self.type = type
        # 当前的连接
        self.sock = None

    # 实现上下文管理协议的__enter__方法
    # __enter__() 方法会在进入 with 块之前被自动调用,返回值会被赋值给as后面的变量
    def __enter__(self):
        # 已经存在连接 就抛出异常 避免重复连接
        if self.sock is not None:
            raise RuntimeError('Already connected')
        # 创建一个新的socket连接
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        # 返回socket供socket块使用
        return self.sock

    # 实现离开方法 将在退出with块的时候调用
    # 三个参数分别为
    # exc_type 异常类型 如果没有异常发生,则为None
    # exc_val 异常实例(对象) 包含错误信息等属性
    # exc_tb traceback对象 指向异常发生时的调用栈信息
    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        # 释放资源
        self.sock = None
        # 可以在__exit__方法中进行异常处理 通过在本方法返回 True 可以抑制异常传播

if __name__ == '__main__':
    conn = LazyConnection(('www.python.org', 80))

    try:
        # 通过with语句来建立和关闭网络链接
        # 
        with conn as s:
            # 发送 HTTP GET 请求
            s.send(b'GET /index.html HTTP/1.0\r\n')
            s.send(b'Host: www.python.org\r\n')
            s.send(b'\r\n')

            # 接收响应数据
            resp = b''.join(iter(partial(s.recv, 8192), b''))

        # 打印响应内容长度及前 200 字节
        print(f"Response length: {len(resp)}")
        print("First 200 bytes:")
        print(resp[:200].decode('utf-8', errors='ignore'))

    except Exception as e:
        print("Error occurred:", e)

嵌套连接的实现

# 原先的代码只允许建立一条单独的socket链接 重复使用with语句会导致异常
# 下面代码可以实现嵌套连接
from socket import socket, AF_INET, SOCK_STREAM

class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = AF_INET
        self.type = SOCK_STREAM
        self.connections = []  # 用列表保存多个 socket

    def __enter__(self):
        sock = socket(self.family, self.type)  # 创建新 socket
        sock.connect(self.address)             # 建立连接
        self.connections.append(sock)          # 添加到列表中
        return sock                            # 返回给 with as 使用

    def __exit__(self, exc_ty, exc_val, tb):
        self.connections.pop().close()         # 弹出最后一个 socket 并关闭

from functools import partial

conn = LazyConnection(('www.python.org', 80))

with conn as s1:
    print("s1 connected")
    with conn as s2:
        print("s2 connected")
        # s1 和 s2 是两个独立的 socket 连接

    # s2 离开后自动关闭
# s1 离开后也自动关闭

# 使用上下文管理器能避免死锁 因为可以在exit方法中执行清理方法

8.4 当创建大量实例时如何节省内存

class Date:
    __slots__ = ['year', 'month', 'day']

    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
        
# 定义 __slots__ 后,Python 会为类实例使用一种基于数组的紧凑内存布局,替代原本的 __dict__,从而节省内存并提高访问速度。但这也带来了无法动态添加属性的限制,除非你手动保留 __dict__

# 但是尽量在必要时再使用这个属性,在多重继承时其存在行为限制,我们只应该在被当作数据结构频繁使用的类上才哟给这个技巧
# 同时其不应该被用于阻止用户添加新的属性 而是应该将其作为优化工具使用

8.5 基于名称约定控制权限

使用_表示内部实现

# python本身并不提供强制性的访问控制机制(private、protected等)
# 使用命名约定来表达封装性 而不是强制访问控制
class A:
    def __init__(self):
        self._internal = 0   # An internal attribute
        self.public = 1      # A public attribute

    def public_method(self):
        # 实现某些操作

    # 以单下划线(\_)开头的名字应该总是被认为只属于内部实现
    # 在外部代码中 不应该直接访问这写内部代码
    def _internal_method(self):
        # 实现某些操作

使用__避免继承冲突

class B:
    def __init__(self):
        self.__private = 0  # 私有属性

    def __private_method(self):
        ...  # 私有方法

    def public_method(self):
        ...  # 公共方法
        self.__private_method()  # 调用私有方法

class C(B):
    def __init__(self):
        super().__init__()
        # 这样子不会覆盖B中的私有变量
        # 这个变量会被重命名为_C__private
        self.__private = 1  
	# 这个方法会被重命名为 _C__private_method
    def __private_method(self):
        ...

# 使用双下划线 __ 开头会触发 Python 的名称重整(Name Mangling)机制,以避免子类与父类之间的属性/方法名冲突。 
总结
表示内部使用的属性或方法_name_helper(),_cache
防止子类覆盖父类的属性/方法__name__balance,__secret()
变量名与关键字冲突name_lambda_= 2.0,class_= "A"

8.6 创建可管理的属性

使用装饰器

# 在实例属性的获取和设定上 增加一些额外的处理过程
class Person:
    def __init__(self, first_name):
        # 构造函数中使用 self.first_name = first_name 是为了让初始化也受到验证逻辑的控制
        # 实际调用的是 setter 方法,会进行类型检查
        self.first_name = first_name

    @property
    # @property装饰器将方法伪装成属性 允许你像访问普通属性一样对其进行访问 也能加入逻辑控制
    # 返回内部存储的名字
    def first_name(self):
        return self._first_name

    # 要先将first设置为@property才能设置@first_name.setter
    @first_name.setter
    def first_name(self, value):
        # 设置姓名时进行内存检查
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    @first_name.deleter
    def first_name(self):
        # 禁止删除该属性
        raise AttributeError("Can't delete attribute")

if __name__ == '__main__':
    # 创建实例
    # 像访问属性一样访问方法
    try:
        p = Person("Guido")
        print("Name:", p.first_name)  # 输出 Guido

        # 修改名字
        p.first_name = "Python"
        print("Updated name:", p.first_name)  # 输出 Python

        # 尝试设置非法类型
        try:
            p.first_name = 42
        except TypeError as e:
            print("Error (expected):", e)

        # 尝试删除属性
        try:
            del p.first_name
        except AttributeError as e:
            print("Error (expected):", e)

    except Exception as e:
        print("Unexpected error:", e)
        
# first_name 是 property,是对外的接口;
# _first_name 是内部变量,是真正的数据存储位置;

使用property( )

# 将get和set方法也定义为property
class Person:
    def __init__(self, first_name):
        self.set_first_name(first_name)

    # Getter function
    def get_first_name(self):
        return self._first_name

    # Setter function
    def set_first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    # Deleter function (optional)
    def del_first_name(self):
        raise AttributeError("Can't delete attribute")

    # 将方法绑定为属性
    name = property(get_first_name, set_first_name, del_first_name)

property 的使用哲学

# property 本质上是一个描述符对象(descriptor object) ,它将一组方法(getter、setter、deleter)绑定在一起,并对外呈现出一个“看起来像属性”的接口
# 在类中访问 Person.first_name 时,实际上得到的是一个 roperty 对象

# 这几个属性分别指向已定义的方法
Person.first_name.fget 
<function Person.first_name at 0x1006a60e0> 
Person.first_name.fset 
<function Person.first_name at 0x1006a6170> 
Person.first_name.fdel 
<function Person.first_name at 0x1006a62e0> 

# fget、fset 等方法是 property 内部机制的一部分,通常我们不会手动调用它们;
# 它们会在访问或修改属性时自动触发;
# property 应该只在需要对属性访问进行额外处理时才使用
# 不要像 Java 那样“为了封装而封装”
# java风格的python代码
class Person:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value
# 如果 @property 并不会带来任何额外处理逻辑(如验证、封装、懒加载等),就不应该为了“封装而封装”

使用property 实现计算

# 使用 @property 可以让类中的某些计算属性看起来就像普通属性一样,不需要调用括号,提高了接口的一致性和可读性。
import math

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * self.radius ** 2

    @property
    def perimeter(self):
        return 2 * math.pi * self.radius
    
c = Circle(4.0) 
c.radius 
# 4.0 
c.area # 不需要使用()
# 50.26548245743669 

错误使用案例

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._first_name = value

    @property
    def last_name(self):
        return self._last_name

    @last_name.setter
    def last_name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._last_name = value
# first_name和last_name的验证逻辑完全一样,但写了两遍
# 如果将来要修改验证逻辑,必须改两个地方

8.7 调用父类中的方法

# 使用super方法调用父类中的方法
class A:
    def spam(self):
        print('A.spam')

class B(A):
    def spam(self):
        print('B.spam')
        super().spam()  # 调用父类的 spam 方法

调用父类中的初始化方法

class A:
    def __init__(self):
        self.x = 0


class B(A):
    def __init__(self):
        super().__init__()
        self.y = 1

代理类的使用

#定义一个代理类 用于控制另一个对象的访问方式
class Proxy:
    # 保存传进来的对象
    def __init__(self, obj):
        self._obj = obj

    # 使用魔术方法处理未知属性的访问
    # 当访问一个不存在于当前对象的属性或方法时,就会调用这个方法
    # 假设不存在这个属性 用户访问proxy.x 实际访问的是proxy._obj.x
    def __getattr__(self, name):
        return getattr(self._obj, name)

    # 将属性赋值委托给内部对象
    def __setattr__(self, name, value):
        # 以下划线开头的是内部属性 我们希望proxy自己进行处理
        if name.startswith('_'):
            # 使用父类的setattr可以避免无限递归
            super().__setattr__(name, value)  # 调用原始的 setattr 方法
        else:
            setattr(self._obj, name, value)
            
class Spam:
    def __init__(self):
        self.x = 10

# 使用代理类进行封装
s = Spam()
p = Proxy(s)

p.x = 20           # 修改的是 s.x
print(p.x)         # 输出 20

p._secret = "abc"  # 设置的是 p 自己的属性
print(p._secret)   # 输出 abc
# 每当给对象的属性赋值时(比如 obj.attr = value),Python 都会自动调用 __setattr__() 方法
# 使用是应该注意
# 当你在 __setattr__ 方法中使用 self.name = value 或 self[name] = value 这种方式设置属性时,Python 又会再次调用 __setattr__ 方法,从而导致无限递归

# 错误的代码示范
class Proxy:
    def __init__(self, obj):
        self._obj = obj  

    def __setattr__(self, name, value):
        if name == '_obj':
            self._obj = value  
        else:
            setattr(self._obj, name, value)
# 执行 self._obj = obj → 触发 __setattr__('self._obj', obj)
# 进入 __setattr__ 方法中判断是否是 _obj
# 执行 self._obj = value → 又一次触发 __setattr__
# 然后进入无限循环

多重继承

# 经常被存在缺陷的代码
# 无法支持多重继承
class Base:
    def __init__(self):
        print('Base.__init__')
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')
# 存在缺陷的多重继承
class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        Base.__init__(self)
        print('A.__init__')

class B(Base):
    def __init__(self):
        Base.__init__(self)
        print('B.__init__')

class C(A, B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)
        print('C.__init__')
# c = C()
# Base.__init__
# A.__init__
# Base.__init__
# B.__init__
# C.__init__
#  Base.__init__()方法被调用了两次

C.__init__ 被调用
│
├── A.__init__ 被显式调用
│   │
│   ├── Base.__init__ 被显式调用
│   │   └── 输出: Base.__init__
│   │
│   └── 输出: A.__init__
│
├── B.__init__ 被显式调用
│   │
│   ├── Base.__init__ 被显式调用(再次)
│   │   └── 输出: Base.__init__
│   │
│   └── 输出: B.__init__
│
└── 输出: C.__init__
# 修正后版本
class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        # 注意这里使用的是super() 所以会先对B的初始化进行调用
        super().__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')

class C(A, B):
    def __init__(self):
        super().__init__()  # 只调用一次 super()
        print('C.__init__')
# Base.__init__
# B.__init__
# A.__init__
# C.__init__

# C.__init__ 被调用;
# super().__init__() → 调用 A.__init__;
# A.__init__ 中的 super().__init__() → 调用 B.__init__;
# B.__init__ 中的 super().__init__() → 调用 Base.__init__;
# 返回并依次打印:B.__init__ → A.__init__ → C.__init__

MRO
# MRO 就是 Python 在查找类的方法时所遵循的顺序。
# 当你调用一个对象的方法时(比如 obj.method()),如果这个方法在当前类中没有定义,Python 会按照 MRO 列表中的顺序 向上查找,直到找到该方法为止。

print(C.__mro__)
# 输出:
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, 
#  <class '__main__.Base'>, <class 'object'>)

Python 使用一种叫做 C3 线性化(C3 Linearization) 的算法来生成 MRO。它的目标是保证:

  1. 子类出现在父类之前;
  2. 保持基类的声明顺序;
  3. 在整个类层次结构中保持一致的顺序。

使用 super() 的本质是:协作式继承 。只要每个类都在适当的时候调用一次 super(),Python 就会按照 MRO 自动完成方法链的调用,确保每个方法都被调用一次,不多不少。

super( )可能调用非父类方法

# 正常的使用是会报错的
class A:
    def spam(self):
        print('A.spam')
        super().spam()

a = A()
a.spam()
# A.spam
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 4, in spam
# AttributeError: 'super' object has no attribute 'spam'

# A 没有继承任何类,即它默认继承自 object
# A 的 MRO A → object
# super().spam()会调用object中的spam方法 但是object中并没有进行此定义 所以报错
# 不会报错 调用了无关的B中的方法
class B:
    def spam(self):
        print('B.spam')

class A:
    def spam(self):
        print('A.spam')
        super().spam()

class C(A, B):
    pass

c = C()
c.spam()
# 输出 A.spam B.spam
# 因为 C.__mro__ = (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>) 
  • 确保在继承体系中所有同名的方法都有可兼容的调用签名(即参数数量相同,参数名称也相同)
  • 确保最顶层的类实现了这个方法。这样沿着 MRO 列表展开的查询链会因为最终找到了实际的方法而终止。

8.8 在子类中扩展属性

# 定义一个父类 尝试在子类中对其进行拓展
class Person:
    def __init__(self, name):
        # __init__ 中的 self.name = name 实际上会触发 name.setter
        # 构造函数中的赋值也会收到类型检查的限制
        self.name = name

    # Getter function
    @property
    def name(self):
        return self._name

    # Setter function
    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('Expected a string')
        self._name = value

    # Deleter function
    @name.deleter
    def name(self):
        raise AttributeError("Can't delete attribute")

类级别上父类的调用

class SubPerson(Person):
    @property
    def name(self):
        print('Getting name')
        return super().name
    
    # 这个注解实现了set的绑定
    @name.setter
    def name(self, value):
        print('Setting name to', value)
        #  super(SubPerson, SubPerson).name在类级别上调用父类的实现
        # 第一个 SubPerson:告诉 super() 从哪个类开始查找继承链
        # 第二个 SubPerson:表明你要从类层面进行查找(不是从实例)
    	# 获取Person.name的描述符
        # 描述符内部实现了__get__()、__set__()、__delete__()方法
        # 这个代码实际上会调用的是Person.name.__set__ 因为传入了slef参数 所以是当前子类的-name被赋值
        super(SubPerson, SubPerson).name.__set__(self, value)
        # 使用super().__setattr__('name', value)也可
        # → 调用 Person.__setattr__ (即 property 描述符的 __set__)
        # → 设置 self._name = value
        # → 不会再次进入 SubPerson.name.setter 方法

    @name.deleter
    def name(self):
        print('Deleting name')
        super(SubPerson, SubPerson).name.__delete__(self)

s = SubPerson('Guido') 
# Setting name to Guido 
s.name 
# Getting name 'Guido' 
s.name = 'Larry' 
# Setting name to Larry 
s.name = 42
# Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "example.py", line 16, in name  raise TypeError('Expected a string')  TypeError: Expected a string  

描述符的访问方式

# 通过实例访问属性
print(s.name)     # 触发 getter
s.name = 'Larry'  # 触发 setter
del s.name        # 触发 deleter

# 通过类访问属性
print(Person.name)    # 获取的是 property 对象本身
print(SubPerson.name) # 同样获取的是 property 对象
# 描述符的行为取决于你是从实例访问还是从类访问,这决定了你是否触发了 getter/setter/deleter


# @property.setter 和 @property.deleter 中,我们要手动调用父类的实现,因此必须通过“类访问”的方式获取到原始的 property 描述符对象,然后手动调用它的 __set__ 或 __delete__ 方法。否则就会导致递归调用或错误的行为。 
# 上面类定义的方法中 使用super(SubPerson, SubPerson)可以获取Person.name这个描述符对象

# 错误的调用方式如super().name = value
# super() 是一个代理对象,它帮你自动在父类中查找方法或属性,而不需要你手动写父类名
# super() 在找到方法后,会自动将该方法绑定到当前实例(即 self)。
# 因此,super().name = value 实际上被解释为 self.name = value,
# 这会再次触发当前类的 name.setter 方法,从而导致无限递归。

安全继承property

在子类中扩展 @property 时,要清楚是要重写整个属性,还是只扩展其中一个方法(getter/setter/deleter)。
当需要调用父类的描述符方法时,必须通过 super(当前类, 当前类) 的方式访问描述符对象,然后手动调用其 __set__ __delete__ 方法。

# 错误示范 没有重写整个属性
class SubPerson(Person):
    @property  # ❌ 只重写了 getter,丢失了 setter
    def name(self):
        print('Getting name')
        return super().name
 
s = SubPerson('Guido') 
# Traceback (most recent call last): File "<stdin>", line 1, in <module> File "example.py", line 5, in __init__ self.name = name AttributeError: can't set attribute 
# 正确的修改方式
class SubPerson(Person):
    @Person.name.getter  # ✅ 只替换 getter 方法
    def name(self):
        print('Getting name')
        return super().name

8.9 创建一种新形式的类属性或实例属性

# 以描述符类的形式定义其功能 创建一种新形式的实例属性

# 自定义描述符类 Integer,用于限制属性必须为整数
# 述符就是以特殊方法__get__()、__set__()和__delete__()的形式实现了三个核心属性访问操作的类
class Integer:
    def __init__(self, name):
        # 保存属性名称,如 'x' 或 'y'
        # self.name 属性会保存字典的键,通过这些键可以找到存储在实例字典中的实际数据
        self.name = name

    def __get__(self, instance, cls):
        # 如果是通过类访问(如 Point.x),返回描述符自身
        if instance is None:
            return self
        else:
            # 如果是通过实例访问(如 p.x),返回实际存储在 __dict__ 中的值
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        # 检查赋值是否为整数类型
        if not isinstance(value, int):
            raise TypeError('期望一个整数(int)')
        # 将值存入实例的 __dict__ 中,以 self.name 为键
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        # 删除实例中对应的属性
        del instance.__dict__[self.name]
# 关于上述中instance的使用
# instance 表示操作的对象实例(如 Point 的实例 p),由 Python 自动传入

# 使用描述符的类
class Point:
    x = Integer('x')  # 使用描述符管理 x 属性
    y = Integer('y')  # 使用描述符管理 y 属性

    def __init__(self, x, y):
        self.x = x  # 实际调用 Integer.__set__
        self.y = y  # 实际调用 Integer.__set__
# 这些方法通过接受类实例作为输入 来工作。之后,底层的实例字典会根据需要适当地进行调整

# 创建实例
if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print("p.x =", p.x)     # 输出: p.x = 2
        p.y = 5
        print("p.y =", p.y)     # 输出: p.y = 5
        p.x = 2.3               # 触发类型错误
    except TypeError as e:
        print("捕获异常:", e)   # 输出: 捕获异常: Expected an int

__dict__属性

在 Python 中,每个类的实例(对象)都有一个 __dict__ 属性 ,它是一个字典(dict),用于存储该对象的所有实例属性

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Tom", 20)
print(p.__dict__)
# {'name': 'Tom', 'age': 20}

# 
p = Point(2,3) 
>>> p.x # Calls Point.x.get(p, Point)
2 
>>> Point.x # Calls Point.x.get(None, Point)
<__main__.Integer object at 0x100671890> 
>>>

描述符的补充

# 描述符只能在类层次定义 不能根据实例来产生
# 错误示范
class Point:
    def __init__(self, x, y):
        self.x = Integer('x')  
        self.y = Integer('y')  # y = Integer('y') 

        self.x = x
        self.y = y
# 同时在实现get方法上会比较复杂
# 如上文的return instance.__dict__[self.name] 
# 实例调用与类调用
# 实例调用
p = Point(2,3) 
p.x # 输出2  调用Point.x.get(p, Point)
# 类调用
Point.x # 调用 Point.x.get(None, Point)
# 输出<__main__.Integer object at 0x100671890> 

# 描述符本身不保存数据,它只是控制如何获取或设置数据
# 真实的数据是保存在实例的 __dict__ 中的。所以当你是“实例访问”时,要从它的字典里取;而当你是“类访问”,则不应该去实例里取。

更高级的基于描述符的代码

# 描述符类:用于类型检查的属性
class Typed:
    def __init__(self, name, expected_type):
        self.name = name            # 属性名
        self.expected_type = expected_type  # 期望的类型

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]  # 获取实际值

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('Expected ' + str(self.expected_type))
        instance.__dict__[self.name] = value  # 存储经过类型检查的值

    def __delete__(self, instance):
        del instance.__dict__[self.name]


# 类装饰器:为指定属性添加类型检查功能
def typeassert(**kwargs):
    def decorate(cls):
        for name, expected_type in kwargs.items():
            # 给类动态添加 Typed 类型检查描述符作为类属性
            setattr(cls, name, Typed(name, expected_type))
        return cls
    return decorate


# 示例使用
@typeassert(name=str, shares=int, price=float)
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
        
s = Stock('Apple', 100, 536.7)
print(s.name)     # 正常输出: Apple
s.shares = 50     # 正常赋值
s.shares = '100'  # 报错:TypeError: Expected <class 'int'>

如果只是针对一个类的一个属性做定制处理,优先使用 property
如果你想写出可复用、通用的属性行为(如类型检查器、缓存机制等),才考虑使用描述符

8.10 让属性具有惰性求值的能力

# 将一个定义为property 只有在访问时才参与计算 同时计算后要把这个值缓存起来
# 定义一个描述符类 lazyproperty,用于实现惰性求值的属性
# Python 中描述符是一种类,它实现了 __get__()、__set__() 或 __delete__() 方法,可以控制属性访问的行为
class lazyproperty:
    def __init__(self, func):
        self.func = func
    
    # instance指的是调用该属性的类实例 当通过类访问时 instance为None 此时会直接返回描述符本身
    # self是当前的描述符对象 如area或perimeter属性
    def __get__(self, instance, cls):
        if instance is None:
            return self
        
        else:
            # 第一次访问时调用函数计算值
            value = self.func(instance)
            # 将计算结果设置为实例属性,覆盖描述符
            setattr(instance, self.func.__name__, value)
            return value

import math

# Circle 类定义半径,并使用 lazyproperty 实现 area 和 perimeter 的延迟计算
class Circle:
    def __init__(self, radius):
        self.radius = radius

    # 将其包装成描述符    
    @lazyproperty
    def area(self):
        print('Computing area')
        return math.pi * self.radius ** 2

    @lazyproperty
    def perimeter(self):
        print('Computing perimeter')
        return 2 * math.pi * self.radius


# 模拟交互环境下的操作和输出
if __name__ == '__main__':
    c = Circle(4.0)
    print(c.radius)  # 输出: 4.0
    print(c.area)  # 输出: Computing area \n 50.26548245743669
    print(c.area)  # 输出: 50.26548245743669 (不再打印 Computing area)
    print(c.perimeter)  # 输出: Computing perimeter \n 25.132741228718345
    print(c.perimeter)  # 输出: 25.132741228718345 (不再打印 Computing perimeter)

当一个描述符是非数据描述符(只实现了 __get__() 没有实现 __set____delete__)时,Python 在属性查找时会优先查看实例字典,只有在找不到的情况下才会调用描述符的 __get__() 方法。

c = Circle(4.0)
print(vars(c))  # 输出: {'radius': 4.0}
# 第一次访问 c.area
print(c.area)   # 输出: Computing area \n 50.26548245743669
# 再次查看实例字典
print(vars(c))  # 输出: {'area': 50.26548245743669, 'radius': 4.0}
# 第二次访问 c.area(不会重新计算)
print(c.area)   # 输出: 50.26548245743669
# 删除 area 属性
del c.area
print(vars(c))  # 输出: {'radius': 4.0}

上述隐患

# 算出的值在创建之后就变成可变的(mutable)了。
c.area 
# 输出Computing area 50.26548245743669 
c.area = 25 
c.area # 输出25 
# 可变性问题的解决
def lazyproperty(func): 
    # 使用私有属性名存储计算结果 避免与原始属性名冲突
    name = '_lazy_' + func.__name__
    
    @property
    # 使用 @property 装饰一个方法时,这个方法中的 self 就是指调用该属性的那个实例对象
    def lazy(self): 
        # 后续直接返回缓存值
        if hasattr(self, name):
            return getattr(self, name) 
        else: 
            # 第一次访问进行计算并缓存到实例
            value = func(self) 
            setattr(self, name, value) 
            return value 
    return lazy

c = Circle(4.0) 
c.area 
# Computing area 50.26548245743669 
c.area 
# 50.26548245743669 
c.area = 25 
# Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: can't set attribute 

# 这种方式的缺点就是所有的 get 操作都必须经由属性的 getter 函数来处理。这比 直接在实例字典中查找相应的值要慢一些。

8.11 简化数据结构的初始化过程

# 示例代码 定义多个数据结构 但是都自定义了初始化方法
# 表示股票信息的类
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price


# 表示二维坐标点的类
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


# 表示圆的类,包含计算面积的方法
class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2

简单使用

# 定义一个通用结构类,用于处理具有固定字段的类初始化
class Structure:
    # 类变量,指定期望的字段
    _fields = []

    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        # 将参数按字段名设置为实例属性
        for name, value in zip(self._fields, args):
            setattr(self, name, value)


# 示例:Stock 类继承 Structure
class Stock(Structure):
    _fields = ['name', 'shares', 'price']

class Point(Structure):
    _fields = ['x', 'y']

class Circle(Structure):
    _fields = ['radius']
    def area(self):
        return math.pi * self.radius ** 2
    
if __name__ == '__main__':
    try:
        # 正常情况
        s1 = Stock('Apple', 100, 150.5)
        print(f"Stock: {s1.name}, {s1.shares}, {s1.price}")
        # Output: Stock: Apple, 100, 150.5
        
        p = Point(2, 3) # 正常创建实例
        
        c = Circle(4.5)
        print(f"Circle area: {c.area()}")  
        # Output: Circle area: 63.61725123519331
        
        # 参数数量不匹配的情况
        s2 = Stock('Google', 200)
    except TypeError as e:
        print(f"Error: {e}")
        # Output: Error: Expected 3 arguments

对关键字参数做映射

# 定义一个通用结构类,支持位置参数和关键字参数混合初始化
class Structure:
    _fields = []  # 子类中定义字段名列表

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        # 设置位置参数
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        # 设置剩余的关键字参数
        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))

        # 检查是否还有未识别的关键字参数
        if kwargs:
            raise TypeError('Invalid argument(s): {}'.format(', '.join(kwargs)))


# 示例类:Stock
class Stock(Structure):
    _fields = ['name', 'shares', 'price']

# 测试示例
if __name__ == '__main__':
    # 仅使用位置参数
    s1 = Stock('ACME', 50, 91.1)
    print(f"s1: {s1.name}, {s1.shares}, {s1.price}")
    # Output: s1: ACME, 50, 91.1

    # 混合使用位置参数和关键字参数
    s2 = Stock('ACME', 50, price=91.1)
    print(f"s2: {s2.name}, {s2.shares}, {s2.price}")
    # Output: s2: ACME, 50, 91.1

    # 全部使用关键字参数(顺序无关)
    s3 = Stock('ACME', shares=50, price=91.1)
    print(f"s3: {s3.name}, {s3.shares}, {s3.price}")
    # Output: s3: ACME, 50, 91.1

    # 错误用法示例
    try:
        s4 = Stock('ACME', 50, price=91.1, unknown=100)
    except TypeError as e:
        print(f"Error: {e}")
        # Output: Error: Invalid argument(s): unknown

允许利用关键字添加额外参数

# 定义一个通用结构类,支持固定字段 + 额外关键字参数
class Structure:
    # 类变量,指定期望的字段名列表
    _fields = []

    def __init__(self, *args, **kwargs):
        if len(args) != len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        # 设置位置参数
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        # 提取额外的关键字参数(不在 _fields 中)
        extra_args = kwargs.keys() - self._fields
        for name in extra_args:
            setattr(self, name, kwargs.pop(name))

        # 如果还有未处理完的 kwargs,说明是重复字段
        if kwargs:
            raise TypeError(f'Duplicate values for {",".join(kwargs)}')


# 示例类:Stock
class Stock(Structure):
    _fields = ['name', 'shares', 'price']


# 测试示例
if __name__ == '__main__':
    # 正常使用位置参数
    s1 = Stock('ACME', 50, 91.1)
    print(f"s1: {s1.name}, {s1.shares}, {s1.price}")
    # Output: s1: ACME, 50, 91.1

    # 使用位置参数 + 额外关键字参数
    s2 = Stock('ACME', 50, 91.1, date='8/2/2012')
    print(f"s2: {s2.name}, {s2.shares}, {s2.price}, {s2.date}")
    # Output: s2: ACME, 50, 91.1, 8/2/2012

    # 错误示例:对已有字段重复赋值
    try:
        s3 = Stock('ACME', 50, 91.1, price=100)
    except TypeError as e:
        print(f"Error: {e}")
        # Output: Error: Duplicate values for price
隐患
# 定义一个通用结构类,用于固定字段的初始化
class Structure:
    # 类变量,指定期望的字段名列表
    _fields = []

    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')
        # 直接访问实例字典
        # 使用 __dict__.update 批量设置属性
        self.__dict__.update(zip(self._fields, args))

存在隐患:

  • 如果子类使用了 __slots__@property(描述符),直接操作 __dict__ 将会失败。
  • Python 中有些类并不使用 __dict__ 存储属性(如 __slots__ 类),这会引发错误。
  • 对IDE也会有影响
# 可以正常运行 但是使用help()查看时会出现问题
>>> help(Stock)
Help on class Stock in module __main__:

class Stock(Structure)
 |  Method resolution order:
 |      Stock
 |      Structure
 |      builtins.object
 |
 |  Methods inherited from Structure:
 |
 |  __init__(self, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
# 构造函数没有显示出具体的字段名称(如 name, shares, price)

8.12 定义一个接口或抽象基类

一些基本操作

from abc import ABCMeta, abstractmethod
# ABCMeta 是一个元类(metaclass),用于创建抽象基类
class IStream(metaclass=ABCMeta):
    # 用于实现声明抽象方法的装饰器
    @abstractmethod
    def read(self, maxbytes=-1):
        pass

    @abstractmethod
    def write(self, data):
        pass
# 抽象类不能被直接实例化
a = IStream() 
# TypeError: Can't instantiate abstract class # IStream with abstract methods read, write
# 子类要实现基类中要求的方法
class SocketStream(IStream):
    def read(self, maxbytes=-1):
        ...
    def write(self, data):
        ...
# 抽象积累可以强制规定所需的编程接口
def serialize(obj, stream):
    if not isinstance(stream, IStream):
        raise TypeError('Expected an IStream')
    ...

虚拟子类注册

# 将标准库中的 io.IOBase 注册为 IStream 的“虚拟子类”
IStream.register(io.IOBase)

# 打开一个普通文件
f = open('foo.txt', 'w')  # 创建一个文件对象用于测试
f.close()
f = open('foo.txt', 'r')

# 检查 f 是否被认为是 IStream 的实例
print(isinstance(f, IStream))  # 输出: True

高级应用

from abc import ABCMeta, abstractmethod
# @abstractmethod 不仅可以用于普通方法,还可以应用于静态方法(@staticmethod)、类方法(@classmethod)以及属性(@property)

class A(metaclass=ABCMeta):
    @property
    @abstractmethod
    # 定义抽象只读属性
    # 子类必须实现定义getter方法
    def name(self):
        pass

    @name.setter
    @abstractmethod
    # @abstractmethod 必须紧挨函数定义
    def name(self, value):
        pass

    @classmethod
    @abstractmethod
    # 参数是cls 表示类本身
    def method1(cls):
        pass

    @staticmethod
    @abstractmethod
    def method2():
        pass

标准库抽象基类

# 标准库中定义了许多抽象基类
from collections.abc import Sequence, Iterable, Sized, Mapping

def check_interfaces(x):

    # 检查是否是序列(支持索引、长度和迭代)
    if isinstance(x, Sequence):
        print("x 是一个序列(Sequence)")

    # 检查是否可迭代(支持 iter())
    if isinstance(x, Iterable):
        print("x 是可迭代对象(Iterable)")

    # 检查是否有大小(支持 len())
    if isinstance(x, Sized):
        print("x 是有大小的对象(Sized)")

    # 检查是否是映射(如字典)
    if isinstance(x, Mapping):
        print("x 是一个映射(Mapping)")
# 抽象基类的缺陷
from decimal import Decimal
import numbers

# 创建一个 Decimal 实例
x = Decimal('3.4')

# 检查是否是 Real 类型
# 虽然说这个数是一个抽象类 但是由于numbers.Real的实现方式并没有将Decimal注册为其子类
print(isinstance(x, numbers.Real))  # 输出: False

8.13 实现一种数据模型或类型系统

对赋给某些属性的值进行限制

使用描述符机制实现

# 实现系统类型以及对值进行检查的框架

# 使用描述符机制来设置属性值
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name  # 属性名称
        for key, value in opts.items():
            setattr(self, key, value)  # 设置额外选项
    def __set__(self, instance, value):
        # 将值直接存储到实例的 __dict__ 中
        instance.__dict__[self.name] = value

# 用于强制类型检查
class Typed(Descriptor):
    expected_type = type(None)  # 子类应重写此属性以指定期望的类型

    def __set__(self, instance, value):
        # 检查传入值是否符合预期类型
        if not isinstance(value, self.expected_type):
            raise TypeError(f'期望类型 {self.expected_type}')
        super().__set__(instance, value)
   
# 用于确保值是非负数(>=0)
class Unsigned(Descriptor):
    def __set__(self, instance, value):
        # 检查值是否小于0
        if value < 0:
            raise ValueError('值必须大于等于 0')
        super().__set__(instance, value)

# 用于限制值的最大长度
class MaxSized(Descriptor):
    def __init__(self, name=None, **opts):
        # 必须提供 size 参数,否则抛出异常
        if 'size' not in opts:
            raise TypeError('缺少 size 参数')
        super().__init__(name, **opts)
        self.size = opts['size']  # 保存最大尺寸限制

    def __set__(self, instance, value):
        # 检查值的长度是否超过限制
        if len(value) >= self.size:
            raise ValueError(f'大小必须小于 {self.size}')
        super().__set__(instance, value)
# 定义具体的数据类型描述符类

# 整数类型
class Integer(Typed):
    expected_type = int

# 无符号整数(非负整数)
class UnsignedInteger(Integer, Unsigned):
    pass

# 浮点数类型
class Float(Typed):
    expected_type = float

# 无符号浮点数(非负浮点数)
class UnsignedFloat(Float, Unsigned):
    pass

# 字符串类型
class String(Typed):
    expected_type = str

# 有大小限制的字符串
class SizedString(String, MaxSized):
    pass
class Stock:
    # 定义字段及其约束条件
    name = SizedString('name', size=8)     # 字符串,最大长度为 8
    shares = UnsignedInteger('shares')     # 整数,且不能为负
    price = UnsignedFloat('price')         # 浮点数,且不能为负

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
 
if __name__ == '__main__':
    try:
        s = Stock('ACME', 50, 91.1)
        print("s.name =", s.name)  # 输出: 'ACME'
        s.shares = 75
        # 尝试设置非法值
        try:
            s.shares = -10
        except ValueError as e:
            print("错误(shares):", e)
            # 错误(shares): 值必须大于等于 0

        try:
            s.price = 'a lot'
        except TypeError as e:
            print("错误(price):", e)
            # 错误(price): 期望类型 <class 'float'>

        try:
            s.name = 'ABRACADABRA'
        except ValueError as e:
            print("错误(name):", e)
            # 误(name): 大小必须小于 8
            
    except Exception as e:
        print("发生错误:", e)

使用类装饰器实现

# 类装饰器:用于自动绑定描述符属性
def check_attributes(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            # 如果传入的是一个已经实例化的描述符对象
            if isinstance(value, Descriptor):
                value.name = key
                setattr(cls, key, value)
            else:
                # 如果传入的是描述符类(如 UnsignedInteger)
                # 则自动调用构造函数并传入 name
                setattr(cls, key, value(key))
        return cls
    return decorate

# 使用装饰器定义类
@check_attributes(
    name=SizedString(size=8),
    shares=UnsignedInteger,
    price=UnsignedFloat
)
class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

使用元类实现

# 使用元类自动绑定描述符属性名
class checkedmeta(type):
    """
    元类 checkedmeta 会在类创建时自动为描述符绑定属性名。
    这样就不需要手动传入 name 参数,例如:
        name = SizedString(size=8)
    而不是:
        name = SizedString('name', size=8)
    """
    def __new__(cls, clsname, bases, methods):
        # 遍历类中所有属性
        for key, value in methods.items():
            # 如果是描述符类型,则自动绑定 name 属性
            if isinstance(value, Descriptor):
                value.name = key
        return type.__new__(cls, clsname, bases, methods)


# 使用元类定义类
class Stock(metaclass=checkedmeta):
    # 直接声明描述符,无需手动指定属性名
    name = SizedString(size=8)     # 字符串,最大长度为 8
    shares = UnsignedInteger()     # 整数,且不能为负
    price = UnsignedFloat()        # 浮点数,且不能为负

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

8.14 实现自定义的容器

collections 库中定义了各种各样的抽象基类,当实现自定义的容器类时它们会非常有 用。为了说明清楚,假设我们希望自己的类能够支持迭代操作。要做到这点,只要简 单地从 collections.Iterable 中继承即可,就像下面这样:

# 实现一个自定义的类,用来模仿普通的内建容器类型比如列表或者字典的行为。

# collections 库中定义了许多的抽象基类
import collections
# 继承collections.Iterable 使自定义类可以实现迭代
# 但是使用时要实现__iter()方法 不然会报错
class A(collections.Iterable):  
    pass
# collextion中的类会按照功能递增的方式形成一个结构层次,如:
Container
  └── Iterable
      └── Sized
          └── Sequence
              └── MutableSequence

# 在实现的过程中 要实现一些要求的行为                
import collections

try:
    seq = collections.Sequence()
except TypeError as e:
    print("TypeError occurred:", e)
# TypeError occurred: Can't instantiate abstract class Sequence with abstract methods __getitem__, __len__ 
# 当前的代码要求实现 支持索引访问的__getitem__(self, index)以及支持获取长度的__len__(self)方法

自定义类的实现

# 使用自定义类实现了所有元素始终以排序后的顺序存储
# 同时支持索引访问(__getitem__)长度查询(__len__) 成员检测(in)迭代(for ... in)切片([start:end])
import collections.abc
import bisect

class SortedItems(collections.abc.Sequence):
    def __init__(self, initial=None):
        self._items = sorted(initial) if initial is not None else []

    def __getitem__(self, index):
        return self._items[index]

    def __len__(self):
        return len(self._items)

    def add(self, item):
        # 使用 bisect.insort进行插入 保持列表的有序
        bisect.insort(self._items, item)
 
if __name__ == '__main__':
    items = SortedItems([5, 1, 3])
    
    print("Initial list:", list(items))     # [1, 3, 5]
    print("Index access:", items[0], items[-1])  # 1 5
    
    items.add(2)
    items.add(-10)
    
    print("After adding elements:", list(items))  # [-10, 1, 2, 3, 5]
    print("Slice [1:4]:", items[1:4])              # [1, 2, 3]
    print("Contains 3?", 3 in items)               # True
    print("Length:", len(items))                   # 5
    
    print("Iterating over items:")
    for n in items:
        print(n)
    
    # 同时自定义容器可以通过各种类型检查
    print("Is Iterable?     ", isinstance(items, collections.abc.Iterable))      # True
    print("Is Sequence?     ", isinstance(items, collections.abc.Sequence))      # True
    print("Is Container?    ", isinstance(items, collections.abc.Container))     # True
    print("Is Sized?        ", isinstance(items, collections.abc.Sized))         # True
    print("Is Mapping?      ", isinstance(items, collections.abc.Mapping))       # False
# 通过实现少量核心方法 即可自动获得大量标准容器
# 继承自collections.MutableSequence 则要实现__getitem__、__setitem__、__delitem__、insert、__len__等方法

import collections.abc

class Items(collections.abc.MutableSequence):
    def __init__(self, initial=None):
        self._items = list(initial) if initial is not None else []

    def __getitem__(self, index):
        print('Getting:', index)
        return self._items[index]

    def __setitem__(self, index, value):
        print('Setting:', index, value)
        self._items[index] = value

    def __delitem__(self, index):
        print('Deleting:', index)
        del self._items[index]

    def insert(self, index, value):
        print('Inserting:', index, value)
        self._items.insert(index, value)

    def __len__(self):
        print('Len')
        return len(self._items)

# 自动实现了append()、remove()、count()、pop()、extend()等方法
if __name__ == '__main__':
    a = Items([1, 2, 3])
    print(len(a))
    a.append(4)
    a.append(2)
    print(a.count(2))
    a.remove(3)
    print("\n最终列表:", list(a))

8.15 委托属性的访问

委托的实现

使用内部变量实现
# 访问实例的属性时能将其委托(delegate)到一个内部持有的对象上
# 委托是一种编程模式 将特定的操作委托给另一个不同的对象

class A:
    def spam(self, x):
        pass

    def foo(self):
        pass

class B:
    def __init__(self):
        self._a = A()  # 内部持有一个 A 的实例

    def spam(self, x):
        return self._a.spam(x)  # 将 spam 方法委托给 A 实例

    def foo(self):
        return self._a.foo()    # 将 foo 方法委托给 A 实例

    def bar(self):
        pass
使用__setattr__实现
# 实现具体功能的类
class A:
    def spam(self, x):
        print(f"A.spam called with {x}")

    def foo(self):
        print("A.foo called")


class B:
    """通过 __getattr__ 将未定义的方法自动委托给 A 的实例"""
    def __init__(self):
        self._a = A()  # 持有一个 A 类的实例

    def bar(self):
        print("B.bar called")

    # 自动将未在 B 中定义的方法委托给内部的 A 实例
    def __getattr__(self, name):
        print(f"Delegating {name} to A...")
        return getattr(self._a, name)


# 测试代码
b = B()
b.bar()         # 调用 B.bar(),直接存在
b.spam(42)      # 不存在于 B,触发 __getattr__,委托给 A.spam()
b.foo()         # 同样被委托到 A.foo()
使用代理类实现
# Proxy 类:封装一个对象,并将属性操作转发给它
class Proxy:
    def __init__(self, obj):
        self._obj = obj  # 持有被代理的对象

    # 当访问不存在的属性时调用
    def __getattr__(self, name):
        print('getattr:', name)
        return getattr(self._obj, name)

    # 属性赋值时调用
    def __setattr__(self, name, value):
        if name.startswith('_'):  # 如果是私有属性(如 _obj),使用正常方式设置
            super().__setattr__(name, value)
        else:
            print('setattr:', name, value)
            setattr(self._obj, name, value)  # 否则,委托给内部对象

    # 删除属性时调用
    def __delattr__(self, name):
        if name.startswith('_'):  # 私有属性由父类处理
            super().__delattr__(name)
        else:
            print('delattr:', name)
            delattr(self._obj, name)  # 否则,委托给内部对象


# 被代理的类
class Spam:
    def __init__(self, x):
        self.x = x  # 实例变量 x

    def bar(self, y):
        print('Spam.bar:', self.x, y)


# 创建实例
s = Spam(2)         # 原始对象
p = Proxy(s)        # 创建代理对象

# 测试代理功能
print(p.x)          # 访问属性 -> 触发 __getattr__
p.bar(3)            # 调用方法 -> 实际上调用了 s.bar()
p.x = 37            # 修改属性 -> 触发 __setattr__
print(s.x)          # 验证原始对象的属性确实被修改

委托有时能替代继承

# 不推荐的实现
class A:
    def spam(self, x):
        print('A.spam', x)

    def foo(self):
        print('A.foo')


class B(A):
    def spam(self, x):
        print('B.spam')        
        super().spam(x)        

    def bar(self):
        print('B.bar')
# 继承是一种很高耦合的操作 父类所有的方法都会暴露出来 控制粒度差
# 多重继承可能导致复杂的方法解析顺序(MRO)
# 使用委托进行处理 可以选择性暴露某些方法 更容易实现接口隔离、行为封装、动态替换等高级特性
class A:
    def spam(self, x):
        print('A.spam', x)

    def foo(self):
        print('A.foo')
 
class B:
    def __init__(self):
        self._a = A()  # 持有一个 A 的实例

    def spam(self, x):
        print('B.spam', x)
        self._a.spam(x)  # 显式调用 A 的 spam 方法

    def bar(self):
        print('B.bar')

    def __getattr__(self, name):
        return getattr(self._a, name)  # 自动委托未定义的方法到 A 实例
# __getattr__ 是一个 fallback 方法 它只在对象本身找不到该属性时才会被调用 如果属性已经定义在实例或类中,则不会触发。

委托的缺陷

# __getattr__ 不会拦截大多数以双下划线(__)开头和结尾的特殊方法(如 __len__, __getitem__ 等)

class ListLike:
    def __init__(self):
        self._items = []

    def __getattr__(self, name):
        return getattr(self._items, name)
    
a = ListLike()

a.append(2)      # 成功:append 是普通方法,__getattr__ 拦截成功
a.insert(0, 1)   # 成功
a.sort()         # 成功

len(a)           # ❌ 报错:TypeError: object of type 'ListLike' has no len()
a[0]             # ❌ 报错:TypeError: 'ListLike' object does not support indexing
# 特殊方法(如 __len__, __getitem__)是在类本身中直接查找的 ,而不是通过 __getattr__
解决方案
# 普通的方法调用可以用__getattr__实现
# 特殊方法需要手动实现
class ListLike:
    def __init__(self):
        self._items = []  # 内部使用一个列表来存储数据

    def __getattr__(self, name):
        # 将未定义的方法委托给内部的 list 对象
        return getattr(self._items, name)

    # 实现了以下特殊方法以支持常见序列操作:
    def __len__(self):
        return len(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        self._items[index] = value

    def __delitem__(self, index):
        del self._items[index]

8.16 在类中定义多个构造函数

# 使用类方法实现备用构造函数
import time

class Date:
    # 主构造函数
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    # 备用构造函数:自动创建“今天”的日期对象
    @classmethod
    def today(cls):
        t = time.localtime()
        return cls(t.tm_year, t.tm_mon, t.tm_mday)

# 使用主构造函数创建实例
a = Date(2012, 12, 21)  # 指定具体日期

# 使用备用构造函数创建实例
b = Date.today()        # 自动获取当前系统日期
# 类方法非常适合用来定义备用构造函数,因为它接收类本身作为参数(cls),能够根据调用者的不同自动创建相应类型的实例,天然支持继承和多态

class NewDate(Date):
    pass

c = Date.today()       # 创建的是Date的实例 此时的cls是Date
d = NewDate.today()    # 创建的是NewDate的实例 此时的cls是NewDate

让初始化支持不同调用(不推荐)

# 一个的demo 这样的代码会难以理解和维护
class Date:
    def __init__(self, *args):
        if len(args) == 0:
            t = time.localtime()
            args = (t.tm_year, t.tm_mon, t.tm_mday)
        self.year, self.month, self.day = args
        
a = Date(2012, 12, 21)  # 清晰:明确指定日期
# 这样的接口是不直观的 用户需要阅读构造函数才能理解其行为 用户期望构造函数只做简单赋值,不应该隐藏复杂逻辑分支
b = Date()              # 不清晰:无参数调用,意图不明
c = Date.today()        # 清晰:通过类方法表达“今天”的语义

8.17 不通过调用 init 来创建实例

# 直接调用__new__方法构建一个未初始化的实例
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
        
# 使用 __new__ 创建一个未初始化的实例
d = Date.__new__(Date)

# 此时访问属性会报错(因为 __init__ 没有被调用)
try:
    print(d.year)  # AttributeError: 'Date' object has no attribute 'year'
except AttributeError as e:
    print(f"Error: {e}")


# 手动进行属性赋值,模拟初始化过程
data = {'year': 2012, 'month': 8, 'day': 29}
for key, value in data.items():
    setattr(d, key, value)
    
# 现在可以正常访问属性
print(d.year)   # 2012
print(d.month)  # 8

应用场景

# 部分的场景需要使用反序列化或者非标准构造场景 就可能需要绕过init函数
# 如从外部数据结构(如字典、JSON、数据库记录)恢复对象
class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __repr__(self):
        return f"Date({self.year}, {self.month}, {self.day})"

    @classmethod
    def from_dict(cls, data):
        d = cls.__new__(cls)  # 创建一个未初始化的对象
        for key, value in data.items():
            setattr(d, key, value)  # 手动设置属性
        return d


# 示例数据
data = {'year': 2012, 'month': 8, 'day': 29}

# 从字典恢复对象
d = Date.from_dict(data)
print(d)  # 输出: Date(2012, 8, 29)

# 在需要以非标准方式构造对象时,应尽量避免直接操作 __dict__,而应使用 setattr() 来设置属性,这样可以确保你的代码兼容各种类定义方式(包括 __slots__、property、描述符等),提高健壮性和可移植性。

8.18 用 Mixin 技术来扩展类定义

Mixin 是一种设计模式 ,在 Python 中通常表现为一个实现了某些功能的小型类。其不独立使用、没有完整的接口、依赖于其他类提供核心实现、提供的是“增强功能”。主要用于在不改变类继承结构的前提下,为多个无关类添加可复用的功能。

# 日志管理拓展案例
# 本代码在在不修改dict的前提下,为它添加额外功能
# 
from collections import defaultdict, OrderedDict

# Mixin 1: 添加日志输出功能
class LoggedMappingMixin:
    __slots__ = ()  # 不分配实例字典,节省内存
    
    def __getitem__(self, key):
        print(f'Getting {key}')
        # 使用super()将请求交给下一个类处理
        return super().__getitem__(key)
    
    def __setitem__(self, key, value):
        print(f'Setting {key} = {value!r}')
        return super().__setitem__(key, value)
    
    def __delitem__(self, key):
        print(f'Deleting {key}')
        return super().__delitem__(key)


# Mixin 2: 键只能设置一次
class SetOnceMappingMixin:
    __slots__ = ()
    
    def __setitem__(self, key, value):
        if key in self:
            raise KeyError(f'{key} already set')
        return super().__setitem__(key, value)


# Mixin 3: 键必须是字符串类型
class StringKeysMappingMixin:
    __slots__ = ()
    
    def __setitem__(self, key, value):
        if not isinstance(key, str):
            raise TypeError('keys must be strings')
        return super().__setitem__(key, value)

# 这些操作最终都会由dict类实现,因为Mixin只是在调用链中添加了一些功能(比如日志、检查等),真正的数据存储和操作还是由 dict 来完成的。

# 示例 1:带日志的 dict
# 通过同时继承dict 和 Mixin,新增了功能,但是没有修改dict类的功能
class LoggedDict(LoggedMappingMixin, dict):
    pass


# 示例 2:只允许设置一次的 defaultdict
class SetOnceDefaultDict(SetOnceMappingMixin, defaultdict):
    pass


# 示例 3:键必须为字符串 + 只能设置一次 + 有序字典
class StringOrderedDict(StringKeysMappingMixin,
                        SetOnceMappingMixin,
                        OrderedDict):
    pass


def main():
    d = LoggedDict()
    d['x'] = 23                 # Setting x = 23
    print("d['x'] =", d['x'])   # Getting x → d['x'] = 23
    del d['x']                  # Deleting x

    d = SetOnceDefaultDict(list)
    d['x'].append(2)            # Setting x = [2]
    d['y'].append(3)            # Setting y = [3]
    d['x'].append(10)           # .append() 是在原对象上进行的操作,并没有调用 __setitem__ 方法,所以不会触发 Mixin 中的“只设置一次”逻辑。 
    try:
        d['x'] = 42             # Setting x = 42 → KeyError: 'x already set'
    except KeyError as e:
        print("Expected error:", e)  # Expected error: x already set

    d = StringOrderedDict()
    d['x'] = 23                 # Setting x = 23
    try:
        d[42] = 10              # TypeError: keys must be strings
    except TypeError as e:
        print("Expected type error:", e)  # Expected type error: keys must be strings
    try:
        d['x'] = 42             # KeyError: 'x already set'
    except KeyError as e:
        print("Expected key error:", e)   # Expected key error: x already set

if __name__ == '__main__':
    main()

mixin类实现细节

  • Mixin 类不是用来单独使用的,它必须和其他类一起“混合”(多重继承)才能发挥作用。

  • Mixin 类通常不维护自己的状态(即没有实例变量),也不定义 __init__() 方法,它只提供方法的增强功能。

给Mixin添加初始化方法和实例变量

给 Mixin 类添加 __init__() 和实例变量是可行但高风险的操作 。你需要通过关键字参数、**kwargs 和良好的命名规范来规避命名冲突与构造函数签名不一致的问题,同时保持 Mixin 的可插拔性和通用性。

class RestrictKeysMixin:
    """
    Mixin 类:限制字典的键必须是某种类型。
    使用关键字参数 `_restrict_key_type` 指定允许的键类型。
    """
    def __init__(self, *args, _restrict_key_type, **kwargs):
        # 存储允许的键类型
        self._restrict_key_type = _restrict_key_type
        # 继续调用父类构造函数(通常是 dict)
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        # 在设置键之前检查类型
        if not isinstance(key, self._restrict_key_type):
            raise TypeError(f'Keys must be {self._restrict_key_type}')
        # 否则正常执行 setitem(最终交给 dict 处理)
        return super().__setitem__(key, value)

# super() 是 Mixin 类能够正常工作的关键,它使得多个 Mixin 可以协同工作,并最终调用到底层的实际类(如 dict)的方法。

# 示例组合类
class RDict(RestrictKeysMixin, dict):
    pass

def main():
    # 创建一个只允许字符串键的字典
    d = RDict(_restrict_key_type=str)
    d['name'] = 'Dave'
    print(d)  # {'name': 'Dave'}

    # 从序列初始化
    e = RDict([('name', 'Dave'), ('n', 37)], _restrict_key_type=str)
    print(e)  # {'name': 'Dave', 'n': 37}

    # 使用关键字参数初始化
    # 
    f = RDict(name='Dave', n=37, _restrict_key_type=str)
    print(f)  # {'name': 'Dave', 'n': 37}

    # 尝试使用非字符串键 → 应该抛出 TypeError
    try:
        f[42] = 10
    except TypeError as e:
        print("Expected error:", e)


if __name__ == '__main__':
    main()

使用类装饰器实现

def LoggedMapping(cls):
    # 保存原始方法
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    # 定义新的方法版本,带日志输出
    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    # 替换类中的特殊方法
    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__

    return cls


# 使用装饰器增强 dict 子类
@LoggedMapping
class LoggedDict(dict):
    pass

8.19 实现带有状态的对象或状态机

# 实现一个状态机 让对象可以在不同的状态中操作
# 这个类中有大量的条件判断 这样并不好

class Connection:
    def __init__(self):
        self.state = 'CLOSED'

    def read(self):
        if self.state != 'OPEN':
            raise RuntimeError('Not open')
        print('reading')

    def write(self, data):
        if self.state != 'OPEN':
            raise RuntimeError('Not open')
        print('writing')

    def open(self):
        if self.state == 'OPEN':
            raise RuntimeError('Already open')
        self.state = 'OPEN'

    def close(self):
        if self.state == 'CLOSED':
            raise RuntimeError('Already closed')
        self.state = 'CLOSED'
# 将每种操作状态以一个单独的类来定义 然后在connection类中使用这些状态

class Connection:
    def __init__(self):
        self.new_state(ClosedConnectionState)

    def new_state(self, newstate):
        self._state = newstate
        
    def read(self):
        return self._state.read(self)

    def write(self, data):
        return self._state.write(self, data)

    def open(self):
        return self._state.open(self)

    def close(self):
        return self._state.close(self)


# Connection state base class
class ConnectionState:
    @staticmethod
    def read(conn):
        raise NotImplementedError()

    @staticmethod
    def write(conn, data):
        raise NotImplementedError()

    @staticmethod
    def open(conn):
        raise NotImplementedError()

    @staticmethod
    def close(conn):
        raise NotImplementedError()


class ClosedConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        raise RuntimeError('Not open')

    @staticmethod
    def write(conn, data):
        raise RuntimeError('Not open')

    @staticmethod
    def open(conn):
        conn.new_state(OpenConnectionState)

    @staticmethod
    def close(conn):
        raise RuntimeError('Already closed')


class OpenConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        print('reading')

    @staticmethod
    def write(conn, data):
        print('writing')

    @staticmethod
    def open(conn):
        raise RuntimeError('Already open')

    @staticmethod
    def close(conn):
        conn.new_state(ClosedConnectionState)
        
if __name__ == '__main__':
    c = Connection()                      # 创建连接对象
    print(c._state)                       # <class '__main__.ClosedConnectionState'>
    
    try:
        c.read()
    except Exception as e:
        print("Exception:", e)            # RuntimeError: Not open

    c.open()                              # 成功打开连接
    print(c._state)                       # <class '__main__.OpenConnectionState'>
    c.read()                              # reading
    c.write('hello')                      # writing: hello
    c.close()                             # 成功关闭连接
    
    print(c._state)                       # <class '__main__.ClosedConnectionState'>

修改实例类型实现切换

class Connection:
    def __init__(self):
        self.new_state(ClosedConnection)

    def new_state(self, newstate):
        # 一些人可能不推荐直接使用修改类类型的方法进行切换 但是相较于上面的代码 这个代码的运行速度确实更快
    	# 修改当前实例的类型,实现状态切换
        self.__class__ = newstate  

    def read(self):
        raise NotImplementedError()

    def write(self, data):
        raise NotImplementedError()

    def open(self):
        raise NotImplementedError()

    def close(self):
        raise NotImplementedError()


class ClosedConnection(Connection):
    def read(self):
        raise RuntimeError('Not open')

    def write(self, data):
        raise RuntimeError('Not open')

    def open(self):
        self.new_state(OpenConnection)

    def close(self):
        raise RuntimeError('Already closed')


class OpenConnection(Connection):
    def read(self):
        print('reading')

    def write(self, data):
        print('writing')

    def open(self):
        raise RuntimeError('Already open')

    def close(self):
        self.new_state(ClosedConnection)


if __name__ == '__main__':
    c = Connection()                      # 创建连接对象
    print(c)                             # <ClosedConnection object>
    try:
        c.read()
    except Exception as e:
        print("Exception:", e)           # Not open
        
    c.open()                             # 切换为 OpenConnection 状态              
    print(c)						   # <OpenConnection object>
    c.read()                             # reading
    c.write('hello')                     # writing
    c.close()                            # 切换回 ClosedConnection 状态

              
    print(c)							# <ClosedConnection object>

8.20 调用对象上的方法,方法名以字符串形式给出

使用getattr或opeartor

# 简单的情况 使用getattr方法
# 通过方法名字符串调用方法
import math
import operator

class Point: 
    def __init__(self, x, y): 
        self.x = x 
        self.y = y 

    def __repr__(self): 
        return 'Point({!r:},{!r:})'.format(self.x, self.y) 

    def distance(self, x, y): 
        return math.hypot(self.x - x, self.y - y) 

p = Point(2, 3) 
d = getattr(p, 'distance')(0, 0)  # 调用 p.distance(0, 0)
print(d)

operator.methodcaller('distance', 0, 0)(p) # 等价于p.distance(0, 0)

points = [ 
    Point(1, 2), 
    Point(3, 0), 
    Point(10, -3), 
    Point(-5, -7), 
    Point(-1, 8), 
    Point(3, 2) 
] 
# 按照距离原点的顺序进行排序
points.sort(key=operator.methodcaller('distance', 0, 0))
# 写法一
result = operator.methodcaller('distance', 0, 0)(p)
# 写法二
# operator.methodcall()创建了一个可调用对象,而且把所需的参数提供给了被调用的方 法,只需要传入对应的self参数即可
d = operator.methodcaller('distance', 0, 0)
result = d(p)
# 同样等价于 p.distance(0, 0)

8.21 实现访问者模式

假设有一个复杂的数据结构,其中包含多种不同类型的节点。同时需要遍历这个结构、根据遇到的对象类型执行不同的操作、不希望把每种操作都硬编码到类本身中、希望代码具有良好的扩展性,添加新操作时尽量不修改已有类。这个时候就需要用到访问者模式了。

# 定义抽象语法树 用于表示数学运算
class Node:
    pass

class UnaryOperator(Node):
    def __init__(self, operand):
        self.operand = operand

class BinaryOperator(Node):
    def __init__(self, left, right):
        self.left = left
        self.right = right

# 继承二值操作
class Add(BinaryOperator):
    pass

class Sub(BinaryOperator):
    pass


class Mul(BinaryOperator):
    pass

class Div(BinaryOperator):
    pass

#继承单值操作
class Negate(UnaryOperator):
    pass

class Number(Node):
    def __init__(self, value):
        self.value = value

# 表达式:1 + 2 * (3 - 4) / 5
t1 = Sub(Number(3), Number(4))     # (3 - 4)
t2 = Mul(Number(2), t1)           # 2 * (3 - 4)
t3 = Div(t2, Number(5))           # 2 * (3 - 4) / 5
t4 = Add(Number(1), t3)           # 1 + (2 * (3 - 4) / 5)

实现一

# 创建数据结构是简单的 问题是后续需要对这些结构进行操作。为了让处理过程变得更加通用,就需要使用访问者模式
# 如下Node类本身是不实现操作的 操作都通过visitor实现


# 访问者基类
class NodeVisitor:
    def visit(self, node):
        # 构造方法名:visit_ + 类型名,例如 visit_Add、visit_Number
        methname = 'visit_' + type(node).__name__
        # 使用 getattr 查找当前类中是否有这个方法
        # 目前实现的代码中self应该是Evaluator和StackCode类
        meth = getattr(self, methname, None)
        # 如果没有找到,则使用 generic_visit 方法作为默认处理
        if meth is None:
            meth = self.generic_visit
        # 调用该方法并传入 node
        return meth(node)
	
    # 默认处理方式:抛出运行时异常,提示未实现对应的 visit_xxx 方法
    def generic_visit(self, node):
        raise RuntimeError(f'No {type(node).__name__} method')


# 求值访问者 继承了访问者基类
class Evaluator(NodeVisitor):
    def visit_Number(self, node):
        return node.value

    def visit_Add(self, node):
        return self.visit(node.left) + self.visit(node.right)

    def visit_Sub(self, node):
        return self.visit(node.left) - self.visit(node.right)

    def visit_Mul(self, node):
        return self.visit(node.left) * self.visit(node.right)

    def visit_Div(self, node):
        return self.visit(node.left) / self.visit(node.right)

    def visit_Negate(self, node):
        return -self.visit(node.operand)

expr = Add(Number(3), Negate(Number(4)))  # 3 + -(4)
e = Evaluator()
print(e.visit(expr))  # 输出: -1

实现二

# 使用堆栈机实现 
class StackCode(NodeVisitor):
    # 初始化指令列表,调用visit()遍历整个表达式树
    def generate_code(self, node):
        # 初始化一个空列表 用于储存生成的指令
        self.instructions = []
        self.visit(node)
        # 返回生成的指令列表
        return self.instructions
    
	# 遇到数字节点,生成PUSH指令
    def visit_Number(self, node):
        # 将数字的值压入栈中
        self.instructions.append(('PUSH', node.value))
    
    # 处理二元操作符(加减乘除) 先处理左右节点,再添加对应操作指令
    def binop(self, node, instruction):
        # 递归调用左节点
        self.visit(node.left)
        # 递归处理右节点
        self.visit(node.right)
        # 添加对应的指令(数值已经入栈了)
        self.instructions.append((instruction,))

    def visit_Add(self, node):
        self.binop(node, 'ADD')

    def visit_Sub(self, node):
        self.binop(node, 'SUB')

    def visit_Mul(self, node):
        self.binop(node, 'MUL')

    def visit_Div(self, node):
        self.binop(node, 'DIV')
        
    # 处理一元操作符,先处理操作数,再添加操作指令
    def unaryop(self, node, instruction):
        self.visit(node.operand)
        self.instructions.append((instruction,))

    def visit_Negate(self, node):
        self.unaryop(node, 'NEG')
    
# 表达式: 1 + 2 * (3 - 4) / 5
t1 = Sub(Number(3), Number(4))     # 3 - 4
t2 = Mul(Number(2), t1)           # 2 * (3 - 4)
t3 = Div(t2, Number(5))           # 2 * (3 - 4) / 5
t4 = Add(Number(1), t3)           # 1 + (2 * (3 - 4) / 5)

#         Add
#        /   \
#   Number    Div
#      |      /  \
#      1    Mul  Number
#          /   \     |
#      Number  Sub   5
#         |     |
#         2    3  4
s = StackCode() 
instructions = s.generate_code(t4)
print(instructions)

# [('PUSH', 1),('PUSH', 2),('PUSH', 3),('PUSH', 4),('SUB',),('MUL',),('PUSH', 5),('DIV',),('ADD',)]

8.22 实现非递归的访问者模式

# 使用递归形式的访问者模式可能超出递归限制,使用堆栈和生成器可以不使用递归
# 使用生成器配合显式栈模拟递归访问,虽然会占用更多内存,但可以突破 Python 默认的递归深度限制(1000 层),从而支持任意深度的表达式树。
import types

class Node:
    pass

class NodeVisitor:
    def visit(self, node):
        # 初始化栈 并放入初始节点
        stack = [node]
        # 用于存储上一个操作/子节点访问的结果
        last_result = None
        # 栈不为空时 持续进行循环
        while stack:
            try:
                # 获取栈顶元素(注意不是弹出)
                last = stack[-1]
                if isinstance(last, types.GeneratorType):
                    # 如果是生成器,send 结果进去继续执行
                    stack.append(last.send(last_result))
                    last_result = None
                elif isinstance(last, Node):
                    # 如果是节点对象,调用 _visit 方法(可能返回生成器)
                    stack.append(self._visit(stack.pop()))
                else:
                    # 否则认为是结果值,弹出并保存
                    last_result = stack.pop()
            except StopIteration:
                # 生成器完成时抛出 StopIteration,直接弹出栈顶
                stack.pop()
        return last_result

    def _visit(self, node):
        methname = 'visit_' + type(node).__name__
        meth = getattr(self, methname, None)
        if meth is None:
            meth = self.generic_visit
        return meth(node)

    def generic_visit(self, node):
        raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)
# 下面这个代码会超出递归限制
a = Number(0)
for n in range(1, 100000):
    a = Add(a, Number(n))
e = Evaluator() 
e.visit(a) 
# RecursionError: maximum recursion depth exceeded
#  对Evaluator类进行优化
class Evaluator(NodeVisitor):
    def visit_Number(self, node):
        return node.value

    def visit_Add(self, node):
        yield (yield node.left) + (yield node.right)

    def visit_Sub(self, node):
        yield (yield node.left) - (yield node.right)

    def visit_Mul(self, node):
        yield (yield node.left) * (yield node.right)

    def visit_Div(self, node):
        yield (yield node.left) / (yield node.right)

    def visit_Negate(self, node):
        yield -(yield node.operand)

过程展示

# 变量初始化
N1 = Number(1)
N2 = Number(2)
N3 = Number(3)
N4 = Number(4)
A1 = Add(N1, N2)
S1 = Sub(N3, N4)
M1 = Mul(A1, S1)
evaluator.visit(M1)

# 初始状态
# stack = [M1] last_result = None
# NodeVisitor.visit 循环

# 处理 M1(Mul Node)
# 	last = M1 (Node) 
# 	-> _visit(M1) 因为是一个节点 所以调用其中的_visit方法
# 		-> 调用visit_Mul方法 返回一个生成器 gen_M1
# 	stack = [gen_M1] 将其加入到栈中

# 运行 gen_M1(第一次)
# 	last = gen_M1 (Generator) 进入生成器分支 -> gen_M1.send(None) 
# 		-> 先yield node.left 返回A1

# 处理 A1 (Add Node)
# 	last = A1 (Node) -> _visit(A1) -> 返回gen_A1
# 	stack = [gen_M1, A1]

# 运行 gen_A1(第一次)
# 	last = gen_A1 (Generator) 进入生成器分支 -> gen_A1.send(None)
# 		-> visit_Add 执行到 (yield node.left),yield node.left(即N1)
# 	stack = [gen_M1, gen_A1, N1] 将N1加入到栈中
# 	last_result = None

# 处理 N1 (Number Node)
# 	last = N1 (Node)
# 	-> _visit(N1) 因为是一个节点 所以调用其中的_visit方法
# 		-> 调用visit_Number(N1) 方法,该方法直接返回 N1.value (即 1)
# 	stack = [gen_M1, gen_A1, 1] (N1被弹出,其结果1被加入)

# 处理结果 1
# 	last = 1 (非Node, 非Generator) 进入else结果分支
# 	last_result = 1 (1被弹出并赋值给last_result)
# 	stack = [gen_M1, gen_A1]


# 运行 gen_A1(第二次, 恢复处理N1的结果)
# 	last = gen_A1 (Generator) 进入生成器分支 -> gen_A1.send(last_result) (即 gen_A1.send(1))
# 		-> visit_Add 中 (yield node.left) 表达式的值变为 1, val_left = 1
# 		-> 继续执行到 (yield node.right),yield出 node.right (即 N2)
# 	stack = [gen_M1, gen_A1, N2] 将N2加入到栈中
# 	last_result = None

# 处理 N2 (Number Node)
# 	last = N2 (Node)
# 	-> _visit(N2)
# 		-> 调用visit_Number(N2) 方法,返回 N2.value (即 2)
# 	stack = [gen_M1, gen_A1, 2] (N2被弹出,其结果2被加入)

# 处理结果 2
# 	last = 2 (非Node, 非Generator) 进入else结果分支
# 	last_result = 2 (2被弹出并赋值给last_result)
# 	stack = [gen_M1, gen_A1]

# 运行 gen_A1(第三次, 恢复处理N2的结果, 并计算Add)
# 	last = gen_A1 (Generator) 进入生成器分支 -> gen_A1.send(last_result) (即 gen_A1.send(2))
# 		-> visit_Add 中 (yield node.right) 表达式的值变为 2, val_right = 2
# 		-> 计算 result = val_left + val_right (1 + 2 = 3)
# 		-> visit_Add yield出 result (即 3)
# 	stack = [gen_M1, gen_A1, 3] 将Add的结果3加入到栈中
# 	last_result = None

8.23 在环状数据结构中管理内存

案例

# 普通的垃圾回收不适用于环形数据结构
import gc

class Data:
    def __del__(self):
        print('Data.__del__')

class Node:
    def __init__(self):
        self.data = Data()
        self.parent = None
        self.children = []

    def add_child(self, child):
        self.children.append(child)
        child.parent = self

if __name__ == '__main__':
    # 删除单个Data()对象 立即删除
    a = Data()
    del a
    gc.collect()

    # 删除单个Node对象 立即删除
    a = Node()
    del a
    gc.collect()

    # 删除父子关系节点 
    a = Node()
    b = Node()
    a.add_child(b)
    del a
    # 没法立即删除 需要手动回收
    gc.collect()

弱引用实现

# 使用树节点展示弱引用机制
# 当两个或多个对象之间互相引用,且没有外部引用指向它们时,就会形成循环引用 。Python 的垃圾回收机制虽然可以处理部分循环引用,但在某些情况下仍可能导致内存泄漏
import weakref


class Node:
    def __init__(self, value):          # 初始化节点值、父节点和子节点列表
        self.value = value              # 存储节点值
        self._parent = None             # 用于保存父节点的弱引用
        self.children = []              # 子节点列表

    def __repr__(self):                 # 返回节点的字符串表示
        return 'Node({!r})'.format(self.value)

    @property
    def parent(self):                 
        return self._parent if self._parent is None else self._parent()

    @parent.setter
    def parent(self, node):             # 设置父节点时使用弱引用
        # weakref 提供了一种不增加引用计数的方式来引用对象,这样就不会阻止对象被垃圾回收
        self._parent = weakref.ref(node)

    def add_child(self, child):         # 添加子节点,并设置其父节点为当前节点
        self.children.append(child)
        # 此处实际调用了parent.setter 
        child.parent = self

# 创建两个节点
root = Node('parent')               
c1 = Node('child')                      

# 建立父子关系
root.add_child(c1)           

# 打印子节点的父节点(此时应为 Node('parent'))
print(c1.parent)                        # 输出: Node('parent')

# 删除父节点引用
del root                                # 删除对 root 的强引用
print(c1.parent)                        # 输出: None 说明正确回收了资源

8.24 让类支持比较操作

# 实现__eq__、__lt__、__le__、__gt__、__ge__等方法即可实现各种比较方法
from functools import total_ordering


class Room:
    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width
        self.square_feet = self.length * self.width


# 使用了 @total_ordering 装饰器,只需定义 __eq__ 和 __lt__,其余比较操作符自动可用
@total_ordering
class House:
    def __init__(self, name, style):
        self.name = name
        self.style = style
        self.rooms = list()

    @property
    def living_space_footage(self):
        return sum(r.square_feet for r in self.rooms)

    def add_room(self, room):
        self.rooms.append(room)

    def __str__(self):
        return '{}: {} square foot {}'.format(self.name,
                                              self.living_space_footage,
                                              self.style)

    def __eq__(self, other):
        return self.living_space_footage == other.living_space_footage

    def __lt__(self, other):
        return self.living_space_footage < other.living_space_footage


# Build a few houses, and add rooms to them
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))

h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))

h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))


# 输出测试结果(每行右边是预期输出)
print('Is h1 bigger than h2?', h1 > h2)     # prints: Is h1 bigger than h2? True
print('Is h2 smaller than h3?', h2 < h3)    # prints: Is h2 smaller than h3? True
print('Is h2 greater than or equal to h1?', h2 >= h1)  # prints: Is h2 greater than or equal to h1? False

houses = [h1, h2, h3]

# 打印最大和最小的房子
print('Which one is biggest?', max(houses))  # prints: Which one is biggest? h3: 1101 square foot Split
print('Which is smallest?', min(houses))     # prints: Which is smallest? h2: 846 square foot Ranch

8.25 创建缓存实例

普通实现

# 当创建类实例时,返回一个缓存引用,确保相同参数只创建一次实例
# 使用单例变体加缓存机制实现

import weakref

# 原始类定义
class Spam:
    def __init__(self, name):
        self.name = name


# 使用 WeakValueDictionary 来缓存实例,当实例不再被外部引用时会被自动回收
_spam_cache = weakref.WeakValueDictionary()

# 工厂函数:用于获取缓存中的实例或创建新实例
def get_spam(name):
    if name not in _spam_cache:
        s = Spam(name)
        _spam_cache[name] = s
    else:
        s = _spam_cache[name]
    return s

a = get_spam('foo')     # 第一次创建 'foo'
b = get_spam('bar')     # 第一次创建 'bar'

print(a is b)           # 输出: False (不是同一个实例)

c = get_spam('foo')     # 获取已有的 'foo' 实例
print(a is c)           # 输出: True (是同一个实例)

del a                   # 删除对 a 的引用
d = get_spam('foo')     # 重新获取 'foo'
print(d is c)           # 输出: True (仍然指向同一个实例)

# 注意:只有当所有强引用都被删除后,WeakValueDictionary 才会真正清除缓存中的对象

优雅实现

有点缺陷
# 主要的问题是__init__方法总会被调用 无论对象实例有没有得到缓存都是如此
import weakref

class Spam:
    _spam_cache = weakref.WeakValueDictionary()

    def __new__(cls, name):
        if name in cls._spam_cache:
            return cls._spam_cache[name]
        else:
            self = super().__new__(cls)
            cls._spam_cache[name] = self
            return self

    def __init__(self, name):
        print('Initializing Spam')
        self.name = name

s = Spam('Dave')      # 第一次创建,应调用 __init__
t = Spam('Dave')      # 第二次创建,仍会调用 __init__

print(s is t)         # 应输出: True
使用全局管理
import weakref

class CachedSpamManager:
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()

    def get_spam(self, name):
        if name not in self._cache:
            self._cache[name] = Spam(name)
        return self._cache[name]

    def clear(self):
        self._cache.clear()


class Spam:
    # 所有 Spam 实例共享同一个 manager
    manager = CachedSpamManager()

    def __init__(self, name):
        self.name = name

    @classmethod
    def get_spam(cls, name):
        return cls.manager.get_spam(name)



if __name__ == '__main__':
    a = Spam.get_spam('foo')     # 第一次创建
    b = Spam.get_spam('foo')     # 应该从缓存中获取
    print(a is b)                # 输出: True (两个变量指向同一个实例)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值