数据库_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

浙公网安备 33010602011771号