2024年11月

为什么需要
[EnumeratorCancellation]

在使用 C# 编写异步迭代器时,您可能会遇到如下警告:

warning CS8425: 异步迭代器“TestConversationService.ChatStreamed(IReadOnlyList<ChatMessage>, ChatCompletionOptions, CancellationToken)”具有一个或多个类型为 "CancellationToken" 的参数,但它们都未用 "EnumeratorCancellation" 属性修饰,因此将不使用所生成的 "IAsyncEnumerable<>.GetAsyncEnumerator" 中的取消令牌参数。

看到这样的警告,您可能会困惑:
究竟需要在异步迭代器的方法参数上添加
[EnumeratorCancellation]
属性吗?如果不添加,会有什么区别?

让我们深入探讨一下这个问题,揭示其背后的真相。

正常调用时,[EnumeratorCancellation] 的影响

如果您只是简单地在异步迭代器方法中传递一个普通的
CancellationToken
,无论是否使用
[EnumeratorCancellation]
,方法的行为似乎并没有显著区别。例如:

public async IAsyncEnumerable<int> GenerateNumbersAsync(CancellationToken cancellationToken = default)
{
    for (int i = 0; i < 10; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        yield return i;
        await Task.Delay(1000, cancellationToken);
    }
}

public async Task ConsumeNumbersAsync()
{
    CancellationTokenSource cts = new CancellationTokenSource();
    Task cancelTask = Task.Run(async () =>
    {
        await Task.Delay(3000);
        cts.Cancel();
    });

    try
    {
        await foreach (var number in GenerateNumbersAsync(cts.Token))
        {
            Console.WriteLine(number);
        }
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("枚举已被取消");
    }

    await cancelTask;
}

输出如下:

0
1
2
枚举已被取消

在上述代码中,即使没有使用
[EnumeratorCancellation]
,取消令牌
cts.Token
依然会生效,导致迭代过程被取消。这可能会让开发者误以为
[EnumeratorCancellation]
没有实际作用,进而引发更多的困惑。

揭开真相:生产者与消费者的职责分离

实际上,
[EnumeratorCancellation]
的核心作用在于
实现生产者与消费者的职责分离
。具体来说:

  • 生产者
    (即提供数据的异步迭代方法)专注于数据的生成和响应取消请求,不关心取消请求的来源或何时取消。

  • 消费者
    (即使用数据的部分)负责控制取消逻辑,独立地决定何时取消整个迭代过程。

通过这种设计,生产者不需要知道取消请求是由谁或何时发起的,简化了生产者的设计,同时赋予消费者更大的控制权。这不仅提高了代码的可维护性和可复用性,还避免了取消逻辑的混乱。

示例说明

下面通过一个示例,直观地展示
[EnumeratorCancellation]
如何实现职责分离。

1. 定义异步迭代器方法

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public class DataProducer
{
    public async IAsyncEnumerable<int> ProduceData(
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        int i = 0;
        while (true)
        {
            cancellationToken.ThrowIfCancellationRequested();
            Console.WriteLine($"[Iterator] 生成数字: {i}");
            yield return i++;
            await Task.Delay(1000, cancellationToken); // 模拟数据生成延迟
        }
	}
}

在这个
DataProducer
类中,
ProduceData
方法使用
[EnumeratorCancellation]
标注了
cancellationToken
参数。这意味着,当消费者通过
WithCancellation
传递取消令牌时,编译器会自动将该取消令牌传递给
ProduceData
方法的
cancellationToken
参数。

2. 定义消费者方法

using System;
using System.Threading;
using System.Threading.Tasks;

public class DataConsumer
{
	public async Task ConsumeDataAsync(IAsyncEnumerable<int> producer)
	{
		using CancellationTokenSource cts = new CancellationTokenSource();

		// 在5秒后发出取消请求
		_ = Task.Run(async () =>
		{
			await Task.Delay(5000);
			cts.Cancel();
			Console.WriteLine("[Trigger] 已发出取消请求");
		});

		try
		{
			// 通过 WithCancellation 传递取消令牌
			await foreach (var data in producer.WithCancellation(cts.Token))
			{
				Console.WriteLine($"[Consumer] 接收到数据: {data}");
			}
		}
		catch (OperationCanceledException)
		{
			Console.WriteLine("[Consumer] 数据接收已被取消");
		}
	}
}


DataConsumer
类中,
ConsumeDataAsync
方法创建了一个
CancellationTokenSource
,并在5秒后取消它。通过
WithCancellation
方法,将取消令牌传递给
ProduceData
方法。这样,消费者完全控制了取消逻辑,而生产者只需响应取消请求。

3. 执行示例

public class Program
{
	public static async Task Main(string[] args)
	{
		var producer = new DataProducer();
		var consumer = new DataConsumer();
		await consumer.ConsumeDataAsync(producer.ProduceData());
	}
}

预期输出:

[Iterator] 生成数字: 0
[Consumer] 接收到数据: 0
[Iterator] 生成数字: 1
[Consumer] 接收到数据: 1
[Iterator] 生成数字: 2
[Consumer] 接收到数据: 2
[Iterator] 生成数字: 3
[Consumer] 接收到数据: 3
[Iterator] 生成数字: 4
[Consumer] 接收到数据: 4
[Trigger] 已发出取消请求
[Consumer] 数据接收已被取消

在5秒后,取消请求被触发,迭代器检测到取消并抛出
OperationCanceledException
,导致迭代过程被中断。请注意DataConsumer在接收生产出来的数据
IAsyncEnumerable<int>
时,已经错过了在生产函数中传入
cancellationToken
的机会,但作为消费者,仍然可以通过
.WithCancellation
方法进行优雅取消。

这展示了生产者与消费者如何通过
WithCancellation

[EnumeratorCancellation]
实现职责分离,消费者能够独立地控制取消逻辑,而生产者只需响应取消请求。

CancellationToken 与 WithCancellation 同时作用时的行为

那么,如果在异步迭代器方法中同时传递了
CancellationToken
参数,并通过
WithCancellation
指定了不同的取消令牌,取消操作会听哪个的?还是都会监听?

结论是:两者都会生效
,只要其中任意一个取消令牌被触发,迭代器都会检测到取消请求并中断迭代过程。这取决于方法内部如何处理多个取消令牌。

示例演示

以下是一个详细的示例,展示当同时传递
CancellationToken
参数和使用不同的
WithCancellation
时的行为。

1. 定义异步迭代器方法

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public class EnumeratorCancellationDemo
{
    // 异步迭代器方法,接受两个 CancellationToken
    public async IAsyncEnumerable<int> GenerateNumbersAsync(
        [EnumeratorCancellation] CancellationToken cancellationToken,
        CancellationToken externalCancellationToken = default)
    {
        int i = 0;
        try
        {
            while (true)
            {
                // 检查两个取消令牌
                cancellationToken.ThrowIfCancellationRequested();
                externalCancellationToken.ThrowIfCancellationRequested();

                Console.WriteLine($"[Iterator] 生成数字: {i}");
                yield return i++;

                // 模拟异步操作
                await Task.Delay(1000, cancellationToken);
            }
        }
        finally
        {
            Console.WriteLine("[Iterator] 迭代器已退出。");
        }
	}
}

2. 定义消费者方法

public class Program
{
	static async Task Main(string[] args)
	{
		Console.WriteLine("启动枚举取消示例...\n");

		var demo = new EnumeratorCancellationDemo();

		// 测试1: 先取消方法参数
		Console.WriteLine("=== 测试1: 先取消方法参数 ===\n");
		await TestCancellation(demo, cancelParamFirst: true);

		// 测试2: 先取消 WithCancellation
		Console.WriteLine("\n=== 测试2: 先取消 WithCancellation ===\n");
		await TestCancellation(demo, cancelParamFirst: false);

		Console.WriteLine("\n演示结束。");
		Console.ReadLine();
	}

	static async Task TestCancellation(EnumeratorCancellationDemo demo, bool cancelParamFirst)
	{
		using CancellationTokenSource ctsParam = new CancellationTokenSource();
		using CancellationTokenSource ctsWith = new CancellationTokenSource();

		if (cancelParamFirst)
		{
			// 第一个取消任务:3秒后取消 ctsParam
			_ = Task.Run(async () =>
			{
				await Task.Delay(3000);
				ctsParam.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsParam (方法参数)");
			});

			// 第二个取消任务:5秒后取消 ctsWith
			_ = Task.Run(async () =>
			{
				await Task.Delay(5000);
				ctsWith.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsWith (WithCancellation)");
			});
		}
		else
		{
			// 第一个取消任务:3秒后取消 ctsWith
			_ = Task.Run(async () =>
			{
				await Task.Delay(3000);
				ctsWith.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsWith (WithCancellation)");
			});

			// 第二个取消任务:5秒后取消 ctsParam
			_ = Task.Run(async () =>
			{
				await Task.Delay(5000);
				ctsParam.Cancel();
				Console.WriteLine("[Trigger] 已取消 ctsParam (方法参数)");
			});
		}

		try
		{
			// 传递 ctsWith.Token 作为方法参数,并通过 WithCancellation 传递 ctsWith.Token
			await foreach (var number in demo.GenerateNumbersAsync(ctsWith.Token, ctsParam.Token).WithCancellation(ctsWith.Token))
			{
				Console.WriteLine($"[Consumer] 接收到数字: {number}");
			}
		}
		catch (OperationCanceledException ex)
		{
			string reason = ex.CancellationToken == ctsWith.Token ? "WithCancellation" : "方法参数";
			Console.WriteLine($"[Iterator] 迭代器检测到取消。原因: {reason}");
			Console.WriteLine("[Consumer] 枚举已被取消。");
		}
	}
}

3. 运行示例并观察结果

启动程序后,控制台输出可能如下所示:

启动枚举取消示例...

=== 测试1: 先取消方法参数 ===

[Iterator] 生成数字: 0
[Consumer] 接收到数字: 0
[Iterator] 生成数字: 1
[Consumer] 接收到数字: 1
[Iterator] 生成数字: 2
[Consumer] 接收到数字: 2
[Trigger] 已取消 ctsParam (方法参数)
[Iterator] 迭代器已退出。
[Iterator] 迭代器检测到取消。原因: 方法参数
[Consumer] 枚举已被取消。

=== 测试2: 先取消 WithCancellation ===

[Iterator] 生成数字: 0
[Consumer] 接收到数字: 0
[Iterator] 生成数字: 1
[Consumer] 接收到数字: 1
[Trigger] 已取消 ctsWith (WithCancellation)
[Iterator] 生成数字: 2
[Consumer] 接收到数字: 2
[Trigger] 已取消 ctsWith (WithCancellation)
[Iterator] 迭代器已退出。
[Iterator] 迭代器检测到取消。原因: WithCancellation
[Consumer] 枚举已被取消。

演示结束。

解释:

  1. 测试1:先取消方法参数 (
    ctsParam
    )


    • 在第3秒时,
      ctsParam
      被取消。
    • 迭代器检测到
      externalCancellationToken
      被取消,抛出
      OperationCanceledException
    • 终止迭代过程,即使
      ctsWith
      还未被取消。
  2. 测试2:先取消
    WithCancellation
    (
    ctsWith
    )


    • 在第3秒时,
      ctsWith
      被取消。
    • 迭代器检测到
      cancellationToken
      被取消,抛出
      OperationCanceledException
    • 终止迭代过程,即使
      ctsParam
      还未被取消。

关键点:

  • 独立生效
    :无论是通过方法参数传递的
    CancellationToken
    还是通过
    WithCancellation
    传递的
    CancellationToken
    ,只要其中一个被取消,迭代器就会响应取消请求并终止迭代。

  • 取消顺序无关紧要
    :不论先取消哪一个取消令牌,迭代器都会正确响应取消请求。取消操作的顺序不会影响最终的效果。

总结

通过上述示例,我们深入了解了
[EnumeratorCancellation]
的必要性及其在异步迭代器中的核心作用。简要回顾:

  • 消除警告
    :使用
    [EnumeratorCancellation]
    可以消除 Visual Studio 提示的警告,确保取消请求能够正确传递给异步迭代器方法。

  • 职责分离
    :它实现了生产者与消费者的职责分离,使生产者专注于数据生成,消费者控制取消逻辑,从而提升代码的可维护性和可复用性。

  • 灵活的取消机制
    :即使同时传递多个取消令牌,只要任意一个被取消,迭代器就会终止,提供了灵活而强大的取消控制能力。

.NET 的这些强大功能为开发者提供了极大的便利和灵活性,使得编写高效、可维护的异步代码变得更加轻松与自信。让我们为 .NET 的强大功能自豪,并在实际开发中善加利用这些工具,构建出更优秀的软件解决方案!

热点随笔:

·
开发人员,千万不要去碰那该死的业务参数,无论什么时候!
(
why技术
)
·
.NET 9正式发布,亮点是.NET Aspire和AI
(
张善友
)
·
AI之旅:起步即迈出重要一步,博客园与 Chat2DB 达成战略合作
(
博客园团队
)
·
瞧瞧别人的Controller,那叫一个优雅!
(
苏三说技术
)
·
在.NET 9中使用Scalar替代Swagger
(
马行空的博客
)
·
太强了!14.7K star!Windows系统居然还有开源替代版了?!
(
狂师
)
·
.NET现在可以做什么,有哪些公司在用的?
(
追逐时光者
)
·
程序员的悲哀是什么?
(
悟空非空也
)
·
使用 C# 入门深度学习:线性代数
(
痴者工良
)
·
基于.NET开源、功能强大且灵活的工作流引擎框架
(
追逐时光者
)
·
.NET 各版本贡献者列表
(
张善友
)
·
程序员如何借势AI提高自己:从高效工作到技能升级的全面指南
(
努力的小雨
)

热点新闻:

·
红旗不藏了:5nm车载芯片,纯视觉端到端智驾,固态电池也安排了
·
.NET 9 正式发布:性能大幅提升,提供 18 个月标准期限支持
·
李彦宏驳大模型泡沫论:文心日调用量15亿 智能体全面爆发
·
日产全球裁员9000人!拥抱华为自救,中国市场三年内推8款新能源车
·
比亚迪方程豹汽车豹 8 发布:首搭华为 HiCar,37.98 万元起
·
雷军:我马上去催一下 网友:“客服001号小雷为您服务”
·
NASA多次求借后 我国提议交换月球样本:美国至今沉默!
·
我国首个百万千瓦级海上光伏项目并网,项目用海面积约 1223 公顷
·
iPhone 部分旧款机型降至半价
·
雅迪上半年销量减少182万台,员工降薪,老板大幅加薪?
·
国产手机,买不起了
·
太荒谬了!千人公司一刀切禁用 JetBrains,非俄籍“备胎” VSCode 上位

前一篇:《探索训练人工智能模型的词汇大小与模型的维度》

序言:
Dropout 是神经网络设计领域的一种技术,通常我们把它翻译成 随机失活 或者 丢弃法。如果训练神经网络的时候不用 Dropout,模型就很容易“读死书”,也就是过拟合,结果可能导致项目失败。

那 Dropout 到底在干什么呢?其实很简单,就是在训练模型的时候,随机关掉隐藏层中的一些神经元,不让它们输出结果。没什么玄乎的,就是这么直接。比如说,在每一轮(epoch)训练中,会随机挑一些神经元“闭麦”,让它们暂时休息,输出值设为 0。但需要注意的是,哪些神经元会被关掉是随机的,每次都不一样,而不是每次关掉一批固定的神经元。这样操作的好处是,模型必须依赖所有神经元协同工作,去学习更普遍的规律,而不是只死记硬背几个特定特征。所以 Dropout 能很好地解决模型的“读死书”问题,让它更灵活、更聪明,也更有能力去识别它从未见过的新知识。

使用 Dropout

在减少过拟合方面,一个常用的技巧是在全连接神经网络中加入 Dropout。我们在第 3 章中探讨了它在卷积神经网络中的应用。这时可能会很想直接使用 Dropout 来看看它对过拟合的效果,但在这里我选择先不急着用,而是等到词汇表大小、嵌入维度和架构复杂度都调整好之后再试。毕竟,这些调整往往比使用 Dropout 对模型效果的影响更大,而我们已经从这些调整中看到了不错的结果。

现在,我们的架构已经简化到中间的全连接层只有 8 个神经元了,因此 Dropout 的作用可能会被最小化,但我们还是来试一试吧。以下是更新后的模型代码,加入了 0.25 的 Dropout(这相当于我们 8 个神经元中丢弃了 2 个):

model = tf.keras.Sequential([

tf.keras.layers.Embedding(vocab_size, embedding_dim),

tf.keras.layers.GlobalAveragePooling1D(),

tf.keras.layers.Dense(8, activation='relu'),

tf.keras.layers.Dropout(0.25),

tf.keras.layers.Dense(1, activation='sigmoid')

])

图 6-14 显示了训练 100 个周期后的准确率结果。这次我们看到训练集的准确率开始超过之前的阈值,而验证集的准确率则在慢慢下降。这表明我们又进入了过拟合的区域。

这一点通过图 6-15 的损失曲线得到了验证。


图 6-14:加入 Dropout 后的准确率


图 6-15:加入 Dropout 后的损失

从这里你可以看到,模型的验证损失又开始呈现出之前那种随着时间增加的趋势。虽然情况没有之前那么糟糕,但显然方向是不对的。

在这种情况下,由于神经元的数量非常少,加入 Dropout 可能并不是一个合适的选择。不过,Dropout 仍然是一个很好的工具,要记得把它放进你的工具箱,在比这个更复杂的架构中使用它。

总结:本节示例演示了在网络中引入 Dropout 的效果。从实验中我们可以看到,Dropout 是一个有效的工具,但它的作用依赖于模型架构和具体场景。对于像本例中这种简化的模型,Dropout 的影响较小。但在更复杂的模型中,它往往是防止过拟合的关键手段。接下来,我们还会介绍几种优化技术,帮助进一步解决模型过拟合的“读死书”问题

几年前MySQL数据库出现突然的从库延迟故障和CPU爆高时,如何排查具体原因,可能说已在腾讯云的MySQL库里开启了SQL全审计,记录了全部执行的SQL,再通过下面的方法就可以很容易找到原因:

1,
实用QPS和TPS高的高效分析方法

但如果没有开通SQL全审计,如何去找原因。当然说可以看慢SQL日志,但这时的慢SQL日志记录的SQL不全,就不好判断分析出具体是哪些SQL引起的。

如何在非开通SQL全审计的MySQL数据库(因实例不可能都开SQL审计,自建MySQL开还不方便存储查看等),
有什么办法?
有更多

信息来分析出原因!

当时接手公司数据库运维时,研发已经写了一个Shell,每10秒保存一下当前的:show full processlist ,保留当前执行SQL情况到文件中,后来发现这些还不够,后来加上长事务和表锁,可能更好,Shell脚本如下:

1,CPU爆高:

监控shell:

每10秒执行一次保留当前查询的监控到txt文本中

#!/bin/bash
cd $(
dirname $0)echo$log_file
sql
="show full processlist"ip=172.16.0.15start_time=$(date +%s)
let
"check_time=start_time+60";
now
=$start_timewhile [ $now -lt $check_time ];dolog_dir=/root/monitor/$(date +"%Y-%m-%d")/$(date +"%H")/$(date +"%M")mkdir -p $log_dir
log_file
=$log_dir/$(date +"%Y-%m-%d-%H-%M-%S")echo $sql | mysql -udba_op -ppwd -h$ip mysql >$log_file.master
mysql
-udba_op -ppwd -h$ip mysql </root/shell/long_tran.sql >$log_file.master_tranecho "next" sleep 10;
now
=$(date +%s)done

long_tran.sql下的SQL脚本:

select timestampdiff(SECOND,trx_started,now()) diffsecond,b.host,b.user,concat('kill',b.ID,';') killsql,b.db,
IFNULL(b.INFO,a.trx_query) runsql
from information_schema.innodb_trx a left joininformation_schema.PROCESSLIST bon a.trx_mysql_thread_id=b.IDwhere timestampdiff(SECOND,trx_started,now()) >=1 order by 1 desc limit 20;

show
open tables where in_use >=1;

Crontab运行调度和清理10天前的日志

*/1 * * * *  sh /root/shell/sg_monitor_master.sh > /dev/null 2>&1
1 1 * * *  find /root/monitor/ -name "20*" -type d -mtime +10 -exec rm -rf {}  \; > /dev/null 2>&1

这样每隔10秒就有下面2个文件:

可以结合慢SQL和这2个文件,监控出出问题时当前的数据库的SQL执行情况,找出问题。

2,从库突然延迟

因为当时公司是做在线教育的,但从库延迟几十秒,对他们影响很大,同时领导就会收到报警短信,钉钉群里叫,就要查是什么原因导致的从库延迟,一种方法是查看上面的监控处理的SQL获取信息,另外当时还有解析出binlog信息,查出具体的SQL,看是什么SQL导致的,具体做法:

1,从腾讯云的下载出问题时间的binlog日志本地的机器

2,根据出问题的时间范围,解析binlog日志,如下:

mysqlbinlog --no-defaults -vv --base64-output=decode-rows --start-datetime='2020-08-18 11:20:00' --stop-datetime='2020-08-18 12:03:00' mysql-bin.000203 >log.sql

3,解析出binlog日志后,用notepad++打开文件,仔细查看运行的SQL日志,分析出SQL,这就是细心活。如果查到具体表,可以用上
grep
过滤,如下:

1, grep user_0  -A 10 -B 10 >/data/61.txt
2, mysqlbinlog --start-datetime="2019-07-08 9:32:00" --stop-datetime="2019-07-09 11:20:00" -v --base64-output=DECODE-ROWS binlog.000001 | grep -A4 'INSERT' > test.sql

4,结合上面信息,发出SQL,让研发同学去修改业务。

注意binlog:
1,主库开启gtid后,pos位置点,每个binlog日志文件开始的pos都是从1开始计算
2,binlog的replace,会转成对应的idelete和insert,或者update等
3,binlog: 执行的SQL都是带有库名

--base64-output参数用来控制binlog部分是否显示出来的,指定为decode-rows表示不显示binglog部分
加-v与加-vv的区别
加-vv
### UPDATE `online`.`ol_teacher_inversion_rate_list` ### WHERE ### @1=595135615 /* INT meta=0 nullable=0 is_null=0 */
加-v
### UPDATE `online`.`ol_teacher_inversion_rate_list` ### WHERE ### @1=595135615
少一个v,少后面的字段类型信息
加--base64-output=DECODE-ROWS与不加的区别:
加:--base64-output=DECODE-ROWS
不显示原始binlog的二进制信息 :如 BINLOG ' JV7gWg8QJwAAdwAA.......'
4,binlog批量删除

如:DELETE FROM `online`.`ol_task_agents_fixed` WHERE `record_date` = '2021-04-21'
在binlog会一条一条记录,删除5000条,会有5000条delete语句,而且是同一个事务。

3,记录原始SQL

当时在公司,公司技术VP说要记录执行的原始SQL,这样大数据读取binlog,把这个原始SQL保留起来,日后审计时就可以通过原始SQL发现谁乱改线上业务数据,同时要把研发人员每次业务要改数据的SQL邮件抄送给他。查询发现mysql还有这样的参数:
binlog_rows_query_log_events参数

默认配置下,ROW格式二进制日志只记录数据发生的变化,并不会记录什么语句导致数据发生变化,而出于审计或者处理bug的需求,需要了解导致数据变化的SQL语句,MYSQL提供了binlog_rows_query_log_events来控制是否在二进制中存放"原始SQL"。开启后如下:

后来在主库上开启该参数,大数据就记录主库的SQL执行情况,要监控谁有乱修改更新线上业务数据,但后面一直没用这个数据去查,反而大数据说记录的执行SQL数据量太大,不得不缩短数据保留日期。

Sphere
类用于创建三维球体对象,它提供了丰富的参数和方法来定制球体的外观和行为。

球体在制作三维动画时,具有广泛的应用场景。

比如:

  • 展示几何概念
    :通过创建不同大小、颜色和透明度的球体,可以直观地展示几何中的体积、表面积等概念
  • 物理模拟
    :在模拟物理现象(如重力、碰撞等)时,可以使用Sphere类来代表球体物体
  • 天文模拟
    :在展示天文现象(如行星运动、星系结构等)时,Sphere类可以用于创建星球、恒星等天体模型

1. 主要参数

Sphere
类的主要参数包括:

参数名称 类型 说明
center Point3D 球体的中心位置
radius float 球体的半径
resolution int 球体的细分程度。这个参数通常用于控制球体表面网格的密度或光滑程度。
u_range [float] 定义了球体在u方向上的参数化范围
v_range [float] 定义了球体在v方向上的参数化范围

resolution
参数设置较大时,会生成更精细、更光滑的球体表面,但也会增加计算量和内存使用。

2. 使用示例

Sphere
类的使用比较简单,下面的示例重点展示其参数的不同使用方式。

2.1. 基本球体

这个示例创建了一个位于原点、半径为1的基本球体。

s = Sphere(radius=1)

2.2. 位置和颜色

在这个示例中,创建了一个球体,并对其位置和颜色进行了自定义。

球心放置在三维空间中的特定点(2, -1, 1),并且球体被填充为红色。

s = Sphere(
    radius=1.5,
    center=np.array([2, -1, 1]),
)
s.set_color(RED)

2.3. 分辨率和透明度

此示例展示了如何调整球体的分辨率和透明度。

通过增加分辨率,球体表面看起来更加光滑;通过设置透明度,让球体呈现出透明效果,使得其后的物体或背景部分可见。

# 高分辨率,透明度高
s1 = Sphere(
    radius=0.5,
    resolution=(32, 32),
    center=np.array([-1, -1, 1]),
    fill_opacity=0.2,
)
s1.set_color(BLUE)

# 中分辨率,中等透明度
s2 = Sphere(
    radius=0.5,
    resolution=(8, 8),
    fill_opacity=0.6,
)
s2.set_color(YELLOW)

# 低分辨率,不透明
s3 = Sphere(
    radius=0.5,
    resolution=(4, 4),
    center=np.array([1, 1, -1]),
    fill_opacity=1,
)
s3.set_color(RED)

2.4. 球体动画

在这个示例中,不仅创建了一个球体,还为其添加了一系列动画效果。

球体首先以淡入效果出现,然后移动到三维空间中的另一个位置,接着绕一个轴旋转,最后进行缩放。

s = Sphere(
    radius=1,
    center=np.array([1, 0, -1]),
)
self.play(FadeIn(s))
self.play(s.animate.move_to(np.array([-1.5, -1, 1])))
self.play(s.animate.rotate(PI / 2, axis=OUT))
self.play(s.animate.scale(1.5))

3. 附件

文中的代码只是关键部分的截取,完整的代码共享在网盘中(
sphere.py
),

下载地址:
完整代码
(访问密码: 6872)