当前位置: 首页 > news >正文

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()

相关文章:

Python 入门 —— 描述器

Python 入门 —— 描述器 文章目录 Python 入门 —— 描述器描述器简单示例定制名称只读属性状态交互验证器类自定义验证器验证器的使用 对象关系映射 描述器 前面我们介绍了两种属性拦截的方式&#xff1a;特性&#xff08;property&#xff09;以及重载属性访问运算符&#…...

测试驱动开发TDD

如何在后端测试代码&#xff0c;测试一个其前端的请求&#xff0c;能否正常处理 以登录请求为例 package com.example.demo.login;import com.example.demo.login.pojo.User; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import…...

[论文笔记]Mixture-of-Agents Enhances Large Language Model Capabilities

引言 今天带来一篇多智能体的论文笔记&#xff0c;Mixture-of-Agents Enhances Large Language Model Capabilities。 随着LLMs数量的增加&#xff0c;如何利用多个LLMs的集体专业知识是一个令人兴奋的开放方向。为了实现这个目标&#xff0c;作者提出了一种新的方法&#xf…...

Redis 7.x 系列【6】数据类型之字符串(String)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 前言2. 常用命令2.1 SET2.2 GET2.3 MSET2.4 MGET2.5 GETSET2.6 STRLEN2.7 SETEX2.8…...

指针(一)

指针基础 在C中&#xff0c;指针是至关重要的组成部分。它是C语言最强大的功能之一&#xff0c;也是最棘手的功能之一。 指针具有强大的能力&#xff0c;其本质是协助程序员完成内存的直接操纵。 指针&#xff1a;特定类型数据在内存中的存储地址&#xff0c;即内存地址。 …...

harmony鸿蒙下实现bc交互的方式和方法

前言 最近在研究harmony操作系统下的交互&#xff0c;因此写一篇文章记录一下。 解决的问题 本篇文章主要是来写解决如果兼容android或者ios的交互&#xff0c;这样子避免h5页面的二次开发&#xff0c;节省资源。 交互的种类 交互对于harmony来说其实只有一种&#xff0c;…...

【MySQL进阶之路 | 高级篇】索引的声明与使用

1. 索引的分类 MySQL的索引包括普通索引&#xff0c;唯一性索引&#xff0c;全文索引&#xff0c;单列索引和空间索引. 从功能逻辑上说&#xff0c;索引主要分为普通索引&#xff0c;唯一索引&#xff0c;主键索引和全文索引.按物理实现方式&#xff0c;索引可以分为聚簇索引…...

探索Java中的设计模式:从单例到工厂模式

探索Java中的设计模式&#xff1a;从单例到工厂模式 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨Java中的设计模式&#xff0c;从经典的单…...

表单(forms)

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在app1文件夹下创建一个forms.py文件&#xff0c;添加如下类代码&#xff1a; from django import forms class PersonForm(forms.Form): first_na…...

Geoserver源码解读四 REST服务

文章目录 文章目录 一、概要 二、前置知识点-FreeMarker 三、前置知识点-AbstractHttpMessageConverter 3.1 描述 3.2 应用 四、前置知识点-AbstractDecorator 4.1描述 4.2 应用 五、工作空间查询解读 5.1 模板解读 5.2 请求转换器解读 一、概要 关于geoserver的r…...

硬件开发笔记(二十一):外部搜索不到的元器件封装可尝试使用AD21软件的“ManufacturerPart Search”功能

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139869584 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…...

【AI大模型】GPTS 与 Assistants API

前言 2023 年 11 月 6 日&#xff0c;OpenAI DevDay 发表了一系列新能力&#xff0c;其中包括&#xff1a;GPT Store 和 Assistants API。 GPTs 和 Assistants API 本质是降低开发门槛 可操控性和易用性之间的权衡与折中&#xff1a; 更多技术路线选择&#xff1a;原生 API、…...

攻击者开始使用 XLL 文件进行攻击

近期&#xff0c;研究人员发现使用恶意 Microsoft Excel 加载项&#xff08;XLL&#xff09;文件发起攻击的行动有所增加&#xff0c;这项技术的 MITRE ATT&CK 技术项编号为 T1137.006。 这些加载项都是为了使用户能够利用高性能函数&#xff0c;为 Excel 工作表提供 API …...

Why RAG is slower than LLM?

I used RAG with LLAMA3 for AI bot. I find RAG with chromadb is much slower than call LLM itself. Following the test result, with just one simple web page about 1000 words, it takes more than 2 seconds for retrieving: 我使用RAG&#xff08;可能是指某种特定的…...

Word页码设置,封面无页码,目录摘要阿拉伯数字I,II,III页码,正文开始123为页码

一、背景 使用Word写项目书或论文时&#xff0c;需要正确插入页码&#xff0c;比如封面无页码&#xff0c;目录摘要阿拉伯数字I&#xff0c;II&#xff0c;III为页码&#xff0c;正文开始以123为页码&#xff0c;下面介绍具体实施方法。 所用Word版本&#xff1a;2021 二、W…...

汽车汽配图纸管理、产品研发管理解决方案

汽车汽配图纸管理、产品研发管理解决方案 随着全球汽车市场的快速发展&#xff0c;中国汽车汽配行业迎来了前所未有的发展机遇。然而&#xff0c;在这一过程中&#xff0c;企业也面临着诸多挑战&#xff0c;如研发能力的提升、技术资料管理的复杂性、以及跨部门协作的困难等。为…...

小程序简单版音乐播放器

小程序简单版音乐播放器 结构 先来看看页面结构 <!-- wxml --><!-- 标签页标题 --> <view class"tab"><view class"tab-item {{tab0?active:}}" bindtap"changeItem" data-item"0">音乐推荐</view><…...

驾校预约管理系统

摘 要 随着驾驶技术的普及和交通安全意识的增强&#xff0c;越来越多的人选择参加驾校培训&#xff0c;以获取驾驶执照。然而&#xff0c;驾校管理面临着日益增长的学员数量和繁琐的预约管理工作。为了提高驾校的管理效率和服务质量&#xff0c;驾校预约管理系统成为了必不可少…...

C++ 左值右值 || std::move() || 浅拷贝,深拷贝 || 数据类型

数据类型&#xff1a; 作用&#xff1a;决定变量所占内存空间的字节大小&#xff0c;和布局方式基本数据类型&#xff1a; 算数类型&#xff1a; 整形&#xff08;bool / char……扩展集 / int / long……&#xff09;&& 浮点形&#xff08;float/double……&#xff…...

发那科机器人IO 分配

IO 信号 也称为输入\输出信号&#xff0c;是机器人与外围设备通信的电信号...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)

参考官方文档&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...

Java 二维码

Java 二维码 **技术&#xff1a;**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...