数据库_tinyDB-Python项目开发

实现说明

 storage 数据存储实现
 database && table 数据库和表的实现
 query 查询规则的实现
 cache 优化和提高数据库的查询和存储效率

文件结构

tinydb/database.py
    class TinyDB(TableBase):
     from . import JSONStorage
     from .storages import Storage
     from .table import Table, Document
     from .utils import with_typehint

tinydb/table.py    
    __all__ = ('Document', 'Table')
    class Document(dict):
	class Table:

tinydb/operations.py
	 
tinydb/middlewares.py
    class Middleware:
        from tinydb import Storage
    class CachingMiddleware(Middleware)
		
tinydb/storages.py	
     __all__ = ('Storage', 'JSONStorage', 'MemoryStorage')
	 class Storage(ABC)
	 class JSONStorage(Storage):
	 class MemoryStorage(Storage)
	 

tinydb/queries.py	     
    __all__ = ('Query', 'QueryLike', 'where') 
    class QueryLike(Protocol):
	class QueryInstance:
	class Query(QueryInstance):
	
	
tinydb/utils.py	    
    __all__ = ('LRUCache', 'freeze', 'with_typehint')
	    class LRUCache(abc.MutableMapping, Generic[K, V]):
		class FrozenDict(dict):
		

``mypy_plugin.py`` that adds support for this pattern.	
    静态类型检查工具(如mypy)来识别代码中的类型错误	
   类型提示(Type Hinting) 可以帮助 IDE 和类型检查器理解代码-提高开发工具的智能提示
   是一种在代码中提供额外信息的机制,用于静态分析、文档生成和提高代码可读性
   Mypy 是 Python 中的静态类型检查器 写的python代码 greeting.py
      然后运行:$ mypy greeting.py
	  
	pytest 进行测试用例管理  

代码说明

 •  database 维护表的集合和存储

 • storage 存储抽象类, 定义了read,write两个抽象方法和一个close空方法。
    • MemoryStorage 基于内存的存储实现
    • JSONStorage 基于JSON序列化的文件存储实现  JSONStorage 主要就是文件的操作,然后进行json数据序列化和反序列化   

 • cache 是数据库的重要实现,tinydb提供了2种 cache 。一种是table的 query-cache 另外一种cache是,数据写入的 cache
      查询缓存使用LRU实现,LRU全称Least Recently Used
 • query是可以进行布尔运算和算术运算的conditon,
     由 QueryInstance 父类和 Query 子类两级实现。 QueryInstance 定义了布尔运算的规则, Query 定义了算术运算的规则


 •document 是普通字典+doc_id属性
 
 •Table主要包括读和写两部分, 写的代表 search 方法
     更新数据模版主要步骤是:
      • 读取数据(database&&table)
      • 封装document对象
      • 更新数据
      • 写入数据
      • 清理缓存
utils中提供
  	frozenset 是给对象计算hash值的关键函数

源码

 class MemoryStorage(Storage):  
     super().__init__()
     self.memory = None
    storage的实现就是每次更换数据全量,data是整个database的数据
	
utils.py 
    class FrozenDict(dict):
    # Calculate the has by hashing a tuple of all dict items 
	 # 转换为元祖,利用元祖不可变的特性计算hash值	
    freeze 冻结
     return FrozenDict((k, freeze(v)) for k, v in obj.items())
	 return frozenset(obj)
	 return tuple(freeze(el) for el in obj)

    
    class LRUCache(abc.MutableMapping, Generic[K, V]):
      时间局部性:如果某个数据项被访问,那么不久后它可能再次被访问。
      空间局部性:如果某个数据项被访问,与它地址相邻的数据项可能很快也将被访问
    使用 OrderedDict 来保持插入元素的顺序。
      set 方法在插入新元素时,如果超过容量限制,则移除最旧的元素。
	  get 方法在访问元素时,将其移动到末尾,以表示最近访问。
tinydb/middlewares.py
  class Middleware:   def __init__(self, storage_cls) -> None:   def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
  class CachingMiddleware(Middleware):
 
tinydb/operations.py
     add(field,n)      delete(field)
	 increment(field)  decrement(field)
	 substract(field,n) set(field,val)
    nested function) 嵌套函数是定义在另一个函数内部的函数。它可以访问外部函数的局部变量,这种特性可以用于创建闭包
 
tinydb/queries.py  逻辑运算和关系运算以及  查询条件的处理
     Query instances can be combined using logical OR and AND and inverted using logical NOT.
    class QueryInstance:	    
	    def __init__(self, test: Callable[[Mapping], bool], hashval: Optional[Tuple]): 
	    is_cacheable()  __call__ __hash__  __repr__  __eq__ 
	    __and__ __or__ __invert__   布尔运算 and, or 和 not 是基于对象的hash判断。逻辑运算 
 
    class Query(QueryInstance)
	   基于Query对象的条件进行判断
	     ORM-like usage:
		 Classical usage: 
		运算
	      关系运算:  eq 相等  ne 不等  lt 小于  le 小于等于  gt 大于   ge 大于等于
	      集合判断: exists, matches, search, test, any, all 和 one_of 进行集合判断
		  fragment noop map 
		    self._path: Tuple[Union[str, Callable], ...] = ()
            def _generate_test( self,
                    test: Callable[[Any], bool],
                    hashval: Tuple,
                    allow_empty_path: bool = False ) -> QueryInstance: 

             """
             return self._generate_test(
                 lambda value: value != rhs,
                 ('!=', self._path, freeze(rhs) )
             )						
		  
		匿名函数--lambda表达式  
		  

        where 是   …的简略表达方式 where(shorthand for Query 
            def where(key: str) -> Query:
                """
                A shorthand for ``Query()[key]``
                """
                return Query()[key]	
                         return self._generate_test()
		  
tinydb/table.py   Table, Document
tinydb/database.py  class TinyDB(  Table, Document
	
class LRUCache(abc.MutableMapping, Generic[K, V]): 
class QueryLike(Protocol): 自定义协议,这个协议要求有__call__ 以及 __hash__
   	class Query(QueryInstance):
    # Query 和 QueryInstance 没有 从 QueryLike 显式继承,但它们实现了 QueryLike 协议
class Table  
    query_cache_class = LRUCache
    def __init__(
        self,
        storage: Storage, name: str,
        cache_size: int = default_query_cache_capacity,
        persist_empty: bool = False
    ):
	    self._query_cache: LRUCache[QueryLike, List[Document]] = self.query_cache_class(capacity=cache_size)
		
class TinyDB(Table):
    def __init__(self, *args, **kwargs) -> None:
        storage = kwargs.pop('storage', self.default_storage_class)
        self._storage: Storage = storage(*args, **kwargs)
        self._opened = True
        self._tables: Dict[str, Table] = {}

继承了 Table中的 insert等方法
新增加table方法
      return self._tables[name]可以使用table中的方法
  


https://tinydb.readthedocs.io/en/latest/extend.html		 

Python属性名称

# 提取并移除 'param' 参数
 param = kwargs.pop("param", None)  # 如果 'param' 不存在,返回 None
# 直接访问 'param' 参数,,但不会移除键值对
 param = kwargs.get("param")  # 如果 'param' 不存在,返回 None

面相对象编程

###属性名称
  01.__init__ 这个方法用于初始化实例的状态,确保在对象创建时设置正确的初始属性值
     可以在__init__函数中为对象的属性赋初始值。
	  父类的__init__
  02. Python的动态类型特性使得它可以在运行时动态地添加、修改或删除对象的属性
  03. 属性名称并不是固定的,可能是动态生成的,可以利用 getattr() 函数动态地获取这些属性的值
  
### __call__
 Python中的一个 callable (可调用对象)是任何你能用一对圆括号和一系列可选参数调用的对象	
   实现了 .__call__() 方法的类的实例
    可以像调用Python里的常规函数一样调用你的类的实例
	_call _函数使得对象可以直接当函数使用。a(“爱!”)等同于a._call _(“爱!”)。a()等同于a._call _()。
	
	Callable 是一个可调用对象类型
	   Callable[[Arg1Type, Arg2Type, ...], ReturnType] 表示一个接受特定参数类型并返回特定类型值的可调用对象
	   Callable  作为 函数参数使用,其实只是做一个类型检查的作用,检查传入的参数值  
	   Callable  作为 函数返回值使用,其实只是做一个类型检查的作用,看看返回值是否为可调用对象
	    __init__(self, test: Callable[[Mapping], bool], hashval: Optional[Tuple]):
		 test: Callable[[Any], bool],
	   
### __getattr__		
	getattr() 函数是 Python 中一个非常有用的内置函数,用于动态地获取对象的属性值和方法,并在处理特殊情况时提供了便利。
	通过合理地应用 getattr() 函数,可以轻松地处理动态属性名称、特殊情况以及动态方法调用等
	  __getattr__     :当你访问一个对象的属性,而该属性在对象中不存在时,Python 会调用 __getattr__ 方法。注意,这个方法只会在属性不存在时被调用
	  __getattribute__: 无论属性是否存在,都会被调用。通常不建议直接重写,定义此方法后,优先调用
	
###__enter__ __exit__ 
   __enter__:进入上下文(with操作对象时)
   __exit__:退出上下文(with中的代码块执行完毕之后,执行__exit__()方法)

### __repr__ 方法是一种特殊的方法,
   全称为 “representation”,即“表示”或“表达”。这个方法用于返回一个对象的“官方”字符串表示形式
 	 当对象被打印(传递给print()函数)或者被转换成字符串类型时(比如使用str(obj)),Python解释器会尝试调用该对象的__str__方法。
     如果一个类没有定义 __str__ 方法,Python解释器会调用内置的 __repr__ 方法来获取对象的字符串表示。
     如果也没有定义 __repr__ 方法,Python解释器会使用默认的字符串表示形式,即返回对象在计算机内存中的实际地址		 
	  __str__(stringification)
	  
### __len__
   当我们在一个对象上调用内置的len()函数时,实际上是在调用该对象的__len__()方法。
   这个方法的主要作用是返回一个对象的长度,例如列表中的元素数量、字符串中的字符数量	
	  返回值类型:__len__()方法应该返回一个整数,表示对象的长度
	  
### __iter__()
 可迭代对象(Iterable):实现了 __iter__() 方法,返回一个迭代器对象的对象
 迭代器协议: 任何实现了 __iter__()和 __next__()方法的对象都是迭代器。
   __iter__(): 返回迭代器对象本身。
   __next__(): 返回容器的下一个元素,直到没有元素时抛出 `StopIteration` 异常

函数式编程

@符号将修饰器应用于我们的函数:
 @property
  这个修饰器用于将方法转化为属性,使其可以像访问属性一样调用。 即不加括号

UML

关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系
  关联(Association)  C类持有一个类型为A的成员变量类实现  用一个带箭头的实线表示
   
聚合关系  聚合(Aggregation)  即has-a的关系,但是整体和部分是可以分离的
   聚合关系可以用带空心菱形的实线来表示,菱形指向整体	
组合关系  组合(Composition)  一种contains a(拥有)关系,这种关系是比聚合还要强,也称为强聚合。体现了严格的整体和部分关系,两者是不可分割的
   组合关系用带实心菱形的实线来表示,菱形指向整体

泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类 泛化(Generalization)
 继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系
 
 
 实现关系 接口与实现类之间的关系。类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作
 
依赖(Dependency), 是use a关系

python 存储

 open()函数是  r+模式表示以读写模式打开文件 写入操作会覆盖原有文件内容
      . r+: 打开并读写文件。如果文件不存在,会抛出FileNotFoundError异常。
      . w+: 写入并读写文件。如果文件存在,内容会被覆盖。如果文件不存在,创建一个新的文件。
      . a+: 在文件的末尾追加读写。如果文件存在,内容会在文件的末尾追加。如果文件不存在,创建一个新的文件
 super().__init__() 
     可以确保在添加子类特有的功能之前,父类中定义的属性和其他必要的设置已经被初始化
	 
 ###文件读写 
     文字I/O (text I/O)、二进制 I/O (binary I/O) 以及原始I/O (raw I/O)
	   文本流的最简单方法是使用 open(),可以选择指定编码:                  f = open("myfile.txt", "r", encoding="utf-8")
	   二进制I/O(也称为缓冲I/O)预期 bytes-like objects 并生成 bytes 对象  f = open("myfile.jpg", "rb")
	   原始 I/O(也称为 非缓冲 I/O)                                        f = open("myfile.jpg", "rb", buffering=0)
	  io.StringIO 对象 io.BytesIO 
	   
    open 函数负责打开文件,并且返回文件对象
    read/write/close 三个方法都需要通过 文件对象 来调用	 
	
    文件指针用于标明文件读写的起始位置
	     文件指针的移动,文件对象提供了 tell() 函数和 seek() 函数。
		  tell() 函数用于判断文件指针当前所处的位置,
		  seek() 函数用于移动文件指针到文件的指定位置。file.seek(offset[, whence])
    	通过移动文件指针的位置,再借助 read() 和 write() 函数,就可以轻松实现,读取文件中指定位置的数据	
	seek(offset, whence=SEEK_SET, /)	
		  os.SEEK_SET :表示文件的相对起始位置 SEEK_SET 或 0: 从流的起始位置开始查找(默认值)offset 必须为 TextIOBase.tell() 所返回的数值或为零

   os.SEEK_CUR :表示文件的相对当前位置 SEEK_CUR 或 1: "查找" 到当前位置;offset 必须为零,表示无操作
   os.SEEK_END :表示文件的相对结束位置 SEEK_END 或 2: 查找到流的末尾;offset 必须为零

    truncate 将流的大小调整为给定的 size 个字节(如果未指定 size 则调整至当前位置--主要用于文件编辑操作
          size -- 如果可选参数存在,文件被截断(最多)的大小  将文件大小调整为给定的字节数
		用法一:不设置truncate()参数
		    把一个文件流截断,不带参数时,就在当前位置截断。
        用法二:设置truncate()参数  
		  size=0 用于清空文件内容 
    flush() 刷新流的写入缓冲区(如果适用)。这对只读和非阻塞流不起作用。
	
	
	fileno()返回流的底层文件描述符(整数)---如果存在。如果 IO 对象不使用文件描述符,则会引发 OSError
	
用于强制将文件描述符 fd 对应的文件的所有未写入的数据写入磁盘。这个函数在 Unix-like 系统上可用		
    os.fdatasync(fd) 或 os.fsync(fd) 之前,你需要确保已经通过 os.open() 或其他方式打开了文件并获得了其文件描述符	
    os.fsync(fd) 用于将所有挂起的、与文件描述符 fd 相关的写操作强制写入到其底层的物理设备中。
	               换句话说,它会确保文件的所有更改都已经从操作系统的缓存中刷新到磁盘上。
				   
cachetools 是一个Python库,提供了各种内存缓存的实现。它可以用于函数结果缓存、对象缓存等场景,能够有效提升程序性能,减少重复计算。
  提供多种缓存策略(LRU, TTL, LFU等)
    LRU Cache (Least Recently Used)   LRU缓存 会优先淘汰最近最少使用的项目。
    LFU Cache (Least Frequently Used) LFU缓存 会优先淘汰使用频率最低的项目
    TTL Cache (Time-To-Live)          TTL缓存 中的项目在指定时间后过期。
Python中创建缓存,我们可以使用 functools 模块中的 @cache装饰器

frozenset函数

frozenset 可以通过将一个可迭代对象传递给 frozenset() 构造函数来创建	
    Python中的一个内置数据类型,表示一个不可变的集合 
	    线程安全‌:由于frozenset的内容不可变,它在多线程环境中是安全的
		哈希表键‌:frozenset可以作为哈希表的键,因为其内容不可变,适合作为字典的键使用‌
		集合的集合:由于 set 是不可哈希的,不能作为集合的元素。如果需要在集合中存储集合,可以使用 frozenset
		
哈希表的键要求是不可变且可哈希的 Dict()	
      可哈希(hashable)意味着对象的内容不可以改变,并且可以通过 __hash__ 方法返回其哈希值
集合(set)的元素必须是可哈希的,是指集合内的元素必须是不可变类型,因为集合是使用哈希表实现的	
def freeze(obj):
    """
    Freeze an object by making it immutable and thus hashable.
    """
    if isinstance(obj, dict):
        # Transform dicts into ``FrozenDict``s
        return FrozenDict((k, freeze(v)) for k, v in obj.items())
    elif isinstance(obj, list):
        # Transform lists into tuples
        return tuple(freeze(el) for el in obj)
    elif isinstance(obj, set):
        # Transform sets into ``frozenset``s
        return frozenset(obj)
    else:
        # Don't handle all other objects
        return obj

python ABC 模块

 基类和C++
 python 抽象类在 ABC 模块中提供,使用 abstractmethod 配合 NotImplementedError 异常定义		
	  ABC,Abstract Base Class(抽象基类),主要定义了基本类和最基本的抽象方法,可以为子类定义共有的API,不需要具体实现。
	   相当于是Java中的接口或者是抽象类。

	抽象基类提供了逻辑和实现解耦的能力,即在不同的模块中通过抽象基类来调用,可以用最精简的方式展示出代码之间的逻辑关系,让模块之间的依赖清晰简单   
包内容 
   定义了一个特殊的metaclass:ABCMeta  抽象基类可以通过从 ABC 派生来简单地创建
   还有一些装饰器:@abstractmethod 和 @abstarctproperty 。“抽象方法” @abstractmethod ,“抽象属性” @abstractproperty 。	
混合类(mix-in类) 小型的类,它只是定义了其他类可能需要的一套附加方法,而不定义自己实例属性,此外,它也不要求使用者调用自己的构造器。	

在 C++ 中,抽象基类是通过定义纯虚函数来实现的。纯虚函数的定义形式为 = 0
   C++ 中的抽象基类通常需要定义虚析构函数 (virtual destructor),以确保通过基类指针删除子类对象时,能够正确调用子类的析构函数
    C++ 中,只有将基类方法声明为 virtual 时,才会启用动态绑定

Python collections 模块

MutableMapping 是 Python collections.abc 模块中的一个抽象基类,它定义了一个可变映射对象的最小接口。
可变映射指的是可以进行增、删、改操作的映射类型,-实现一个继承 MutableMapping 的自定义字典类
  至少实现以下五个方法:
    getitem(self, key):通过键访问值(如 mydict[key])。
    setitem(self, key, value):将键和值关联(如 mydict[key] = value)
    delitem(self, key):删除给定键的键值对(如 del mydict[key])
    iter(self):返回一个可迭代对象,用于遍历字典的键。        def __iter__(self) -> Iterator[K]:
    len(self):返回字典中的键值对数量(如 len(mydict))。     def __len__(self) -> int:
已经实现的方法,例如 get(), keys(), values(), items() 等
      缓存系统
        在实现缓存机制时,我们通常需要记录每个缓存项的使用情况,以便在缓存容量达到上限时淘汰最少使用的项。
		通过继承 MutableMapping,可以轻松创建一个类似于 LRU(最近最少使用)缓存的类。
        
      数据库连接池
        在一些应用中,管理数据库连接池时,
		我们可能希望每次访问或使用连接时,记录其使用顺序,从而管理活跃连接的数量。

实现一个自定义的 LRU 缓存,可以使用 collections.OrderedDict 。
   collections.OrderedDict 还提供了 popitem() 和 move_to_end() 两个方法,用于删除元素和调整元素顺序
  move_to_end(key, last=True):将指定的键移动到有序字典的开头或末尾。
       当 last=True 时(默认值),将键移动到末尾;当 last=False 时,将键移动到开头

python typing模块

 typing模块为Python的静态类型注解提供了支持:能让开发者更清晰地了解函数和变量的预期类型
     内置的类型别名,比如 List Tuple Dict  等,可以用于注解变量和函数的预期类型
	 Union 允许参数接受多种不同类型的数据
	 Optional 表示参数可以是指定类型或者None     Optional[str] 相当于 str|None
	 Any 代表任何类型,等同于无类型提示
	TypeVar 允许创建泛型函数或类  Callable 和 Sequence 等泛型类型的使用   泛型(Generic) 
	from typing import Generator
静态类型检查工具辅助,不会影响Python的动态特性,可以选择性地使用类型注解
   TYPE_CHECKING 时,你可以提供额外的信息,这些信息对于类型检查器(如mypy)是有用的,但不应该在运行时执行

typing 模块还提供了一些用于类型检查的工具,如 isinstance()、issubclass() 等。   
 
typing 模块中,Protocol 是一个用于定义协议(Protocol)的类。
     协议是一种形式化的接口,定义了一组方法或属性的规范,而不关心具体的实现。
	Protocol 类提供了一种方式来定义这些协议。
List[T],Set[T],Dict[K, V]等:用于注解容器类型,T、K、V分别代表容器内元素类型,字典的键和值类型。

函数式编程

嵌套函数和闭包
 封装和模块化-隐藏实现细节--状态保持   
 函数嵌套定义--函数嵌套调用
   命名空间 namespace 以及作用域
      Local(局部)、Enclosing(嵌套)、Global(全局)、Built-in(内置),简称LEGB。
   嵌套函数(Nested Function)是指 定义 在另一个函数内部的函数。
 
闭包的定义
    必须有一个嵌套函数。
    内部函数引用了外部函数的变量。
    外部函数返回了内部函数。
# 使用闭包
   延迟计算 闭包可以用来延迟计算某些值,而不是立即计算。比如,你可以通过闭包实现类似惰性求值的效果。
   装饰器使用 装饰器本质上也是闭包的一种应用。闭包可以在函数执行前后进行额外的操作,增强函数的功能。
   数据封装 闭包可以有效地实现数据的封装,防止外部直接访问数据,同时提供操作数据的接口
 在闭包中修改外层函数的变量,需要使用 nonlocal 关键字。否则,闭包只能访问这些变量,而不能修改它们。
 
 
匿名函数和具名函数 
  lambda 表达式,又称匿名函数,是一种无需显式命名的函数。
  在 Python 中,lambda 表达式可以快速定义一个轻量级函数,尤其适合处理简单的、临时的操作逻辑。 
   lambda value: value == rhs,  lambda value: value != rhs,    lambda value: value < rhs,

其他

 Python社区的相关的帮助文件是用rst结尾的文档格式-Sphinx文档生成工具
     .rst文件是ReStructuredText(RST)格式的文件,主要用于编写技术文档、软件说明文件和报告等。‌
	 RST文件使用简单的标记语法,类似于Markdown
 1. 算术运算:用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共七种。
 2. 关系运算:用于比较运算。包括大于(>)、小于(<)、等于(= =)、大于等于(>=)、小于等于(<=)和不等于(!=)六种。
 3. 逻辑运算:用于逻辑运算。包括与(&&)、或(||)、非(!)三种。 布尔运算
 4. 位操作运算:参与运算的量

参考

抽象基类:Python 与 C++ 的比较 https://sieni-blog.com/2024/09/09/abstract-base-class-comparison-of-the-python-and-cpp/	
Python面试题:在 Python 中,如何实现一个 LRU(最近最少使用)缓存?https://blog.csdn.net/bifengmiaozhuan/article/details/140393657
python tinydb 源码阅读 https://game404.github.io/post/python/tinydb/
https://tinydb.readthedocs.io/en/latest/usage.html#remarks-on-storage
https://github.com/msiemens/tinydb/blob/master/tinydb/table.py
posted @ 2024-12-30 17:25  辰令  阅读(75)  评论(0)    收藏  举报