Python 入门 —— 描述器

Python 入门 —— 描述器

文章目录

  • Python 入门 —— 描述器
    • 描述器
      • 简单示例
      • 定制名称
      • 只读属性
      • 状态交互
      • 验证器类
        • 自定义验证器
        • 验证器的使用
      • 对象关系映射

描述器

前面我们介绍了两种属性拦截的方式:特性(property)以及重载属性访问运算符,还有一种方式是通过描述器来拦截属性访问,特性只是一种特定类型的描述器的简化使用方式,而描述器也是由 __getattribute__ 函数调用。

描述器允许我们把特定属性的 getsetdel 操作指向独立的类对象方法,使对象能够自定义属性查找、存储和删除操作。

任何实现了:__get____set____delete__ 方法中的一种的类都可以称为描述器。

描述器的主要目的是提供一个挂钩,允许存储在类变量中的对象控制在属性查找期间发生的情况。

传统上,调用类控制查找过程中发生的事情。但描述器反转了这种关系,并允许正在被查询的数据对此进行干涉。

简单示例

class Name:"""Name descriptor doc"""def __get__(self, obj, objtype=None):print('get')return obj._namedef __set__(self, obj, value):print('set')obj._name = valuedef __delete__(self, obj):print('del')del obj._nameclass Person:name = Name()def __init__(self, name):self.name = nametom = Person('Tom')
# set
tom.name
# get
# 'Tom'
tom.name = 'Robert'
# set
tom.name
# get
# 'Robert'
vars(tom)
# {'_name': 'Robert'}
del tom.name
# del

虽然我们为 Person 定义了 name 属性,但是存储在 __dict__ 中的还是 _name(与 Name 描述符中使用的属性一致)。

注意

必须将描述符赋值给一个类属性,如果赋值为实例属性,将无法工作,可以自己尝试一下。注意这三个方法的参数,obj 表示 Person 类实例对象,objtype 默认为 Person

定制名称

在上面的例子中,我们定义的描述符只能用于 _name 属性,但是一般我们所定义的描述符都希望其能够适用于更多的属性,这显然不符合我们实际的需求。

我们可以添加 __set_name__ 方法来记录字段名称,让每个描述符都有自己的公有属性和私有属性名称,例如

class Field:def __set_name__(self, owner, attr):self.public_name = attrself.private_name = '_' + attrdef __get__(self, obj, objtype=None):value = getattr(obj, self.private_name)print('Accessing', self.public_name)return valuedef __set__(self, obj, value):print('Updating', self.public_name, 'to', value)setattr(obj, self.private_name, value)class Person:name = Field()age = Field()def __init__(self, name, age):self.name = nameself.age = agetom = Person('Tom', 19)
# Updating name to Tom
# Updating age to 19
tom.name
# Accessing name
# 'Tom'
vars(tom)
# {'_name': 'Tom', '_age': 19}
tom._name
# 'Tom'
vars(vars(Person)['name'])
# {'public_name': 'name', 'private_name': '_name'}

其中 owner 是使用描述器的类,name 是分配给描述器的类变量名。

vars 函数只是查找属性并不会触发方法,访问私有属性也不会经过描述符。

只读属性

使用描述符来创建只读属性,光定义 __get__ 方法,而没有定义 __set__ 是不够的,例如

class TestDesc:def __get__(self, obj, obj_type=None):print('Get')class Test:t = TestDesc()a = Test()
a.t
# Get
vars(a)
# {}
a.t = 100
a.t
# 100
vars(a)
# {'t': 100}

对描述符属性赋值会将其覆盖,可以看到赋值后出现了一个常规的公开属性,要设置只读属性,要在 __set__ 方法中让赋值行为引发一个异常

class TestDesc:def __get__(self, obj, obj_type=None):print('Get')def __set__(self, obj, value):raise AttributeError("Con't set value!")class Test:t = TestDesc()a = Test()
a.t
# Get
a.t = 100
# AttributeError: Con't set value!

状态交互

描述符内也可以定义属性值,在 __get____set__ 函数内也可以使用实例参数来获取包含描述符的类的属性值

class Money:def __init__(self, value):self.value = valuedef __get__(self, obj, obj_type=None):return self.value * getattr(obj, 'rate')def __set__(self, obj, value):self.value = valueclass Exchange:money = Money(1000)def __init__(self, rate):self.rate = rates = Exchange(0.567)
s.money
# 567.0
s.rate = 0.678
s.money
# 678.0

上面这段代码,属性 value 仅存在于描述符中,并不会与使用描述符的类中的属性有冲突。money 属性会随着 rate 值的变化自动计算出相应的值。

验证器类

验证器是一个用于托管属性访问的描述器。在存储任何数据之前,它会验证新值是否满足各种类型和范围限制。如果不满足这些限制,它将引发异常,从源头上防止数据损坏。

这一功能通常在前后端数据校验中比较常用,我们可以自己实现一个验证器。例如,我们定义一个抽象类,必须实现 validate 方法

from abc import ABC, abstractmethodclass Validator(ABC):def __set_name__(self, owner, name):self.private_name = '_' + namedef __get__(self, obj, objtype=None):return getattr(obj, self.private_name)def __set__(self, obj, value):self.validate(value)setattr(obj, self.private_name, value)@abstractmethoddef validate(self, value):pass
自定义验证器

我们可以对数据进行常见的几种校验,例如:

  • OneOf:验证值是一组受约束的选项之一。
class OneOf(Validator):def __init__(self, *options):self.options = set(options)def validate(self, value):if value not in self.options:raise ValueError(f'Expected {value!r} to be one of {self.options!r}')
  • Number:验证值是否为 intfloat。根据可选参数,它还可以验证值在给定的最小值或最大值之间。
class Number(Validator):def __init__(self, minvalue=None, maxvalue=None):self.minvalue = minvalueself.maxvalue = maxvaluedef validate(self, value):if not isinstance(value, (int, float)):raise TypeError(f'Expected {value!r} to be an int or float')if self.minvalue is not None and value < self.minvalue:raise ValueError(f'Expected {value!r} to be at least {self.minvalue!r}')if self.maxvalue is not None and value > self.maxvalue:raise ValueError(f'Expected {value!r} to be no more than {self.maxvalue!r}')
  • String:验证值是否为 str。根据可选参数,它可以验证给定的最小或最大长度,还可以验证用户定义的 predicate
class String(Validator):def __init__(self, minsize=None, maxsize=None, predicate=None):self.minsize = minsizeself.maxsize = maxsizeself.predicate = predicatedef validate(self, value):if not isinstance(value, str):raise TypeError(f'Expected {value!r} to be an str')if self.minsize is not None and len(value) < self.minsize:raise ValueError(f'Expected {value!r} to be no smaller than {self.minsize!r}')if self.maxsize is not None and len(value) > self.maxsize:raise ValueError(f'Expected {value!r} to be no bigger than {self.maxsize!r}')if self.predicate is not None and not self.predicate(value):raise ValueError(f'Expected {self.predicate} to be true for {value!r}')
验证器的使用

我们定义一个类,包含三种带验证器的描述器属性

class Component:name = String(minsize=3, maxsize=10, predicate=str.isupper)kind = OneOf('wood', 'metal', 'plastic')quantity = Number(minvalue=0)def __init__(self, name, kind, quantity):self.name = nameself.kind = kindself.quantity = quantity

描述器会阻止错误实例的创建

Component('Widget', 'metal', 5)      # 'Widget' 不是全大写
# Traceback (most recent call last):
#     ...
# ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget'Component('WIDGET', 'metle', 5)      # 'metle' 不在取值范围
# Traceback (most recent call last):
#     ...
# ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'}Component('WIDGET', 'metal', -5)     # quantity 的值要大于等于 0
# Traceback (most recent call last):
#     ...
# ValueError: Expected -5 to be at least 0
Component('WIDGET', 'metal', 'V')    # quantity 不是数值
# Traceback (most recent call last):
#     ...
# TypeError: Expected 'V' to be an int or floatc = Component('WIDGET', 'metal', 5)  # 通过

对象关系映射

我们可以使用描述器来实现一个简单的对象关系映射(ORM)框架

其核心思路是将数据存储在外部数据库中,Python 实例仅持有数据库表中对应的的键,描述器负责对值进行查找或更新。

我们首先定义字段描述器

class Field:def __set_name__(self, owner, name):self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;'self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;'def __get__(self, obj, objtype=None):return conn.execute(self.fetch, [obj.key]).fetchone()[0]def __set__(self, obj, value):conn.execute(self.store, [value, obj.key])conn.commit()

然后用 Field 类来定义描述了数据库中每张表的模式(models)。

class Movie:table = 'Movies'                    # Table namekey = 'title'                       # Primary keydirector = Field()year = Field()def __init__(self, key):self.key = keyclass Song:table = 'Music'key = 'title'artist = Field()year = Field()genre = Field()def __init__(self, key):self.key = key

要使用模型,首先要创建一个数据库

def create_tables(conn):with conn:conn.execute('''CREATE TABLE IF NOT EXISTS Movies (title TEXT PRIMARY KEY,director TEXT,year INTEGER)''')conn.execute('''CREATE TABLE IF NOT EXISTS Music (title TEXT PRIMARY KEY,artist TEXT,year INTEGER,genre TEXT)''')def insert_movie(conn, title, director, year):with conn:conn.execute('''INSERT INTO Movies (title, director, year) VALUES (?, ?, ?)''', (title, director, year))def insert_song(conn, title, artist, year, genre):with conn:conn.execute('''INSERT INTO Music (title, artist, year, genre) VALUES (?, ?, ?, ?)''', (title, artist, year, genre))conn = sqlite3.connect('example.db')
create_tables(conn)insert_movie(conn, 'Inception', 'Christopher Nolan', 2010)
insert_movie(conn, 'The Matrix', 'Lana Wachowski, Lilly Wachowski', 1999)insert_song(conn, 'Bohemian Rhapsody', 'Queen', 1975, 'Rock')
insert_song(conn, 'Imagine', 'John Lennon', 1971, 'Pop')

从数据库中检索数据及对其进行更新

Movie('Inception').director
# 'Christopher Nolan'
mat = Movie('The Matrix')
f'Released in {mat.year} by {mat.director}'
# 'Released in 1999 by Lana Wachowski, Lilly Wachowski'Song('Imagine').artist
# 'John Lennon'Movie('Inception').director = 'Nolan'
Movie('Inception').director
# 'Nolan'

关闭数据库连接

conn.close()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34871.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

更适合敏感口腔的护理牙刷

最近在用一款清九野小红盾舒敏牙刷&#xff0c;感觉它很适合牙龈敏感的人&#xff0c;让刷牙体验有了显著的提升。这款牙刷的柔软刷毛和精细设计让我的刷牙过程变得轻松愉快。它的内外圈双重植毛技术&#xff0c;在清洁牙齿的同时&#xff0c;还能深入牙缝&#xff0c;温和地去…

设计模式原则——单一职责原则(SPS)

设计模式原则 设计模式示例代码库地址&#xff1a; https://gitee.com/Jasonpupil/designPatterns 单一职责原则&#xff08;SPS&#xff09;&#xff1a; 又称单一功能原则&#xff0c;面向对象五个基本原则&#xff08;SOLID&#xff09;之一原则定义&#xff1a;一个类应…

linux启动jar包,提示jvm内存不足

出现场景 服务器内存还剩余很多&#xff0c;但是启动jar报内存不足 Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00007f66434f0000, 65536, 1) failed; error无法分配内存 (errno12) [thread 140076481570560 also had an error] # # Compiler re…

基于Docker的淘客返利平台部署

基于Docker的淘客返利平台部署 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;在本文中&#xff0c;我们将探讨如何利用Docker技术来部署一个淘客返利平台。Doc…

还能这样执行命令?命令执行绕过及防护规则研究

一、引言 我是渗透工程师->很多小伙伴在做攻防实战时发现有时在命令执行的payload中穿插单双引号命令也能执行成功&#xff0c;有时却又不行。那么到底在什么条件下用什么样的方式能实现对命令的切分呢&#xff1f;其中的原理又是如何&#xff1f;有没有其他绕过方式&#…

openlayers 轨迹回放(历史轨迹),实时轨迹

本篇介绍一下使用openlayers轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;实时轨迹 1 需求 轨迹回放&#xff08;历史轨迹&#xff09;实时轨迹 2 分析 主要是利用定时器&#xff0c;不断添加feature 轨迹回放&#xff08;历史轨迹&#xff09;&#xff0c;一般是…

Ubuntu 22.04 MySQL安装并设置远程访问

Ubuntu 22.04 LTS环境下 1 安装 # 更新软件包列表 sudo apt update# 查看可使用的安装包 sudo apt search mysql-server# 安装最新版本&#xff08;显示冲突装不了&#xff0c;可以先卸了再装&#xff09; sudo apt install -y mysql-server # 安装指定版本 sudo apt install…

rga_mm: RGA_MMU unsupported Memory larger than 4G!解决

目录 报错完整log如下:解决方案:报错完整log如下: [ 3668.824164] rga_mm: RGA_MMU unsupported Memory larger than 4G! [ 3668.824305] rga_mm: scheduler core[4] unsupported mm_flag[0x0]! [ 3668.824320] rga_mm: rga_mm_map_buffer map dma_buf err

如何有效地优化 Erlang 程序的内存使用,以应对大规模数据处理的需求?

要有效地优化Erlang程序的内存使用&#xff0c;以应对大规模数据处理的需求&#xff0c;可以考虑以下几个方面&#xff1a; 减少不必要的内存分配&#xff1a;避免过多的数据复制和不必要的数据结构创建。可以使用Erlang的二进制数据类型来避免数据复制&#xff0c;使用原子数据…

Linux环境下安装MySQL5.7.20(源码安装)

&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;本专栏主要发表mysql实战的文章&#xff0c;文章主要包括&#xff1a; 各版本数据库的安装、备份和恢复,性能优化等内容的学习。。 &#x1f4e3; ***如果需要观看配套视频的小伙伴们&#xff0c;请…

【Apache Doris】周FAQ集锦:第 8 期

【Apache Doris】周FAQ集锦&#xff1a;第 8 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户和…

MySQL集群如何实现读写分离

数据源配置&#xff1a;定义了主从数据库的连接池。读写分离规则&#xff1a;通过MasterSlaveRuleConfiguration定义了主从数据库的读写分离规则。负载均衡算法&#xff1a;定义了从数据库的负载均衡算法。创建ShardingDataSource&#xff1a;使用数据源和读写分离规则创建了Sh…

STM32学习-HAL库 定时器

这里先介绍一些HAL的一些库函数&#xff0c;HAL_XXX_Init()这种是外设初始化函数&#xff0c;一般是传入结构体。 HAL_XXX_MspInit()这种是外设硬件相关的初始化函数&#xff0c;包括GPIO,NVIC,CLOCK等&#xff0c;一般会在HAL_XXX_Init()中调用。HAL_XXX_IT()这是与外设中断相…

Scikit-learn:原理与使用指南

文章目录 1. Scikit-learn 的原理1.1 一致性API1.2 模块化和可扩展性1.3 高效性 2. Scikit-learn 的使用2.1 安装 Scikit-learn2.2 导入必要的库和数据2.3 训练模型2.4 预测和评估2.5 交叉验证和网格搜索 3. 总结 Scikit-learn 是 Python 中一个非常强大且易于使用的机器学习库…

【CSS】深入探讨 CSS 的 `calc()` 函数

深入探讨 CSS 的 calc() 函数 calc() 是一个 CSS 函数&#xff0c;用于在样式表中进行数学计算&#xff0c;从而动态地设置 CSS 属性值。它允许开发者在指定长度、百分比、数值等时&#xff0c;进行加减乘除运算。通过 calc() 函数&#xff0c;我们可以实现更灵活和响应式的设…

vue3项目使用@antv/g6实现可视化流程功能

文章目录 项目需求一、需要解决的问题二、初步使用1.动态数据-组件封装(解决拖拽会留下痕迹的问题&#xff0c;引用图片&#xff0c;在节点右上角渲染图标&#xff0c;实现&#xff0c;事现旋转动画&#xff0c;达到loading效果)2.文本太长&#xff0c;超出部分显示(...),如下函…

Fluent udf编译的一些注意事项

Fluent udf编译的一些注意事项 参考链接&#xff1a;1.fluent UDF编译环境处理_哔哩哔哩_bilibili 2.【觉兽课堂】ANSYS FLUENT UDF教学02&#xff1a;UDF语法及编写 小白入门必备_哔哩哔哩_bilibili #1 需要注意的内容 ##1.1 修改vs的路径 在fluent路径中&#xff0c;打开ud…

Golang笔记:使用serial包进行串口通讯

文章目录 目的使用入门总结 目的 串口是非常常用的一种电脑与设备交互的接口。这篇文章将介绍golang中相关功能的使用。 本文使用的包为 &#xff1a;go.bug.st/serial https://pkg.go.dev/go.bug.st/serial https://github.com/bugst/go-serial 另外还有一些常见的包如&…

cpp入门(命名空间,输入输出与缺省参数)

目录 cpp关键字 命名空间 命名空间的使用 1.加名称及作用域限定符 2.使用using将命名空间中某个成员引入 3.展开命名空间 注意 输入输出 缺省参数 cpp关键字 命名空间 定义命名空间&#xff0c;需要使用到namespace关键字&#xff0c;后面跟命名空间的名字&#xff0c…

【odoo | JavaScript | ES6】浅谈前端导入(import)和导出(export)

概要 前端开发中的导入&#xff08;import&#xff09;和导出&#xff08;export&#xff09;是指在JavaScript模块系统中引入和输出代码的机制。ES6&#xff08;ECMAScript 2015&#xff09;引入了这种模块化系统&#xff0c;使开发者可以更加高效地组织和管理代码。 导出 导…