DjangoORM字段参数、常用字段类型及参数、模型和表单验证器详解

由于项目原因必须使用DjangoORM模型,所以今天整理了一下关于DjangoORM模型里的详细内容。包含字段参数、常用字段类型及参数、模型和表单验证器。


一、通用字段参数
这些参数可以应用于多种字段类型:

(1)null:如果为 True,则数据库中该字段可以存储 NULL 值。默认为 False。

(2)blank: 布尔值。如果为 True,则表单验证允许该字段为空。默认为 False。关键区别:blank 控制表单验证(是否允许在表单中留空),而 null 控制数据库存储(是否允许存储 NULL 值)。 它们是独立的设置。通常,对于字符串类型(CharFieldTextField 等),如果允许为空,应该同时设置 blank=Truenull=True

(3)choices: 一个由二元组 (实际存储值, 显示值) 组成的序列,用于提供字段的可选项。例如:STATUS_CHOICES = [(0, '草稿'), (1, '发布')]。在表单中会显示为下拉框。

(4)default: 字段的默认值。

(5)help_text: 在表单中显示的帮助文本,用于提供字段的说明。

(6)primary_key: 布尔值。如果为 True,则该字段为主键。每个模型只能有一个字段设置 primary_key=True。 通常 Django 会自动添加 AutoFieldBigAutoField 作为主键,除非你需要自定义主键字段名或使用复合主键(不推荐)。

(7)unique: 布尔值。如果为 True,则该字段的值在数据库中必须是唯一的。

(8)verbose_name: 字段的人类可读的名称,用于在管理后台等地方显示。如果不指定,Django 会使用字段名并把下划线转换为空格。

(9)db_column: 用于指定数据库字段的名称。通常不需要手动指定,Django 会根据字段名自动生成。只有在需要与现有数据库集成或有特殊的命名规范时才需要使用。

(10)db_index: 布尔值。如果为 True,则在数据库中为此字段创建索引,可以加快查询速度。索引会提高查询速度,但会降低写入速度,并且会占用额外的存储空间。因此,应该只为经常用于查询的字段创建索引。

二、常用字段类型及参数

(1)AutoField: 一个自动递增的整数类型字段,通常用作主键。在 Django 3.2 之前,默认主键类型是 AutoField,之后是 BigAutoField。强烈建议不要手动指定主键,除非有特殊需求。

BigAutoField: 64 位整数类型字段,类似于 AutoField,但支持更大的数值范围。Django 3.2 之后成为默认主键类型。

(2)CharField: 用于存储短字符串。

max_length: 字符串的最大长度,必须指定。

(3)TextField: 用于存储大量文本。

没有 max_length 限制。

IntegerField、BigIntegerField、SmallIntegerField、PositiveIntegerField、PositiveSmallIntegerField: 用于存储各种大小的整数。

(4)FloatField: 用于存储浮点数。对应 SQL 的 FLOATDOUBLE。精度可能存在问题,对于需要精确计算的场景,建议使用 DecimalField

(5)DecimalField: 用于存储固定精度的十进制数。

max_digits: 数字的最大位数(包括整数部分和小数部分)。

decimal_places: 小数位数。

(6)BooleanField: 用于存储布尔值(TrueFalse)。对应 SQL 的 TINYINT(1)BOOLEAN

(7)NullBooleanField:已完全弃用,应该使用 BooleanField(null=True)

(8)DateField: 用于存储日期。

auto_now: 每次保存对象时自动更新为当前日期。通常用于记录修改时间。

auto_now_add: 对象第一次创建时自动设置为当前日期。通常用于记录创建时间。

auto_nowauto_now_add 互斥,不能同时使用。

(9)DateTimeField: 用于存储日期和时间。

auto_now: 每次保存对象时自动更新为当前日期和时间。

auto_now_add: 对象第一次创建时自动设置为当前日期和时间。

auto_nowauto_now_add 互斥,不能同时使用。

(10)TimeField: 用于存储时间。

(11)DurationField: 用于存储时间间隔。

(12)EmailField: 用于存储电子邮件地址。会对输入进行基本的电子邮件格式验证。

(13)FileField: 用于存储文件。

upload_to: 指定文件上传的目录。强烈推荐使用可调用对象(函数或方法),以便更灵活地组织文件存储路径,例如按日期或用户ID等。

max_length: 文件路径的最大长度。

(14)ImageField: 用于存储图像文件。继承自 FileField。需要安装 Pillow 库 (pip install Pillow)。

(15)URLField: 用于存储 URL。会对输入进行基本的 URL 格式验证。

(16)UUIDField: 用于存储 UUID。

(17)GenericIPAddressField: 用于存储 IPv4 或 IPv6 地址。

protocol: 指定协议('both''IPv4''IPv6')。

unpack_ipv4: 如果为 True,则将 IPv4 地址解包为 IPv6 格式。

(18)ForeignKey: 用于定义多对一关系。

to: 关联的模型。

on_delete: 非常重要! 指定删除关联对象时的行为:

models.CASCADE: 级联删除。

models.PROTECT: 阻止删除。

models.SET_NULL: 设置为 NULL,必须配合 null=True 使用。

models.SET_DEFAULT: 设置为默认值,必须配合 default 参数使用。

models.DO_NOTHING: 什么也不做(不推荐,容易造成数据不一致)。

models.SET(value): 设置为指定的值 valuevalue 也可以是一个可调用对象。

related_name: 反向关联的名称。强烈推荐显式指定,提高代码可读性。

related_query_name: 用于反向查询时的查询名称。

db_constraint: 是否在数据库中创建外键约束,默认为 True。通常保持默认值。

(19)ManyToManyField: 用于定义多对多关系。

through: 指定中间模型。当需要在关系中存储额外数据(例如关系创建时间)时,必须使用 through 指定中间模型。

db_table: 指定数据库表的名称。通常不需要指定,Django 会自动生成。

parent_link: 仅用于多表继承的情况,表示该字段是父模型的链接。并非所有 ManyToManyField 都会用到。

(20)OneToOneField: 用于定义一对一关系。本质上就是一个 unique=TrueForeignKey

再次强调的重要点:

理解 blanknull 的区别及其组合使用方式。

on_delete 参数至关重要,根据业务逻辑谨慎选择。

强烈建议为 ForeignKeyManyToManyField 指定 related_name

NullBooleanField 已完全弃用。

Django 3.2 之后,默认主键类型是 BigAutoField

auto_nowauto_now_add 互斥。

对于浮点数精确计算,使用 DecimalField

结合上面通用字段参数和常用字段类型及参数编写几个关于电商平台的表:

import uuid
from django.db import models
from django.core.exceptions import ValidationError
from django.db.models import F
from django.utils.translation import gettext_lazy as _class Category(models.Model):"""商品分类"""name = models.CharField(_("分类名称"), max_length=50, unique=True)description = models.TextField(_("分类描述"), blank=True, null=True)def __str__(self):return self.nameclass Meta:verbose_name = _("商品分类")verbose_name_plural = _("商品分类") # 添加复数形式class Tag(models.Model):"""商品标签"""name = models.CharField(_("标签名称"), max_length=20, unique=True)def __str__(self):return self.nameclass Meta:verbose_name = _("商品标签")verbose_name_plural = _("商品标签")class Product(models.Model):"""商品信息"""STATUS_CHOICES = [(0, _('下架')),(1, _('上架')),]id = models.UUIDField(_("商品ID"), primary_key=True, default=uuid.uuid4, editable=False)name = models.CharField(_("商品名称"), max_length=100, db_index=True)description = models.TextField(_("商品描述"), blank=True, null=True)price = models.DecimalField(_("商品价格"), max_digits=14, decimal_places=2)stock = models.PositiveIntegerField(_("商品库存"), default=0)created_at = models.DateTimeField(_("创建时间"), auto_now_add=True)updated_at = models.DateTimeField(_("更新时间"), auto_now=True)status = models.IntegerField(_("商品状态"), choices=STATUS_CHOICES, default=1)category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='products', verbose_name=_("商品分类"))tags = models.ManyToManyField(Tag, related_name='products', blank=True, verbose_name=_("商品标签"))image = models.ImageField(_("商品图片"), upload_to='products/', blank=True, null=True)def __str__(self):return self.nameclass Meta:verbose_name = _("商品")verbose_name_plural = _("商品")ordering = ['-created_at']class Order(models.Model):"""订单信息"""STATUS_CHOICES = [(0, _('待付款')),(1, _('已付款')),(2, _('已发货')),(3, _('已完成')),(4, _('已取消')),]order_number = models.CharField(_("订单编号"), max_length=20, unique=True)created_at = models.DateTimeField(_("下单时间"), auto_now_add=True)status = models.IntegerField(_("订单状态"), choices=STATUS_CHOICES, default=0)def __str__(self):return self.order_numberclass Meta:verbose_name = _("订单")verbose_name_plural = _("订单")class OrderItem(models.Model):"""订单条目"""order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items', verbose_name=_("所属订单"))product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='order_items', verbose_name=_("商品"))quantity = models.PositiveIntegerField(_("购买数量"))price = models.DecimalField(_("购买时价格"), max_digits=14, decimal_places=2)def clean(self):if self.quantity > self.product.stock:raise ValidationError({'quantity': _('库存不足')}) # 翻译错误信息def save(self, *args, **kwargs):# 使用 F() 表达式原子性地更新库存,避免并发问题if self.pk is None: #仅在创建新OrderItem时更新库存Product.objects.filter(pk=self.product.pk).update(stock=F('stock') - self.quantity)super().save(*args, **kwargs)class Meta:verbose_name = _("订单条目")verbose_name_plural = _("订单条目")# 移除信号处理,使用 F() 表达式更高效和安全
# @receiver(pre_save, sender=OrderItem)
# def check_stock(sender, instance, **kwargs):
#     if instance.quantity > instance.product.stock:
#         raise ValidationError({'quantity': '库存不足'})
# 关于库存管理的更详细解释:
# 使用 F() 表达式是解决并发环境下库存管理的最佳实践。假设有两个用户同时购买同一件商品,如果使用 Python 代码先读取库存,然后减去购买数量再保存,就可能出现以下情况:
# 用户 A 读取库存,假设为 10。
# 用户 B 读取库存,此时库存仍然为 10。
# 用户 A 购买 5 件,库存更新为 5。
# 用户 B 购买 5 件,库存更新为 5。
# 结果是两个用户都购买了 5 件,总共购买了 10 件,而实际上库存只减少了 5 件,导致超卖。

三、模型和表单验证器

(1)数值验证器:

MaxValueValidator(limit_value): 验证值是否小于或等于 limit_value

MinValueValidator(limit_value): 验证值是否大于或等于 limit_value

DecimalValidator(max_digits, decimal_places): 验证十进制数的位数和精度。max_digits 是总位数(包括小数位),decimal_places 是小数位数。

IntegerValidator: 验证值是否为整数。

(2)字符串验证器:

EmailValidator(message=None, code=None, allowlist=None): 验证值是否为有效的电子邮件地址。allowlist 参数允许指定额外的域名白名单。

RegexValidator(regex, message=None, code=None, inverse_match=None, flags=0): 使用正则表达式 regex 验证值。inverse_match 参数用于反向匹配(即值 不 应该匹配正则表达式)。flags 参数用于设置正则表达式的标志(例如 re.IGNORECASE)。

URLValidator(schemes=None, message=None, code=None): 验证值是否为有效的 URL。schemes 参数允许指定允许的 URL 协议列表(例如 ['http', 'https'])。

MaxLengthValidator(limit_value): 验证字符串的最大长度。

MinLengthValidator(limit_value): 验证字符串的最小长度。

(3)文件验证器:

FileExtensionValidator(allowed_extensions, message=None, code=None): 验证上传文件的扩展名是否在 allowed_extensions 列表中。

(4)其他验证器和验证函数:

validate_email(value): 验证值是否为有效的电子邮件地址。(功能与 EmailValidator 类似,但可以直接作为函数使用。)

validate_slug(value): 验证值是否为有效的 slug(URL 中使用的短标签,通常只包含字母、数字、连字符和下划线)。

validate_unicode_slug(value): 验证值是否为有效的 Unicode slug。

validate_ipv4_address(value): 验证值是否为有效的 IPv4 地址。

validate_ipv6_address(value): 验证值是否为有效的 IPv6 地址。

validate_ipv46_address(value): 验证值是否为有效的 IPv4 或 IPv6 地址。

(5)使用方法:

这些验证器可以用于模型字段和表单字段。

(6)模型字段: 将验证器实例作为列表传递给字段的 validators 参数。

(7)表单字段: 同样,将验证器实例列表传递给字段的 validators 参数。

(8)自定义验证器:

除了内置验证器,你还可以创建自定义验证器。自定义验证器是一个可调用对象(例如函数或带有 __call__ 方法的类),它接收一个值,并在值无效时引发 django.core.exceptions.ValidationError 异常。

结合上面模型和表单验证器和上面的案例重新编写关于电商平台的表:

import uuid
from django.db import models, transaction
from django.db.models import F
from django.utils.translation import gettext_lazy as _
from django.core.validators import (MaxValueValidator,MinValueValidator,DecimalValidator,RegexValidator,URLValidator,MaxLengthValidator,MinLengthValidator,FileExtensionValidator,validate_email,validate_slug,
)
from django import formsclass Category(models.Model):"""商品分类"""name = models.CharField(_("分类名称"), max_length=50, unique=True)description = models.TextField(_("分类描述"), blank=True, null=True)def __str__(self):return self.nameclass Meta:verbose_name = _("商品分类")verbose_name_plural = _("商品分类")class Tag(models.Model):"""商品标签"""name = models.CharField(_("标签名称"), max_length=20, unique=True)def __str__(self):return self.nameclass Meta:verbose_name = _("商品标签")verbose_name_plural = _("商品标签")class Product(models.Model):"""商品信息"""STATUS_CHOICES = [(0, _('下架')),(1, _('上架')),]id = models.UUIDField(_("商品ID"), primary_key=True, default=uuid.uuid4, editable=False)name = models.CharField(_("商品名称"), max_length=100, db_index=True, validators=[MinLengthValidator(2)])description = models.TextField(_("商品描述"), blank=True, null=True)price = models.DecimalField(_("商品价格"), max_digits=14, decimal_places=2, validators=[MinValueValidator(0)])stock = models.PositiveIntegerField(_("商品库存"), default=0) # 移除不必要的MaxValueValidator,或根据实际业务调整created_at = models.DateTimeField(_("创建时间"), auto_now_add=True)updated_at = models.DateTimeField(_("更新时间"), auto_now=True)status = models.IntegerField(_("商品状态"), choices=STATUS_CHOICES, default=1)category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name='products', verbose_name=_("商品分类"))tags = models.ManyToManyField(Tag, related_name='products', blank=True, verbose_name=_("商品标签"))image = models.ImageField(_("商品图片"), upload_to='products/', blank=True, null=True, validators=[FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif'])])def __str__(self):return self.nameclass Meta:verbose_name = _("商品")verbose_name_plural = _("商品")ordering = ['-created_at']class Order(models.Model):"""订单信息"""STATUS_CHOICES = [(0, _('待付款')),(1, _('已付款')),(2, _('已发货')),(3, _('已完成')),(4, _('已取消')),]order_number = models.CharField(_("订单编号"), max_length=20, unique=True, db_index=True, validators=[RegexValidator(r'^[A-Za-z0-9-]+$', _('订单编号只能包含字母、数字和连字符'))])created_at = models.DateTimeField(_("下单时间"), auto_now_add=True)status = models.IntegerField(_("订单状态"), choices=STATUS_CHOICES, default=0)def __str__(self):return self.order_numberclass Meta:verbose_name = _("订单")verbose_name_plural = _("订单")class OrderItem(models.Model):"""订单条目"""order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items', verbose_name=_("所属订单"))product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='order_items', verbose_name=_("商品"))quantity = models.PositiveIntegerField(_("购买数量"), validators=[MinValueValidator(1)])price = models.DecimalField(_("购买时价格"), max_digits=14, decimal_places=2, validators=[MinValueValidator(0)])def save(self, *args, **kwargs):with transaction.atomic():if self.pk is None:Product.objects.filter(pk=self.product.pk).update(stock=F('stock') - self.quantity)else:old_item = OrderItem.objects.get(pk=self.pk)stock_diff = old_item.quantity - self.quantityProduct.objects.filter(pk=self.product.pk).update(stock=F('stock') + stock_diff)super().save(*args, **kwargs)def delete(self, *args, **kwargs):with transaction.atomic():Product.objects.filter(pk=self.product.pk).update(stock=F('stock') + self.quantity)super().delete(*args, **kwargs)class Meta:verbose_name = _("订单条目")verbose_name_plural = _("订单条目")class OrderItemForm(forms.ModelForm):class Meta:model = OrderItemfields = '__all__'def clean_quantity(self):quantity = self.cleaned_data['quantity']product = self.cleaned_data['product']if quantity > product.stock:raise forms.ValidationError(_('库存不足'))return quantity

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

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

相关文章

Go语言的数据库交互

Go语言的数据库交互 引言 Go语言自2009年发布以来,因其简洁、高效,并发编程支持等特点,逐渐被广泛应用于后端开发、云计算等领域。在实际开发中,数据库交互是应用程序的重要组成部分,本文将探讨Go语言如何与不同类型…

Matlab回归预测大合集(不定期更新)-188

截至2025-1-2更新 1.BP神经网络多元回归预测(多输入单输出) 2.RBF神经网络多元回归预测(多输入单输出) 3.RF随机森林多元回归预测(多输入单输出) 4.CNN卷积神经网络多元回归预测(多输入单输…

【读书与思考】历史是一个好东西

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】【读书与思考】 导言 以后《AI日记》专栏我想专注于 AI 相关的学习、成长和工作等。而与 AI 无关的一些读书、思考和闲聊,我打算写到这里,我会尽量控制自己少想和少写。 下图的一些感想…

Git使用mirror备份和恢复

Git使用mirror备份和恢复 使用到的命令总结备份1.进入指定代码仓库,拷贝地址2.进入要备份到的文件夹,右键打开git命令行,输入以下命令3.命令执行完成后会生成一个新文件夹 恢复1.在gitee上创建代码仓库![请添加图片描述](https://i-blog.csdn…

人工智能的可解释性:从黑箱到透明

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​ ​ 人工智能(AI)的快速发展和广泛应用,带来了许多革新的成果,但也引发了对其透明性和可解释…

前后端环境配置java/vue/maven/node.js/mysql

java环境配置 maven环境配置 node环境安装 mysql8安装 workbench安装 开发工具下载安装 redis安装配置 git安装配置 Java 简介:Java 是一种广泛使用的面向对象的编程语言,由 Sun Microsystems(现为 Oracle)于 1995 年发布…

Nacos注册中心介绍及部署

文章目录 Nacos注册中心介绍及部署1. 注册中心简介2. 注册中心原理3. Nacos部署-基于Docker3.1 Nacos官网下载3.2 基础数据信息3.3 环境信息3.4 docker安装部署3.5 测试3.5 测试 Nacos注册中心介绍及部署 1. 注册中心简介 Spring Cloud注册中心是Spring Cloud微服务架构中的一…

Nginx与frp结合实现局域网和公网的双重https服务

背景: 因为局域网内架设了 tiddlywiki、 Nextcloud 等服务,同时也把公司的网站架设在了本地,为了实现局域网直接在局域网内访问,而外部访问通过frps服务器作为反向代理的目的,才有此内容。 实现的效果如下图琐事 不喜欢…

zephyr移植到STM32

Zephy如何移植到单片机 1. Window下搭建开发环境1.1 安装Choncolatey1.2 安装相关依赖1.3创建虚拟python环境1.4 安装west1.4.1 使用 pip 安装 west1.4.2 检查 west 安装路径1.4.3 将 Scripts路径添加到环境变量1.4.4 验证安装 1.5 获取zephyr源码和[安装python](https://so.cs…

【分糖果——DFS】

题目 代码1 #include <bits/stdc.h> using namespace std; set<string> s; void dfs(int num1, int num2, int u, string ans) {if (u 7){if (num1 num2 > 5)return;ans (char)((num1) * 17 num2);s.insert(ans);return;}for (int i 0; i < num1; i){f…

buildroot 编译 x264 及 ffmpeg

buildroot 编译 x264 及 ffmpeg 依赖安装x264编译安装解压源码并修改配置文件配置及编译编译错误: aarch64-linux-ar: two different operation options specified编译结果ffmpeg安装源码编译错误 : ERROR: x264 not found using pkg-config为在rk3568平台上开发音视频采集及…

【HarmonyOS】鸿蒙应用实现屏幕录制详解和源码

【HarmonyOS】鸿蒙应用实现屏幕录制详解和源码 一、前言 官方文档关于屏幕录制的API和示例介绍获取简单和突兀。使用起来会让上手程度变高。所以特意开篇文章&#xff0c;讲解屏幕录制的使用。官方文档参见&#xff1a;使用AVScreenCaptureRecorder录屏写文件(ArkTS) 二、方…

解决在VS2019/2022中编译c++项目报错fatal error C1189: #error : “No Target Architecture“

解决在VS2019/2022中编译c项目报错fatal error C1189: #error : “No Target Architecture” 报错原因 在winnt.h中&#xff0c;不言而喻&#xff0c;一目了然&#xff1a; 代码节选&#xff1a; #if defined(_AMD64_) || defined(_X86_) #define PROBE_ALIGNMENT( _s ) TY…

统计素数并求和(PTA)C语言

本题要求统计给定整数M和N区间内素数的个数并对它们求和。 输入格式: 输入在一行中给出两个正整数M和N&#xff08;1≤M≤N≤500&#xff09;。 输出格式: 在一行中顺序输出M和N区间内素数的个数以及它们的和&#xff0c;数字间以空格分隔。 输入样例: 10 31输出样例: 7…

Python教程丨Python环境搭建 (含IDE安装)——保姆级教程!

工欲善其事&#xff0c;必先利其器。 学习Python的第一步不要再加收藏夹了&#xff01;提高执行力&#xff0c;先给自己装好Python。 1. Python 下载 1.1. 下载安装包 既然要下载Python&#xff0c;我们直接进入python官网下载即可 Python 官网&#xff1a;Welcome to Pyt…

Java中时间类的转换是怎么进行的

String转TimeStamp DateFormat format new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Timestamp startTime new Timestamp(format.parse(startDate).getTime()); TimeStamp转String String nowDate new SimpleDateFormat("yyyyMMdd").format(nowTimeS…

如何限制大量请求并发

前言&#xff1a; 1、主流浏览器在 HTTP/1.1 下对同一域名的最大并发请求数通常是 6~8 个。超过限制的请求会进入队列&#xff0c;等待空闲的连接。 2、可以利用Promise模拟任务队列&#xff0c;控制并发请求数量&#xff0c;以避免对服务器造成过大的压力。&#xff08;先进先…

LeetCode:2274. 不含特殊楼层的最大连续楼层数(排序 Java)

目录 2274. 不含特殊楼层的最大连续楼层数 题目描述&#xff1a; 实现代与解析&#xff1a; 排序 原理思路&#xff1a; 2274. 不含特殊楼层的最大连续楼层数 题目描述&#xff1a; Alice 管理着一家公司&#xff0c;并租用大楼的部分楼层作为办公空间。Alice 决定将一些…

实现AVL树

目录 AVL树概念 AVL树结构 AVL树插入 LL型 - 右单旋 RR型 - 左单旋 LR型 - 左右双旋 RL型 - 右左双旋 插入代码实现 AVL树测试 附AVL树实现完整代码 AVL树概念 前面的博客介绍了搜索二叉树&#xff0c;二叉搜索树-CSDN博客 在某些特定的情况下&#xff0c;⼆叉搜索树…

极客说|微软 Phi 系列小模型和多模态小模型

作者&#xff1a;胡平 - 微软云人工智能高级专家 「极客说」 是一档专注 AI 时代开发者分享的专栏&#xff0c;我们邀请来自微软以及技术社区专家&#xff0c;带来最前沿的技术干货与实践经验。在这里&#xff0c;您将看到深度教程、最佳实践和创新解决方案。关注「极客说」&am…