类和对象
一切都是对象
在 Python 中,“一切皆对象”是一个核心理念。简单来说,Python 中的所有东西都是对象 ,无论是数值、字符串、函数、类,甚至是模块和代码本身。Python 对象有三个特征:身份 、类型 和值 。
身份 :对象在内存中的地址,可以通过 id()
函数查看,例如 id(obj)
。
类型 :对象的类别,可以通过 type()
函数查看。例如,type(5)
返回 <class 'int'>
。
值 :对象的实际内容或数据,直接调用变量名即可查看。
1 2 3 4 i = 2 print (id (i)) print (type (i)) print (i)
一切皆对象意味着我们可以将几乎任何东西赋值给变量或作为参数传递给函数。把任何函数传给一个变量。
类(Class)与对象(Object)是不同的,类是没初始化的模板,定义了结构和行为;对象是初始化了的实例,内存中有具体的值。
类 :类是对象的蓝图或模板,用来描述一类事物的通用结构和行为。定义类时,指定了该类对象的属性和方法。
对象 :对象是类的具体实例,表示某个具体的事物。
但是从更加一般的角度来说,类也是一种对象,也是有具体的值。
1 2 3 4 5 6 class MyClass: pass print(id(MyClass)) print(type(MyClass)) # 输出: <class 'type'> print(MyClass) # <class '__main__.MyClass'>
重要的基类
在 Python 中,type
和 object
是两个核心的基类:
object
是所有类的基类,所有类都继承自 object
。
type
是所有类的类型,也就是说,每个类都是 type
的实例 ,包括了 type 本身和 object。
总的来说,type
是所有类的类型 :每个类都是 type
的实例,包括 MyClass
、int
等。
type
也是创建类的工厂 :type
是一种创建类对象的机制,相当于类的构造器。
type
是一个类,且继承自 object
:它可以创建类,并且它自己也是一个类,具有类的一切特性。
类的常见属性
.__base__
属性表示当前类的直接基类 ,即当前类继承的父类。如果一个类没有显式继承父类,则 .__base__
为 object
,因为所有类默认继承自 object
。
1 2 3 4 5 6 7 8 class Parent : pass class Child (Parent ): pass print (Child.__base__) print (Parent.__base__)
在这里,Child.__base__
是 Parent
,而 Parent.__base__
是 object
,因为 object
是所有类的最终基类。
.__class__
表示当前实例或类的类型 ,即这个对象是由哪个类创建的。对于类本身,.__class__
通常会指向 type
,因为类对象是 type
的实例。
1 2 3 4 5 6 7 class MyClass : pass obj = MyClass() print (obj.__class__) print (MyClass.__class__)
在这个示例中,obj.__class__
表明 obj
是 MyClass
的实例,而 MyClass.__class__
表明 MyClass
是 type
的实例。
.__dict__
属性是一个字典 ,包含类或实例的所有属性。对于实例,.__dict__
包含该实例的所有实例属性。对于类,.__dict__
包含该类的所有类属性和方法。
1 2 3 4 5 6 7 8 9 10 class MyClass : class_attr = "class attribute" def __init__ (self, value ): self .instance_attr = value obj = MyClass("instance attribute" ) print (obj.__dict__) print (MyClass.__dict__)
在这个示例中,obj.__dict__
仅包含实例属性,而 MyClass.__dict__
包含类属性和方法。
.__subclasses__()
方法返回所有直接子类的列表 。这在需要递归遍历继承树或找到所有子类时非常有用。
1 2 3 4 5 6 7 8 9 10 class Base : pass class Child1 (Base ): pass class Child2 (Base ): pass print (Base.__subclasses__())
这里,Base.__subclasses__()
返回一个包含 Child1
和 Child2
的列表。
.__name__
是类的简单名称,返回类的名称字符串。.__qualname__
是类的完全限定名(Qualified Name),包含嵌套类或作用域信息。
1 2 3 4 5 6 7 class Outer : class Inner : pass print (Outer.__name__) print (Outer.Inner.__name__) print (Outer.Inner.__qualname__)
Inner.__qualname__
以字符串形式描述了完整的嵌套路径信息。
__call__
方法:类的可调用行为,使得类的实例可以像函数一样被调用。如果你在类中定义了 __call__
方法,那么创建的实例可以被直接调用,并执行 __call__
方法中的代码。
1 2 3 4 5 6 class CallableClass : def __call__ (self, *args, **kwargs ): print ("Instance is called like a function" ) obj = CallableClass() obj()
__del__
方法:实例的销毁,用于对象销毁时 执行清理工作。会在实例被垃圾回收时调用,通常用于关闭资源或执行清理操作。
1 2 3 4 5 6 class MyClass : def __del__ (self ): print ("Instance is being destroyed" ) obj = MyClass() del obj
对象的初始化
类的初始化过程包括类的创建 和实例的初始化 。当我们创建一个类的实例时,会依次调用 __new__
和 __init__
等方法来完成实例化流程。理解这个过程不仅有助于掌握 Python 的类机制,还可以通过自定义这些方法,控制类的创建和初始化行为,实现更多高级功能(如单例模式 、池化对象 等)。
当我们调用 MyClass()
创建类的实例时,Python 会经过以下几个主要步骤:
__new__
方法:创建实例。__new__
是一个静态方法 ,负责创建实例 ,并分配内存空间,通常返回一个新的实例对象。
__new__
的第一个参数是 cls
,表示当前类,Python 会根据这个类来创建新实例。__new__(cls, name, bases, dct)
如果 __new__
不返回实例(例如返回 None
或其他类型),则不会继续调用 __init__
,实例化会中止。
需要注意,object 对象是一个空的对象,是所有对象的父类。所以 super 返回了一个新的空白的对象。
1 2 3 4 5 6 7 8 9 10 11 12 class MyClass : def __new__ (cls, *args, **kwargs ): print ("Calling __new__" ) instance = super ().__new__(cls) return instance def __init__ (self, value ): print ("Calling __init__" ) self .value = value obj = MyClass(10 )
__init__
方法:初始化实例。__init__
的第一个参数是 self
,表示新创建的实例对象。通常在 __init__
中设置对象的属性,并完成实例的初始配置。
在类定义时,Python 实际上调用了**元类(MetaClass)**来创建类对象。默认情况下,Python 使用 type
作为元类,即 type
类是类的生成工厂。通过定义自定义的元类,可以在类创建时做更多的控制,例如在类生成前检查某些条件,或动态地添加方法和属性。
1 2 3 4 5 6 7 8 9 10 class MyMeta (type ): def __new__ (cls, name, bases, dct ): print (f"Creating class {name} " ) return super ().__new__(cls, name, bases, dct) class MyClass (metaclass=MyMeta): pass print (MyClass)
元类编程
通过重写 __new__
方法,可以确保类只有一个实例,实现单例模式 。
1 2 3 4 5 6 7 8 class Singleton : _instance = None def __new__ (cls ): if not cls._instance: cls._instance = super ().__new__(cls) return cls._instance
注意,元类的 __call__
和 对象的 __call__
是不一样的,它们的作用和用途是不同的。元类的 __call__
方法用于控制类的实例化过程 ,返回一个对象。当使用 MyClass()
来实例化一个类时,Python 会调用元类的 __call__
方法。在默认的实现中,元类的 __call__
负责调用类的 __new__
和 __init__
方法,以完成实例的创建和初始化。对象的 __call__
方法是定义在类实例 上的,它让实例像函数一样可以被调用 。
用元类来实现单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13 class SingletonMeta (type ): _instances = {} def __call__ (cls, *args, **kwargs ): if cls not in cls._instances: cls._instances[cls] = super ().__call__(*args, **kwargs) return cls._instances[cls] class Singleton (metaclass=SingletonMeta): def __init__ (self, value ): self .value = value
在多线程环境下,可能会出现多个线程同时创建实例的情况。通过在元类中加入线程锁,可以确保多线程下单例的唯一性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import threadingclass ThreadSafeSingletonMeta (type ): _instances = {} _lock = threading.Lock() def __call__ (cls, *args, **kwargs ): with cls._lock: if cls not in cls._instances: cls._instances[cls] = super ().__call__(*args, **kwargs) return cls._instances[cls] class ThreadSafeSingleton (metaclass=ThreadSafeSingletonMeta): def __init__ (self, value ): self .value = value
ORM 映射 :在 ORM 框架中,将数据库表结构映射为类结构,简化数据库操作。这个真的非常常见,而且 dct 字段还包括了传进来的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 class TableMeta (type ): def __new__ (cls, name, bases, dct ): columns = {key: value for key, value in dct.items() if isinstance (value, str )} dct['columns' ] = columns return super ().__new__(cls, name, bases, dct) class User (metaclass=TableMeta): id = 'INTEGER PRIMARY KEY' name = 'TEXT' email = 'TEXT' print (User.columns)
为什么 Python 是动态语言
Python 是一门典型的动态语言 ,这意味着它能够在运行时对对象的结构和行为进行修改,甚至可以动态地生成、修改和删除对象的属性和方法。这种动态性在开发业务逻辑密集型的应用时尤为高效,能够帮助程序员以更简洁、灵活的方式快速实现功能。
Python 对象通过 __dict__
属性(属性字典)管理自身的所有可动态改变的属性,这让 Python 对象能够随时添加、修改或删除属性和方法。
1 2 3 4 5 6 7 8 9 10 11 12 class MyClass : pass def new_class_method (self ): print ("New method for MyClass!" ) MyClass.new_method = new_class_method obj = MyClass() obj.new_method()
动态语言因其灵活性和开发效率而备受欢迎,尤其在业务逻辑密集的应用场景中,动态语言能够帮助程序员快速写出产品。比如说 Web 开发,数据科学和机器学习,自动化和脚本,核心在于业务逻辑。
动态语言的灵活性也带来了一个明显的缺点,即缺少静态类型 。由于类型的缺失,动态语言的代码在复杂项目中可能变得难以理解和维护,尤其对于不熟悉项目或代码的新手来说。:许多 IDE 会依赖静态类型信息来提供智能提示、自动补全和类型检查。动态语言由于没有类型信息,IDE 很难准确地推断变量的类型,导致开发者得不到准确的智能提示。
所以建议多使用类型提示(Type Hints)。
面向对象
需求分析
我们的目标是构建一个基于代码的 RAG(Retrieval-Augmented Generation)系统,专注于代码辅助工作。这个系统的核心流程包括将项目源代码转化为可检索的知识库(codebase),利用检索器进行相关性查询,并基于对话系统与用户交互,利用 LLM(大型语言模型)提供回答。
根据需求,我们可以提取以下名词 ,每个名词可能对应一个类或对象:
项目 (Project):表示用户输入的项目源代码集合。
Codebase :代码知识库,用于存储和管理项目的源代码,使得其内容可被检索。
检索器 (Retriever):一个工具类,用于在 Codebase
中执行搜索和相关性检索。
对话 (Conversation):支持用户与 LLM 间的交互,对话会调用检索器在 Codebase
中查询答案。
LLM (大型语言模型):处理用户的自然语言问题并生成响应。与检索器结合以形成 RAG 系统。
核心需求:
输入项目的源代码并构建 Codebase
基于问题检索相关的源代码
实现基于对话的交互,使用 Codebase 作为知识库
Conversation :表示与用户的交互,管理对话流程,并与 LLM 进行通信。负责在需要时调用 Retriever
从 Codebase
中获取信息,以增强 LLM 的回答。
类的设计
根据以上分析,可以划分出以下主要类及其职责:
类名
职责描述
Project
表示用户输入的源代码项目,负责加载和管理项目文件。
Codebase
作为知识库,负责存储和管理项目源代码,并提供索引、解析等操作,使之可被 Retriever
查询。
Retriever
提供在 Codebase
中执行检索的功能,通过索引和关键词匹配找到相关代码片段。
Query
(可选)
封装用户的查询请求,提供查询预处理功能,如关键词提取。
Conversation
支持用户与 LLM 的交互,管理对话流并在需要时调用 Retriever
从 Codebase
中查询信息。
LLM
代表大型语言模型,负责接收用户的自然语言问题,结合 Retriever
的结果生成增强回答。
类间关系和交互
根据类的职责和需求,我们可以进一步细化类之间的关系:
Project -> Codebase :Project
提供源代码内容,Codebase
接收并存储这些内容。Codebase
需要从 Project
获取代码文件内容,解析后生成索引。
Codebase <-> Retriever :Retriever
需要访问 Codebase
中的内容,执行搜索操作以找到与查询相关的代码片段。Codebase
提供检索接口以支持 Retriever
进行查询。
Conversation -> Retriever :Conversation
接收用户的自然语言问题,如果涉及代码查询,会将问题转化为 Query
,并调用 Retriever
从 Codebase
检索答案。Retriever
的返回内容将传递给 LLM
以生成回答。
Conversation -> LLM :Conversation
与 LLM
交互,将用户的问题及 Retriever
返回的代码上下文传递给 LLM
,由 LLM
生成回答并回复给用户。
设计模式
工厂模式
用于创建 Project
实例,封装实例化细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Project : def __init__ (self, name: str , source_files: List [str ] ): self .name = name self .source_files = source_files def load_files (self ): print (f"Loading files for project: {self.name} " ) class ProjectFactory : @staticmethod def create_project (name: str , source_files: List [str ] ) -> Project: project = Project(name, source_files) project.load_files() return project
策略模式
Codebase
支持不同的存储方式(如内存或数据库),可使用策略模式动态选择存储方案。下面还可以看到我们抽象出了存储层当作接口,设计了两种存储方式,接着作为参数传入 codebase,实现不同策略的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class StorageStrategy (ABC ): @abstractmethod def save_codebase (self, code_data: Dict ): pass @abstractmethod def load_codebase (self ) -> Dict : pass class InMemoryStorage (StorageStrategy ): def __init__ (self ): self ._data = {} def save_codebase (self, code_data: Dict ): self ._data = code_data print ("Codebase stored in memory." ) def load_codebase (self ) -> Dict : print ("Loading codebase from memory." ) return self ._data class DatabaseStorage (StorageStrategy ): def __init__ (self ): self ._data = {} def save_codebase (self, code_data: Dict ): self ._data = code_data print ("Codebase stored in database." ) def load_codebase (self ) -> Dict : print ("Loading codebase from database." ) return self ._data class Codebase : def __init__ (self, storage_strategy: StorageStrategy ): self .storage_strategy = storage_strategy self .index = {} def build_codebase (self, project: Project ): for file in project.source_files: self .index[file] = f"Indexed content of {file} " self .storage_strategy.save_codebase(self .index) def search (self, query: str ) -> List [str ]: results = [content for file, content in self .index.items() if query in content] return results
适配器模式
适合不更改老系统,和新的设计保持一致性。假设我们的系统中引入了一个新组件 LegacyRetriever
,这是一个旧的检索系统,用于检索代码片段,但它的接口设计不同,只有 search_code(query)
方法,而我们当前的系统需要通过 Retriever
的 retrieve(query)
方法来调用它。
那么设置一个适配器,包裹一层,那么 LegacyRetrieverAdapter
就满足了 Retriever
的接口要求了。甚至我们还能使用 self.__dict__.update(dict(execute=music_player.play))
的方式动态地给类增加函数,那么更加灵活了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class LegacyRetriever : def __init__ (self, codebase ): self .codebase = codebase def search_code (self, query ): return [content for file, content in self .codebase.index.items() if query.lower() in content.lower()] class LegacyRetrieverAdapter (Retriever ): def __init__ (self, legacy_retriever ): self .legacy_retriever = legacy_retriever def retrieve (self, query ): return self .legacy_retriever.search_code(query)
代理模式
在代理模式(Proxy Pattern)中,代理对象作为访问其他对象的接口,通常用于在不改变目标对象的前提下,为其提供控制访问 、懒加载 、缓存 或日志记录 等附加功能。
假设我们希望增强 Retriever
的功能,可以使用代理模式来实现如下扩展:
访问控制 :限制非授权用户的查询访问。
缓存 :缓存上次查询的结果,避免重复计算或检索。
日志记录 :记录每次查询的日志,便于后续查看或调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class AccessControlProxyRetriever : def __init__ (self, retriever, authorized_users ): self .retriever = retriever self .authorized_users = authorized_users def retrieve (self, query, user ): if user not in self .authorized_users: print (f"Access denied for user: {user} " ) return [] print (f"Access granted for user: {user} " ) return self .retriever.retrieve(query) class CachingProxyRetriever : def __init__ (self, retriever ): self .retriever = retriever self .cache = {} def retrieve (self, query ): if query in self .cache: print (f"Returning cached result for query: {query} " ) return self .cache[query] result = self .retriever.retrieve(query) self .cache[query] = result return result class LoggingProxyRetriever : def __init__ (self, retriever ): self .retriever = retriever self .logs = [] def retrieve (self, query ): print (f"Logging query: {query} " ) self .logs.append(query) return self .retriever.retrieve(query) def get_logs (self ): return self .logs
代理模式的牛逼之处,就是可以直接组合起来,因为这些类都实现了 retriever
函数。
1 2 3 4 5 6 7 8 9 10 11 12 project = Project("MyProject" , ["file1.py" , "file2.py" ]) codebase = Codebase(project) retriever = Retriever(codebase) log_proxy = LoggingProxyRetriever(retriever) cache_proxy = CachingProxyRetriever(log_proxy) access_proxy = AccessControlProxyRetriever(cache_proxy, authorized_users) print (access_proxy.retrieve("file1" , "Alice" ))
单例模式
参考元类编程。
观察者模式
观察者模式(Observer Pattern)是一种行为型模式,它定义了一种一对多的依赖关系,让多个观察者对象监听某一个主题对象。主题在状态发生变化时会通知所有观察者,观察者可以随时动态增删。这种设计使得观察者与主题解耦,提高了代码的灵活性和可维护性。
这种模式在事件订阅,pub-sub 系统非常常见,非常实用。pub 和 sub 可以非常干净的解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Subject : def __init__ (self ): self .__observers = [] def register (self, observer ): self .__observers.append(observer) def unregister (self, observer ): self .__observers.remove(observer) def notifyAll (self, *args, **kwargs ): for observer in self .__observers: observer.notify(self , *args, **kwargs) class Observer : def __init__ (self, subject ): subject.register(self ) def notify (self, subject, *args, **kwargs ): print (f"{self} received {args} {kwargs} from {subject} " ) subject = Subject() observer1 = Observer(subject) observer2 = Observer(subject) subject.notifyAll("First notification" ) subject.unregister(observer1) subject.notifyAll("Second notification" )
命令模式
命令模式(Command Pattern)是一种行为型设计模式,它的核心思想是将请求封装成对象 ,从而让你可以独立地传递、保存、撤销和重做请求 。每一个命令对象代表一个请求或操作,比如“创建文件”、“删除文件”、“重命名文件”等等。
在命令模式中,通常包含以下几个角色:
命令(Command)接口 :定义执行操作的接口,所有的命令类都应该实现这个接口。
具体命令(Concrete Command)类 :实现命令接口,封装具体的操作行为,比如“创建文件”、“删除文件”等。
接收者(Receiver) :执行具体操作的对象,命令对象通过接收者来完成操作。
调用者(Invoker) :负责执行命令的类,通常会记录所有命令,并提供撤销、重做等功能。
定义命令接口 :首先,我们定义 Command
接口,它包含了所有命令必须实现的 execute
和 undo
方法。
1 2 3 4 5 6 class Command : def execute (self ): raise NotImplementedError("Subclasses must implement 'execute' method" ) def undo (self ): raise NotImplementedError("Subclasses must implement 'undo' method" )
定义接收者 (家电设备):接收者是实际执行命令的家电设备。我们以灯、音响和空调为例,给每个接收者定义具体的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Light : def turn_on (self ): print ("Light is turned ON" ) def turn_off (self ): print ("Light is turned OFF" ) class Stereo : def play_music (self ): print ("Stereo is playing music" ) def stop_music (self ): print ("Stereo stopped playing music" ) class AC : def set_temperature (self, temperature ): print (f"AC temperature set to {temperature} °C" ) def turn_off (self ): print ("AC is turned OFF" )
定义具体命令类 :接下来,定义具体的命令类,每个命令类都会实现 Command
接口,并且封装特定设备的操作。每个命令对象会持有接收者的引用,以便在调用 execute
和 undo
时,执行接收者的具体操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class LightOnCommand (Command ): def __init__ (self, light ): self .light = light def execute (self ): self .light.turn_on() def undo (self ): self .light.turn_off() class LightOffCommand (Command ): def __init__ (self, light ): self .light = light def execute (self ): self .light.turn_off() def undo (self ): self .light.turn_on() class PlayMusicCommand (Command ): def __init__ (self, stereo ): self .stereo = stereo def execute (self ): self .stereo.play_music() def undo (self ): self .stereo.stop_music() class StopMusicCommand (Command ): def __init__ (self, stereo ): self .stereo = stereo def execute (self ): self .stereo.stop_music() def undo (self ): self .stereo.play_music()
定义调用者(Invoker) ,调用者负责管理和执行命令对象,提供操作执行、撤销等功能。调用者类 RemoteControl
可以持有一组命令,并支持批量撤销操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 class RemoteControl : def __init__ (self ): self .history = [] def press_button (self, command ): command.execute() self .history.append(command) def undo_last (self ): if self .history: command = self .history.pop() command.undo()
控制的主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 light = Light() stereo = Stereo() ac = AC() light_on_cmd = LightOnCommand(light) light_off_cmd = LightOffCommand(light) play_music_cmd = PlayMusicCommand(stereo) stop_music_cmd = StopMusicCommand(stereo) set_ac_temp_cmd = SetACTemperatureCommand(ac, 22 ) remote = RemoteControl() remote.press_button(light_on_cmd) remote.press_button(play_music_cmd) remote.press_button(set_ac_temp_cmd) remote.undo_last() remote.undo_last()
参考