技术

Python并发 Agent Functon Calling LLamaIndex入门 Multi-Agent探索 Python虚拟机 LLM工作流编排 Python实践 下一个平台Agent 激发LLM涌现——提示工程 LLM微调理论及实践 大佬沉思 LLM外挂知识库 LLMOps 多模态LLM Python一些比较有意思的库 Transformers源码学习 LangChain源码学习 通用分布式计算引擎Ray Python并发 go依赖注入 go collection gc的基本原理 golang性能分析及优化 数据湖 高性能计算与存储 Linux2.1.13网络源代码学习 《大数据经典论文解读》 三驾马车学习 Spark 内存管理及调优 Yarn学习 从Spark部署模式开始讲源码分析 容器狂占内存资源怎么办? 多角度理解一致性 golang io使用及优化模式 Flink学习 c++学习 学习ebpf go设计哲学 ceph学习 学习mesh kvm虚拟化 学习MQ go编译器以及defer实现 学习go 为什么要有堆栈 汇编语言 计算机组成原理 运行时和库 Prometheus client mysql 事务 mysql 事务的隔离级别 mysql 索引 坏味道 学习分布式 学习网络 学习Linux go堆内存分配 golang 系统调用与阻塞处理 Goroutine 调度过程 重新认识cpu mosn有的没的 负载均衡泛谈 单元测试的新解读 《Redis核心技术与实现》笔记 《Prometheus监控实战》笔记 Prometheus 告警学习 calico源码分析 对容器云平台的理解 Prometheus 源码分析 并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go channel codereview gc分析 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes扩缩容 神经网络模型优化 直觉上理解深度学习 如何学习机器学习 TIDB源码分析 什么是云原生 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 共识算法 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全访问机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占资源怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes类型系统 源码分析体会 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 线程排队 jib源码分析之细节 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI——容器网络是如何打通的 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 精简代码的利器——lombok 学习 《深入剖析kubernetes》笔记 编程语言那些事儿 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 如何分发计算 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 calico学习 AQS——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记 log4j学习 为什么netty比较难懂? 递归、回溯、动态规划 apollo client源码分析及看待面向对象设计 学习并发 docker运行java项目的常见问题 OpenTSDB 入门 spring事务小结 分布式事务 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型和jvm内存布局 java exception Linux IO学习 netty内存管理 测试环境docker化实践 netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker namespace和cgroup zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 深度学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM执行 git maven/ant/gradle/make使用 再看tcp kv系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 Go基础 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

架构

dddfirework源码分析 RAG与知识图谱 大模型推理服务框架vLLM 大模型推理服务框架 模型服务化(未完成) 大模型RHLF 大模型训练 大模型推理 从Attention到Transformer k8s设备管理 LLM工具栈 ddd从理念到代码 如何应用LLM 小鼠如何驾驭大象(LLM)? 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 kubevela源码分析 容器和CPU那些事儿 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 AutoML和AutoDL 特征平台 实时训练 分布式链路追踪 helm tensorflow原理——python层分析 如何学习tensorflow 数据并行——allreduce 数据并行——ps embedding的原理及实践 机器学习中的python调用c 机器学习训练框架概述 tensornet源码分析 大模型训练和推理 X的生成——特征工程 tvm tensorflow原理——core层分析 模型演变 《深度学习推荐系统实战》笔记 keras 和 Estimator tensorflow分布式训练 分布式训练的一些问题 基于Volcano的弹性训练 图神经网络 pytorch弹性分布式训练 从混部到统一调度 从RNN到Attention pytorch分布式训练 CNN 《动手学深度学习》笔记 pytorch与线性回归 多活 volcano特性源码分析 推理服务 kubebuilder 学习 mpi 学习pytorch client-go学习 提高gpu 利用率 GPU与容器的结合 GPU入门 AI云平台梳理 tensorflow学习 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 学习rpc BFF openkruise学习 可观察性和监控系统 基于Kubernetes选主及应用 《许式伟的架构课》笔记 Admission Controller 与 Admission Webhook 发布平台系统设计 k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? 《软件设计之美》笔记 mecha 架构学习 Kubernetes events学习及应用 CRI 资源调度泛谈 业务系统设计原则 grpc学习 元编程 以应用为中心 istio学习 下一代微服务Service Mesh 《实现领域驱动设计》笔记 概率论 serverless 泛谈 《架构整洁之道》笔记 处理复杂性 那些年追过的并发 服务器端编程 网络通信协议 架构大杂烩 如何学习架构 《反应式设计模式》笔记 项目的演化特点 反应式架构摸索 函数式编程的设计模式 服务化 ddd反模式——CRUD的败笔 研发效能平台 重新看面向对象设计 业务系统设计的一些体会 函数式编程 《左耳听风》笔记 业务程序猿眼中的微服务管理 DDD实践——CQRS 项目隔离——案例研究 《编程的本质》笔记 系统故障排查汇总及教训 平台支持类系统的几个点 代码腾挪的艺术 abtest 系统设计汇总 《从0开始学架构》笔记 初级权限系统设计 领域驱动理念 现有上传协议分析 移动网络下的文件上传要注意的几个问题 推送系统的几个基本问题 做配置中心要想好的几个基本问题 不同层面的异步 分层那些事儿 性能问题分析 用户认证问题 资源的分配与回收——池 消息/任务队列

标签

k8s设备管理 多类型负载协调员Koordinator controller-runtime细节分析 finops学习 kubevela多集群 kubevela中cue的应用 基于k8s的工作流 kubevela源码分析 容器和CPU那些事儿 数据集管理fluid 应用管理平台kubevela karmada支持crd 多集群管理 helm 从混部到统一调度 volcano特性源码分析 kubebuilder 学习 client-go学习 tf-operator源码分析 k8s批处理调度/Job调度 喜马拉雅容器化实践 Kubernetes 实践 openkruise学习 基于Kubernetes选主及应用 Admission Controller 与 Admission Webhook k8s水平扩缩容 Scheduler如何给Node打分 Scheduler扩展 深入controller openkruise cloneset学习 controller-runtime源码分析 pv与pvc实现 csi学习 client-go informer源码分析 kubelet 组件分析 调度实践 Pod是如何被创建出来的? Kubernetes events学习及应用 CRI 资源调度泛谈 如何学习Kubernetes 以应用为中心 kubernetes operator kubernetes扩缩容 serverless 泛谈 什么是云原生 自定义CNI IPAM docker和k8s安全访问机制 Kubernetes监控 Kubernetes 控制器模型 Kubernetes资源调度——scheduler Kubernetes类型系统 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 Kubernetes源码分析——从kubectl开始 kubernetes yaml配置 CNI——容器网络是如何打通的 当我在说PaaS时,我在说什么 《深入剖析kubernetes》笔记 Kubernetes存储 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件

Python实践

2024年03月16日

简介

Paul Graham: 代码中任何外加的形式都是一个信号,表明我对问题的抽象还不够深入,也经常提醒我,自己正在手动完成的事情,本应该写代码通过宏的扩展自动实现。

魔法函数

魔法函数。允许你在类中自定义函数(函数名格式一般为__xx__),并绑定到类的特殊方法中。比如在类A中自定义__str__()函数,则在调用str(A())时,会自动调用__str__()函数,并返回相应的结果。Python中的魔法函数可以大概分为以下几类(看到魔法函数 就可以去查表):

  1. 类的构造、删除: object.__new__(self, ...) object.__init__(self, ...) object.__del__(self)
  2. 二元操作符: 加减乘除等,比如 + 对应 object.__add__(self, other)。矩阵乘法matrix multiplication a @ b 对应 object.__matmul__(self, other)
  3. 扩展二元操作符:+=/-=
  4. 一元操作符:取负等
  5. 比较函数:<=/>
  6. 类的表示、输出:str() len()
  7. 类容器:in 操作等

类的魔术方法:__init__/__new__/__call__/__del__,魔术方法一般由解释器调用(除了__init__),除非涉及大量元编程。如需调用魔术方法,最好调用相应的内置函数,比如len/iter/str等。借助魔术方法,自定义对象的行为可以像内置对象一样,符合社区所认可的python风格。魔术方法最重要的用途

  1. 模拟数值类型
  2. 对象的字符串表示形式。对应 __str____repr__
  3. 对象的布尔值。对应 __bool__
  4. 实现容器

支持运算符重载,这或许是python 在数据科学领域取得巨大成功的关键原因。在python中,函数调用()(__call__) 、属性访问.(__getattr__) 以及项访问和切片[]也是运算符。

yield和生成器

生成器在很多常用语言中,并没有相对应的模型。调用生成器函数(带有yield关键字的函数),返回一个生成器对象 generator(由python编译器构建,提供了·next`方法实现),也就是说,生成器函数是generator工厂。generator 实现了Iterator 接口,因此generator 也可以迭代。生成器函数中的return 语句触发generator抛出StopIteration 异常。生成器是懒人版本的迭代器,在你调用 next() 函数的时候,才会生成下一个变量。生成器并不会像迭代器一样占用大量内存,只有在被使用的时候才会调用。

  1. next(generator) 调用生成器的 __next__() 方法,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值后挂起, 并在下一次执行 next(generator) 方法时从当前位置继续运行。
  2. Python 引入生成器的最初目的是作为迭代器的替代。Python 中,可以迭代处理(比如在 for 循环中)的对象称为可迭代对象,可迭代对象实现了 __iter__() 特殊方法,返回一个迭代器。生成器允许用户使用 yield 关键字,将函数作为迭代器,而不用定义类并实现对应的特殊方法。Python 会自动帮用户填充特殊方法,使生成器变成迭代器
  3. 生成器以懒加载模式按需返回值,因而更节约内存空间(甚至可以用于生成无限序列 ==> 迭代器是一个有限集合,生成器则可以成为一个无限集),可以暂停(只能将控制权移交给调用者next 函数)和恢复(被next 调用)。
class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = self.text.split()

    def __iter__(self):
        return SentenceIterator(self.words)
class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = self.text.split()
    # __iter__ 返回一个generator(也是一个Iterator)
    def __iter__(self):
        for word in self.words:
            yield word

return和yield异同

  1. 相似的是:yield 和 return 都可以在一个函数里将值返回给调用方;
  2. 不同的是:return 后,函数运行就终止了,而 yield 则只是暂停运行。 含有 yield 的函数,不再是普通的函数,直接调用含有 yield 的函数,返回的是一个生成器对象(generator object)。可以使用 for 循环(实际还可以使用 list 或者 next 函数)来遍历该生成器对象,将 yield 的内容一个一个打印出来。

yield from 表达式句法可把一个生成器的工作委托给一个子生成器。引入 yield from 之前,如果一个生成器根据另一个生成器的值产出值,则需要使用for 循环。

def sub_gen():
    yield 1.1
    yield 1.2
def gen():
    yield 1
    for i in sub_gen():
        yield i
    yield 2
def gen():
    yield 1
    yield from sub_gen()
    yield 2

闭包

在虚拟机中,函数机制的实现都离不开 FunctionObject 和 FrameObject 这两个对象。有了 FunctionObject,一个函数就可以像普通的整数或者字符串那样,作为参数传递给另外一个函数,也可以作为返回值被传出函数。所以,在 Python 语言中,函数也和整数类型、字符串类型一样,是第一类公民(first class citizen)。把函数作为第一类公民,使新的编程范式成为可能,但它也引入了一些必须要解决的问题,例如自由变量和闭包的问题。

def func():
    x = 2
    
    def say():
        print(x)

    return say

f = func()
f()

当 say 函数在 func 函数的定义之外执行的时候,依然可以访问到 x 的值。这就好像在定义 say 函数的时候,把 x(更准确的说是x的reference) 和 say 打包在一起了,我们把这个包裹叫做闭包(closure)。

在 Python 字节码中,外部函数中定义并且被内部函数使用的变量被记录在 cell_vars 中,我们把它叫做 cell 变量。而对于内部函数,这些变量就是自由变量。自由变量是指在某个作用域内使用的变量,但该变量在这个作用域内并没有被定义或赋值,它的定义存在于当前作用域的外部。在函数中查找变量的时候,遵循 LEBG 规则。其中 L 代表局部变量,E 代表闭包变量(Enclosing),G 代表全局变量,B 则代表虚拟机内建变量。

注意,只有嵌套在其它函数中的函数才可能需要处理不在全局作用域中的外部变量,这些外部变量位于外层函数的局部作用域内。

装饰器

fluent python:装饰器是一种可调用对象,其参数是另一个函数(被装饰的函数)。装饰器可能会对被修饰的函数做些处理,然后返回函数,或者把函数替换成另一个函数或可调用对象(也就是类)。装饰器在加载模块时立即执行,通常是在导入时,很多python框架会使用这样的装饰器把函数添加到某个中央注册处,比如注册http handler。严格来说,装饰器只是语法糖。

所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。装饰器通常返回在装饰器内部定义的函数,取代被装饰的函数。Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变 ==> 函数签名变了),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。

简单示例

def func():
    ...
def timer(func, *args, **kwargs):
     def decorated(*args, **kwargs): 
        st = time.perf_counter()
        ret = func(*args, **kwargs)
        print('time cost: {} seconds'.format(time.perf_counter() - st))
        return ret
    # 返回一个函数指针,这样才能赋值给func
    return decorated
func = timer(func1)
# func = timer(func1) 这样的写法麻烦且不具有共通性,所以python提供了一种装饰器的标准用法
def timer(func):
"""装饰器:打印函数耗时"""
    def decorated(*args, **kwargs): # 一般把 decorated 叫作“包装函数”,接收任意数目的可变参数 (*args, **kwargs),主要通过调用原始函数 func 来完成工作。在包装函数内部,常会增加一些额外步骤,比如打印信息、修改参数等。
        st = time.perf_counter()
        ret = func(*args, **kwargs)
        print('time cost: {} seconds'.format(time.perf_counter() - st))
        return ret
    return decorated

类装饰器

绝大多数情况下,我们会选择用嵌套函数来实现装饰器,但这并非构造装饰器的唯一方式。事实上,某个对象是否能通过装饰器(@decorator)的形式使用只有一条判断标准,那就是 decorator 是不是一个可调用的对象。类同样也是可调用对象。

class Count:
    def __init__(self, func):   # 初始化时传入原函数 func()
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print('num of calls is: {}'.format(self.num_calls))
        return self.func(*args, **kwargs)
@Count
def example():
    print("hello world")
example()

# 输出
num of calls is: 1
hello world
example()

# 输出
num of calls is: 2
hello world
...

装饰器是一种特殊类型的函数,它接受另一个函数作为输入参数。这意味着你可以在修饰器内部访问和操作这个被传入的函数。装饰器将额外增加的功能,封装在自己的装饰器函数或类中;当使用 @decorator 语法应用装饰器到一个函数上的时候,Python 会用装饰器返回的新函数来替换原始函数。这样一来,每当尝试调用原始函数的时候,实际上是调用了装饰器返回的那个新函数。修饰器不过是类似函数调用add = call_cnt(add)的一种语法上的简写、语法糖。在vm实现,如果一个方法被装饰器修饰,则函数调用的字节码会从CALL_FUNCTION改为CALL_FUNCTION_EX,解释器会帮忙将add 调用改为 add=call_cnt(add) 后的add。PS:所以是不是可以认为,语法糖的实现都有解释器帮忙?

参数化装饰器

python 会把被装饰的函数作为第一个参数传给装饰器函数,那么如何让装饰器接受其他参数呢?答案是创建一个装饰器工厂函数来接受那些参数,然后再返回一个装饰器,应用到被装饰的函数上。

registry = set()
def register(active=True):
    def decorate(func)
        print('running register(%s)' % func)
        if active:
            registry.add(func)
        return func
    return decorate
@register(active=False) 
def f1():
    print('running f1()')
@register() # 注意此处是 register() 而不是 register。 装饰器是decorate
def f2():
    print('running f2()')

decorator库

原始的装饰器一般简单写法是两层嵌套,如果使用decorator库,将原始嵌套的写法改造成单层的写法,两层的参数合并了,且使用decorator库实现的装饰器实现了签名不变。

def log(func):
    def wrapper(*args, **kw):
        print 'before run'
        return func(*args, **kw)
    return wrapper
###########################
from decorator import decorator
@decorator
def log(func, *args, **kw):
    print 'before run'
    return func(*args, **kw)

with和上下文管理器

with 是一种不常见的控制流功能, 目的是简化常用的一些try/finally结构,

with 语句对一段定义在上下文管理器的代码进行包装(封装 try-catch-finally),当对象使用 with 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义: __enter__()__exit__()。PS:魔术方法的支持下的一种语法糖。

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源。类似于java的 try(xx){...}try...finally...

with open('path','读写模式‘) as f:
    do something

等价于

f = open('path','读写模式')
do something
f.close()

要使用 with 语句,首先要明白上下文管理器 以及 上下文管理协议(Context Management Protocol):

  1. 在有两个相关的操作需要在一部分代码块前后分别执行的时候,可以使用 with 语法自动完成。__enter__()方法会在with的代码块执行之前执行,__exit__()会在代码块执行结束后执行。如果使用了 as 子句,则将 enter() 方法的返回值赋值给 as 子句中的 target。
  2. 使用 with 语法可以在特定的地方分配和释放资源

基于类的上下文管理器

class FileManager:
    def __init__(self, name, mode):
        print('calling __init__ method')
        self.name = name
        self.mode = mode 
        self.file = None
        
    def __enter__(self):
        print('calling __enter__ method')
        self.file = open(self.name, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('calling __exit__ method')
        if self.file:
            self.file.close()
            
with FileManager('test.txt', 'w') as f:
    print('ready to write to file')
    f.write('hello world')
    
## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method

基于生成器的上下文管理器,可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with 语句。使用@contextmanager 能减少创建上下文管理器的样板代码,不用编写一个完整的类来定义__enter__()__exit__() 方法,而只需实现一个含有yield 语句的生成器,生成想让__enter__() 方法返回的值。yield 把函数主体分为两部分:在yield 之前的所有代码在调用__enter__() 方法时执行,yield 之后的代码在调用__exit__() 方法时执行。

from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
        
with file_manager('test.txt', 'w') as f:
    f.write('hello world')

需要注意的是,当我们用 with 语句执行上下文管理器的操作时,一旦有异常抛出,异常的类型、值等具体信息,都会通过参数传入“exit()”函数中。你可以自行定义相关的操作对异常进行处理,而处理完异常后,也别忘了加上“return True”这条语句,否则仍然会抛出异常。

动态语言

动态属性

在python 中,数据属性和方法统称属性 attribute,方法是可调用的attribute,动态属性的接口与数据属性一样(obj.attr),不过按需计算。 在python 中实现动态属性有以下几种

  1. @property
  2. __getattr__。对于my_obj.x 表达式,python会检查my_obj 实例有没有名为x的属性,如果没有,就到类(my_obj.__class__)中查找,如果还没有,就会沿着继承图继续向上查找,如果依旧找不到,则调用my_obj所属的类中定义的 __getattr__ 方法,传入self 和属性名称的字符串形式(例如’x’)。大多数时候,定义了__getattr__ 也要定义 __setattr__

我们通常把__init__ 称为构造方法,这是从其他语言借鉴过来的术语,__init__ 第一个参数是self,可见在解释器调用__init__ 时,对象已存在。调用类创建实例时,python 调用的特殊方法是 __new__,这是一个类方法,以特殊方法对待,因此不必使用@classmethod装饰器。python 创建对象的过程用伪代码可以概括为

def make(the_class,some_arg):
    new_object = the_class.__new__(some_arg)
    if isinstance(new_object, the_class):
        new_object.__init__(some_arg)
    return new_object
# 以下两个语句的作用基本相同
x = Foo('bar')
x = make(Foo, 'bar')

在python中,函数和类通常可以互换,因为二者都是可调用对象,而且python没有实例化对象的new 运算符,调用构造函数和调用工厂函数没有区别。此外,只要能返回新的可调用对象,取代被装饰的函数,二者都可以用作装饰器。

__init_subclass__ 是python面向对象编程里的一个魔法方法,它在初始化子类时被调用。在 __new__ 函数的内部,会检测要创建的类所继承的父类,有没有定义 __init_subclass__。如果有,则执行。可用于检查子类的定义是否符合要求,比如要求子类必须实现某个方法

动态方法

猴子补丁,在运行时修改类或模块,而不改变源码。

def set_card(deck, position, card): # python 方法说到底就是普通函数,第一个参数命名为self 只是约定
    deck._cards[position] = card
FrenchDeck.__setitem__ = set_card  
# 等同于
class FrenchDeck:
    def set_card(self,position, card):
        deck._cards[position] = card

在抽象基类上调用register 方法,注册的类就成了抽象基类的虚拟子类。issubclass 能够识别这种关系。类的继承关系是在一个名为 __mro__的特殊类属性中指定的,它会按顺序列出类及其超类,python 会按这个顺序搜索方法。

A.register(B)
@A.register # A.register 也可以作为装饰器使用
class B:

类型检查

python dict 有时候被当做记录使用,以key表示字段名称,value 可以是不同的类型。 比如用json 描述一本书的记录

{
    "isbc":"xx",
    "title":"xx",
    "authors": ["xx","xx"],
    "pagecount":478
}

在python 3.8 之前没有什么好方法可以注解这段记录,因为dict value 必须是同一类型。下面两个注解都不完美。

  1. Dict[str,Any],值可以是任何类型
  2. Dict[str,Union[str,int,List[str]]],难以理解 TypedDict 解决了这个问题。
    from typing import TypedDict 
    class BookDict(TypedDict):
     isbc:str
     title:str
     authors:list[str]
     pagecount:int
    

    TypedDict 仅为类型检查工具而生,在运行时没有作用。

其它

在Lisp或Dylan中, 23个设计模式中有16个的实现方式比在C++中更简单,而且能保持同等质量。 有时,设计模式或API要求组件实现单方法接口,而该方法有一个很宽泛的名称,例如execute、run、do_it。在python中,这些模式或api通常可以使用作为一等对象的函数实现,从而减少样板代码。

classmethod 最常见的用途是定义备选构造函数,staticmethod 修饰的函数就是普通函数,只是碰巧位于类的定义体中,而不是在模块层定义。