Home
cover of episode #486: CSnakes: Embed Python code in .NET

#486: CSnakes: Embed Python code in .NET

2024/11/22
logo of podcast Talk Python To Me

Talk Python To Me

Chapters

Introduction to the concept of integrating Python libraries into .NET code using CSnakes, a project by Anthony Shaw and Aaron Powell.
  • CSnakes project enables .NET developers to use almost 600,000 Python packages.
  • Previous efforts allowed Python syntax but lacked full library integration.

Shownotes Transcript

如果您是医生、开发人员或在有这些人的地方工作,充分利用 pipi 及其近 60 万个软件包在您的 .NET 代码中岂不是很好?但是您该怎么做呢?之前的尝试允许您编写 Python 语法,但使用完整的库(尤其是基于 C 的库)却难以实现,直到 CSnakes 出现。Anthony Shaw 和 Aaron Powell 的这个项目实现了两种语言之间相当强大的集成。我们今天请他们两位来到节目中,向我们详细介绍一下。

Anthony Shaw 和 Aaron Powell 的这个项目实现了两种语言之间相当强大的集成,我们今天请他们两位来到节目中,向我们详细介绍一下。这是 Talk Python to Me,第 486 集,录制于 2024 年 11 月 4 日星期一。我是 Michael Kennedy,Python to Me 的主持人。

本片段由 Python 制作。欢迎收听 Talk Python to Me,每周播客节目,介绍 Python。我是主持人 Michael Kennedy。请在 Mastodon 上关注我:@mkennedy,并关注播客:@talkpython,这两个帐户都在 Mastodon.org 上,以便随时了解节目的最新动态,并收听超过九年的节目,网址为 talkpython.fm。如果您想参与我们的直播节目,可以在 YouTube 上找到直播流,请订阅我们的 YouTube 频道:talkpython.fm/youtube,并接收即将播出的节目的通知。

本集由 Posit 赞助。Posit Connect 来自 Shiny 的制造商,可共享和部署您创建的所有数据项目,包括您的影响力、流媒体仪表板、Shiny 应用、快速 API、闪存季度报告、仪表板和应用程序。Posit Connect 支持所有这些。通过访问 talkpython.fm/posit 来免费试用 Posit Connect。

本集由 Bluehost 提供。您需要一个网站吗?那就选择 Bluehost 吧!

他们的 AI 可在几分钟内构建您的 WordPress 网站,并且内置工具可优化您的增长。不要等待。访问 talkpython.fm/bluehost 开始吧,各位。在我们开始对话之前,我想谈谈基础设施。

实际上,Talk Python 这里发生了一些有趣的事情,我已在我们的新 Talk Python 博客上写了关于它们的文章,我将在节目说明中链接到这两篇文章。首先,我们将我们的网络托管迁移到美国的一个全新的托管提供商。

它由一家名为 Hetzner 的德国公司运营。这非常有趣。我写了所有这些内容,我认为那些正在考虑“我们应该在哪里运行我们的应用程序?”的人,如果他们对当前的托管位置不满意,值得考虑一下。

我刚刚重写了 talkpython.fm 的所有代码,大约一万行 Python 代码,迁移到一个异步 Web 框架。哪个框架呢?嗯,这是旅程的一部分。

因此,我写了一篇长篇博文,介绍了从 Flask 迁移到 Quart(异步 Flask)的过程,以及我考虑过的其他框架、迁移过程以及它的运行情况。因此,如果这两个主题中的任何一个让您感兴趣,请查看节目说明中文章的链接。一如既往,非常感谢您的收听。

让我们开始对话吧。您好,Anthony。您好,大家。欢迎来到 Talk Python。

还有 Aaron。

很高兴你们能来,Anthony,也很高兴再次邀请你来到节目中。你是我最受欢迎的嘉宾之一,看看这会让你在榜单上排到什么位置。但你确实名列前茅,我很高兴邀请你来到节目中。谢谢。Aaron,欢迎你,很高兴你来到这里。虽然我们已经一起做过很多次节目了,Anthony。

也许可以快速介绍一下自己。我致力于开源项目。我写过一本名为《深入 Python 内部》的书,如今大多数人都是通过这本书认识我的,而不是通过我本人。

是的,我们将讨论更多关于 Python 内部结构的内容,也许是将 Python 放入 .NET 中。我不知道。这有点像深入了解其他事物的内部结构。我们将弄清楚这将如何进行。这是一个非常棒的项目。Aaron,请介绍一下你自己。

好吧,我绝对是这里最年长的人。我一生中几乎从未编写过 Python 代码。我通过与 Anthony 合作完成这个项目,现在对它了解得更多一些,但我是一名 .NET 开发人员,从事这项工作的时间比……

我承认。

在录音音频中。十年,是的,我们至少要算十年。

我是一名优秀的 .NET 开发人员。

优秀的 .NET 开发人员。我还制作软件。我是一名团队成员,Jason Dane 是一位围绕 .NET 的倡导者,是的,等等。

我还编写 JavaScript,所以我尽我所能避免在我的时间里进入 Python 领域。而你来了,我也来了。是的。

太棒了。是的。所以这是一个不同的世界,对吧?来自 .NET,它保证了静态类型,有很多 Simon Collins,很多类型。然后是 Python,它喜欢它的空格。

但是,我又对空格有点小看法。所以我并不像……我有点理解 Python 语言的宽松空格方面,但与此同时,我确实想念我的花括号。

是的,诀窍是,如果您使用的是一个好的编辑器,您就不会真正注意到它。如果您没有,那就很糟糕了。所以要使用一个好的编辑器。我还想了解一下……

你世界上的最新动态。你已经参与这个项目几个月了……

现在是……几个月了。是的,我正努力为……我们今年有两个大型会议,其中一个将在几年后举行。

正在努力准备演示,以期在合作伙伴会议上展示。

这个会议规模很大,有几千人?

是的,没错。

我去过一个名为 Prepare Mica 的会议。我认为有 3 万人。播客展台。

是的,微软让我作为十个播客之一参加了这个活动。我想,这是什么?我几乎从未听说过这个。我听说过这个会议……

目前,但我已经在这个项目上工作了几个月,我们是在 5 月份的 Build 大会上启动这个项目的,这是一个面向开发人员的微软大会,我与许多不同的开发人员和微软的人员进行了交谈,他们正在考虑这个想法,如果我们再次关注它,那么像从 .NET 运行 Python 代码,你会如何处理?我有一些想法,把一些想法放在一起,然后开始与 Aaron 一起开发一个非常早期的原型,只是为了看看它是否可行。然后我们让一些东西工作起来,然后……基本上又重新开始,然后重新设计。然后,是的,我们一直在……

从那时起就一直在做这件事。但作为 .NET 生态系统中的一员,当我看到 Python 存在的库的广度时,有很多事情是我希望在最近的主要项目中进行一些 AI 工作时能够做到的。在 .NET 空间中,并没有那么多软件包或选项。

所以一直以来,我都觉得,好吧,如果我想做这件事,我建议使用 Python,我一直都觉得……我们应该弥合这一差距,这可以说是最初的想法之一,这是一个明确的问题。但我认为很多人都在学习它。但随后进入行业,Python 可能并不是组织正在使用的语言。所以,我们仍然有这个根本性的……

知识爆炸和可能性,顺便说一句,有近 60 万个其他库,包括最大的基于 AI 的资源社区,就你而言……

想象一下,你有 60 万个 Python 包。你把它加到……大约 40 万个 .NET 包上,那就是数百万种可能性。

是的,太棒了。只是为了让那些主要……主要面向 .NET 观众的人了解一下,NuGet 是 .NET 的 pip 等效项,好吗?你有了这个想法……有人说,嘿,你会怎么做?

然后你被……是的,我被指定为设计师,我的意思是,他们让我研究如何将 Python 集成到 .NET 中有一段时间了。如果你要处理这个问题,你会怎么做?我带他们浏览了所有过去尝试过此事的项目,或者有不同的方法可以做到这一点,并提出了每种方法的优缺点,以及非常低级别的集成和高级别的集成。所以,高级别的集成方法是在你的 Python 应用程序之上放置一个快速的 API,并通过 HTTP 调用它。

某种方式自动生成一些 REST 端点,然后让它们做它们的事情。

是的,你可以使用像 OpenAPI 这样的东西,在上面构建,自动生成客户端,这就像……这就像高级选项。然后,像最低级别的选项一样……你知道,你会进入底层代码级别,几年前我用 Pyjion 探索过,是的,这是一个非常黑暗的隧道。

它充满了可怕的错误和尖锐的边缘。

而且,挑战之一是,像模式实现这样的东西现在变化如此之快,就像……在 3.13 之前,现在又看 3.14。而且像所有这些变化一样,这在不断变化。所以它需要一个快乐的中间地带。就我们如何……融合这两者而言。

是的,必须有一些级别的解耦,对吧?这样 Python 就可以……进行更改,而不会……

你不断地追赶它,对吧?是的,因为我们将使用本机 C API 在 C 级别进行集成。有一种方法可以通过它来使用 Python,就像嵌入模式一样。没有多少项目这样做,但这绝对是可能的。所以你可以在另一个进程中启动一个 Python 解释器,在所有情况下……

那个进程……是的,我认为 Blender 这样做。我很确定电影和视频行业中有很多公司为了他们的自动化管道而这样做。我不认为这很好。

我和一些电影和视频领域的人交谈过,他们必须在 Python 上工作,因为当他们编写实现以集成该东西、集成 Python 时,它就完成了。就像你说的那样,Python 继续前进,很多工作……然后我们必须坚持使用 Python 2,这并不好。

是的,没错。所以我们研究了这个问题,并说,我们希望支持 Python 的新版本。我们希望支持……实际上是旧版本,我认为大约是 3.8,这是我愿意接受的旧版本。

我认为我们可能不会支持 3.9,因为我们在 .NET 端需要一些特定的功能。再说一次,有很多变化正在发生,也许你可以分享更多。但就像你知道的,.NET 也经历了很多变化。所以我们必须不断地……在平台上移动,我们希望在两者之间建立集成。所以让我们考虑一下,我们如何以一种……对人们有用的方式来做到这一点,我们可以……你知道,我们可以测试它,我们可以……

使它稳定。是的,也许你可以谈谈这一点,人们可能认为 .NET 是一个使用 Visual Studio 的 Windows 编程库,对吧?但它从那时起发生了很大的变化。有 .NET Core,有可以在 Linux 上运行的版本。有很多关于它的变化,对吧?

是的,我们有一个误解,那就是人们将 .NET 与 Windows 联系起来。从历史上看,这是事实。

就像……这就是我开始在 Windows 上进行开发的地方,而且……我仍然在 Windows 上进行开发,我的主要操作系统……多年来我一直使用 macOS 和 Linux,但 Windows 是我更舒服的地方。

但是,自从……基本上进行了重构以来,已经超过十年了,.NET 最初被称为 .NET Framework。所以现在我们有这两个分支。目前,您有 .NET Framework,这是我们传统上认为的……它与 Windows 等紧密相关。

所以它在很大程度上是围绕着更长的尾部支持生命周期等等而设计的,它非常稳定,但也获得了最少的投资,仅仅是因为它需要在一年又一年中保持稳定和一致,然后我们有 .NET Core,或者现在我们只是称之为 .NET。命名事物很难,说实话。但是,我们有 .NET,并且每年……都会发布一个版本,这就像对运行时进行重大修改一样。

所以 .NET 9 实际上在下周发布,至少在录制时是下周。这被认为是……一个较短的支持生命周期。您熟悉许多运行时现在拥有的支持生命周期。所以我知道人们现在对此有很多疑问,部分原因是……如果采用这种每年的滚动生命周期,并且您有长期支持和短期支持,那么长期支持是偶数版本。所以 .NET 8 是最新的长期支持版本…… .NET 6 是之前的版本,它实际上在下周结束支持,这才是人们开发的地方……你不能只针对 Python 3.13 和 .NET 9 作为唯一支持的版本,因为说实话,大多数……大多数软件包都在某个地方……大多数项目都会……所以我们必须做一些……

即使对我来说,你的一些网络应用程序之类的东西,你也会有一些冗余。几周前,我升级到 3.13,因为某些依赖项,依赖项中的一些内容已被删除,已被更改为删除,并且它不起作用。好吧,再过几周再试一次,直到它能工作并赶上进度。

这绝对是一个挑战。本节由 Posit(Shiny 的制造商,以前是 RStudio,尤其是 Shiny for Python)赞助。让我问你一个问题。

你在构建很棒的东西吗?当然,你是。你是。

每天都是开发人员或科学家。这就是我们所做的。你应该查看 Posit Connect。

Posit Connect 是一种让你发布、共享和部署所有使用 Python 构建的数据产品的方法。迈克尔,人们总是问我同样的问题。我有一些很酷的数据科学项目或笔记本电脑。

我该如何与我的用户共享?利益相关者、团队成员,我需要快速学习。API、Flask、也许是 React,这些都是很酷的技术,我相信你会从中受益,但也许应该专注于数据项目。让 Posit Connect 处理这方面的事情。

使用 Posit Connect,你可以快速安全地部署你在 Python 中构建的内容:流、Dash、Shiny、Plotly、FastAPI、Flask、Quarto、R Markdown 和 API。Posit Connect 支持所有这些,并附带所有必要的工具来满足 IT 和其他企业的要求。让部署成为工作流程中最简单的步骤,使用 Posit Connect。在有限的时间内,

你可以免费试用 Posit Connect 三个月,方法是访问 talkpython.fm/posit。网址是 talkpython.fm/POSIT。

链接在你的播放器节目说明中。感谢 Posit 团队对 Talk Python 的支持。当我们打开这个时,你提到过以前有过其他尝试,其他方法。

我想到的一种是 IronPython。IronPython 是 CLR 的 Python 实现,这与你正在使用的完全不同。所以也许可以将其与人们以前做的一些事情进行对比。

是的,我认为以前做过的大约三个项目都处于同一领域,但它们的目标不同。我的意思是,我们项目的主要目标是,有很多 delta-v 已经拥有应用程序。它们通常非常大,就像大型企业应用程序一样。

所以它们是非常大的应用程序。他们有团队在上面工作,并且他们想使用某种技术。这种技术通常是一个包或库或示例,尤其是在数据科学领域,现在很多主要的 Python 库都围绕着这个想法,因为开发人员的需求来自你。

我们想使用这些库,我们想在 Python 中使用这个特定的包,那么我们如何将它们导入到我们的 .NET 应用程序中呢?这就是我们从这个角度出发的方式,即我们如何做到两全其美?就像你有一个快速成熟的企业应用程序一样。

然后是你想使用的 Python 技术。IronPython 是用 C# 编写的 Python 实现,我认为这已经做了一段时间了。是的,就像旧版本的 .NET、旧版本的 Python 一样。

它也类似于 Jython。我认为我认为它与 Jython 属于同一类,它是 Java 的 Python 实现。那时,很多人都在用其他语言编写 Python 的不同实现。

所以是的,你可以编写 Python 语法并在 CLR 中运行它。但是你不能做像导入 numpy 这样的事情。

这就是区别所在。如今,生态系统中的大多数包都是针对 CPython 设计和测试的。至少它们是针对 CPython 测试的。但是所有其他实现都差不多,我知道像 pandas、NumPy、scikit-learn,所有这些科学工具。

特别是 C 扩展类型的东西,是的。

我认为我过去写过关于这个问题的文章,关于模式。但是,Python 的一个解决方案是实现 C 扩展模块,这是一个很好的解决方案,或者 Cython,它也是类似的东西,这是一个很好的解决方案。但这意味着生态系统更紧密地绑定到 CPython 作为解释器。所以我不想做的是,对吧,一个新的解释器,这又回到了兼容性问题和可维护性问题。到我们发布它的时候,它已经过时了,我们只是给自己带来了很多额外的工作。

这甚至不是关于你是否能做到这一点,你是否能跟上语言的发展,以及有数百万个库可以使用。但是如果你这样做,你将无法做到这一点。

是的,Pyjion 是我们过去在剧集中介绍过的另一个项目,它是 P-Y-J-I-O-N。

JION。

这是一个针对 CPython 的 JIT,它碰巧使用了 .NET Core JIT 引擎,因为那是当时最成熟的 JIT 之一。还有其他可用的 JIT。它可以用 LLVM JIT 编写。

我不知道谁实际上在 IronPython 上工作,所以他非常了解这个领域,他最初在 Pyjion 上工作,然后几年后又远程工作。这是一个不同的问题。这并不能帮助你将 .NET 和 Python 集成在一起。

它碰巧使用了 .NET 的组件来实现 JIT。该项目实际上已经停止了。它的一些好处已经被整合到 CPython 3.10 和 3.11 中。

让它更快的东西,大部分都被吸收了。第三个是 Python.NET,这可能是与我们启动的项目最接近的一个。这个已经存在一段时间了。

这个已经存在一段时间了,它是一件类似的事情,但它们主要关注的是另一种集成方式。那么,你如何从 Python 调用 .NET 库和东西呢?你可以调用 Python。

哦,对。Python.NET 是你做到这一点的方式。

这个项目已经存在一段时间了。所以 API 非常稳定。我们最初确实考虑过这个作为潜在的解决方案,但发现它在设计中有一些与我们想要做的事情相冲突的东西。

所以它对我们来说并不是一个选择。而且很多方面,API 与我们想要的方式不同。所以这就是……

替代方案。所以你最终自己编写了一个,因为这些都不匹配,对吧?IronPython 永远无法跟上,而且它没有包支持,反过来也是如此。

是的,所以我想,好吧,你为什么认为你能做到这一点?很多人,很多聪明人都在这上面投入了很多时间。你为什么认为这会……这会奏效?你对……有什么新的看法?

你在想什么?我真的很想利用现代 Python 的一些好处,并使用 .NET 来做到这一点。在我们实现这个的过程中,我们学到的一件事是,80% 的挑战都与类型相关,而 Aaron 正在微笑,因为他同意我的观点,像这样集成两个不同的生态系统,是关于调用函数、方法等等。

但是调用东西是关于用正确的参数调用它们以及传递数据。在不同类型之间移动信息并取回信息是强制性的。如果你集成了,我不知道,某个你在 Python 中的函数到 .NET,你将用类似的东西调用它,你将发送一些数据并期望返回一些数据。现在在 Python 中,有类型。它是动态类型的,但类型确实存在。

即使你没有把它放在你的……你的函数签名中,仍然有一些实例字符串最终会被安装在……

幕后。是的,完全正确,在 .NET 中,你可以从 .NET 中调用一个函数到 Python,但是你必须编写大量的代码。也就是说,我认为这个参数可能是这种类型,需要将其转换为这种类型。然后当它返回时,它可能是这个,先尝试这个。

所以我们说,现在类型提示在 Python 中越来越流行,我们想做的基本上是,你提供一些 Python 代码,当你编写一个函数时,这是你想调用的函数,你猜的函数,你说它接受,你知道,有这些参数,它们是这些类型,它返回这个类型。然后我们做的是编写一个代码生成器。一个源代码生成器,它会在你的项目中查看你想要嵌入的 Python 文件,查看定义了哪些函数,它们的签名是什么以及它们接受什么类型。

然后它会生成一个可以为你调用它的函数,并进行等效转换。然后它处理封送处理,就像在 .NET 和 Python 之间来回传递类型一样。该项目的大部分内容实际上都是关于这个封送处理的。

好的?所以它利用了 CPython API 来直接与运行时通信。

你可以用 Python 编写一个函数,你知道它接受一个字符串、一个列表和一个元组,并返回一个字典,这是一种非常常见的事情,是的,你会在 Python 中为它提供类型注释,然后你将该 Python 文件放入你的 .NET 项目中,然后你运行,再次构建,你会看到一个 .NET 中的同名函数,它就是你刚刚定义的函数,带有参数,它们是 .NET 中的等效参数。你只需调用该函数,它就会运行导入。

这种数据交换有多复杂?我可以使用我自己的类型并交换它们吗?或者它们必须是六七种核心类型?我认为数据交换实际上是棘手的部分之一,对吧?这是一个包含一百万个项目的列表。你不会把它们全部复制到 .NET 类型中,然后在运行时复制回 .NET 列表或类似的东西吧?

是的。如果你转到文档中的参考页面,你会看到一个我们目前支持的列表,然后是一个我们支持的核心列表,然后是其他任何内容。所以我们对泛型有支持,例如,如果你说这是一个字符串列表,我们会给你返回一个只读字符串列表。

在 .NET 中,如果它是一个字典,并且你指定了键和值类型,我们会给你返回一个 .NET 键值对。现在在 Python 中,它通常可以是任何东西,它可以是一个联合,或者它可以是某种复杂的东西。所以你可以选择说它可以是一个动态定义的类型,这仍然受支持,然后在运行时你可以查看类型以查看它是什么,然后将其转换为……

你需要的东西。我在 .NET 端看到了很多只读内容。这就是问题所在。

从 Python 接收数据时,我们在 .NET 端做了一个决定,即任何时候我们接收数据或发送数据时,它都应该表示为只读,以表明如果你正在调用从 Python 公开的 .NET 函数,并且它返回一个集合,例如字典、列表或元组,那么它们应该作为只读返回,以便清楚地向你作为使用者表明你并不拥有这些数据,就像你没有在你的应用程序中创建这些数据一样,你对它们不负责任。然后你可以将其转换为某种不可变的数据结构,如果你需要修改它,但是你是在做出明确的代码选择,将数据从原始来源转换为在你的新……

运行时空间中不可变的东西,好的。所以如果你有一个只读列表,你可以创建一个列表,然后你必须将其传递给构造函数。

某些东西,我会做一些事情,比如从现有数据集合中创建一个 .NET 列表。然后它变得更……更无聊,比如列表,比如只读列表,例如,它可以直接插入 LINQ,以便你可以使用 LINQ 中的查询或表达式方法来对 .NET 空间中的这些数据集合执行操作,一旦它从 Python 中重新水化。

所以我想你会得到很多这些只读 .NET 类型的东西。你可以使用 LINQ,是的,我希望我们有这个。我知道我们有一些类似的东西,但我们真的想确保……

我们正在处理这个问题,并且之前提到的一个重点是,你正在使用两个非常不同的类型系统,并且编写 Python 代码的方式与编写 .NET 代码的方式不同。所以我们不想让我们的代码生成器感觉像是我们只是创建了一个 .NET 外壳的 Python 代码。

我们希望感觉就像,因为我不开发,而且我正在对抗“不要”在一个方面,所以当你使用你公开给外部的类型和模块时,我们所做的就是为你生成一个幕后的类。然后我们为你生成一个界面,然后将其注册到依赖注入容器中。因此,你可以在应用程序的其他地方解析它。

所以现在,因为我不是开发人员,重要的是我正在将一个接口注入到我在代码库其他地方使用的类中。我不必去“好吧,不,我正在调用某种导入,或者我正在进行某种解析”。

另一个我认为我可能不熟悉的是,作为非开发人员,我正在做的只是,它感觉就像我运行的是,比如说,C# 商店,而我们优化了这个 C# 商店。我说,我们有一个软商店,它在某种程度上像一个软商店一样工作。如果我们不是开发人员,我们会优化它,以获得你在编写商店代码时所期望的那种开发体验。碰巧的是,你没有运行它,我们为你自动生成了部分 C# 代码,并将其注入到……

我们谈论的是交换数据,这是在同一个进程中的 CPython。对吗?是的。

CPython 是什么?通常情况下,如果你调用……CPython 是你用来运行 Python 代码的解释器。

它主要用 C 实现,标准库模块中的一些是用 C 编写的,许多 CPython 的许多部分都是用 C 编写的。你可以用 C 为 Python 编写模块,而你做的事情,比如创建数字和字典,都是用 C API 完成的。而部分 C API 是能够在一个进程内启动一个 Python 解释器。

这就是嵌入式 CPython。有不同的 API 可以做到这一点。有很多不同的方法来处理它。但实际上我们所做的是,当你启动 CPython 时,你将一个 Python 解释器注入到环境中。

它不像一个单独的进程,它不像是在后台运行的 Windows 进程,而是嵌入到进程中,就像加载库一样。库在进程内运行。然后我们完全控制 CPython 的一切。

所以我们可以导入模块。我们可以转换类型,来回转换。所以我们正在做很多工作,这些工作通常是在同一个扩展模块或解释器内部完成的。

所以如果我们想创建一个浮点数,例如从一个无符号整数中,我们会在 C# 层面做。所以我们可以用一个非常高性能的 API 来做。如果我们想查看字典的大小,查看类型等等,我们会在 C# 层面做,所以它非常高性能。

而且非常重要的是,虽然类型检查对其他主要挑战非常重要,但 Python 自由地使用线程和线程池以及协程等等,而 Python 可以进行线程处理,但它很棘手。所以你必须非常非常小心。

我认为这是我从其他在这个领域寻找解决方案的项目中没有看到的新的解决方案,关于他们如何处理它,或者它基于旧的 Python 风格。最近事情变化太大了。所以我们希望确保你可以在多线程环境中运行它,无论是 Avalonia UI 这样的桌面界面,还是一个 Web 应用程序。

所以我们其中一个演示是一个简单的 Web 应用程序 API,每个请求都可以运行一个 Python 函数。它们在 .NET 中的线程池中以单独的线程运行。你不必担心 GIL,你不必了解太多关于 GIL 是什么以及它是如何工作的。所以我们做了很多工作来确保这一点。这可以发生。

本节 Talk Python 播客由 Bluehost 提供。有想法,但不知道如何建立网站?使用 Bluehost 的 AI 设计工具,你可以快速生成高质量、快速加载的网站。一旦你满意外观,只需点击发布,你的网站就上线了,就这么简单。

这并不重要,无论你是业余爱好者、企业家还是刚刚开始你的副业,Bluehost 都能满足你的需求,它内置了营销和电子商务工具,可以帮助你长期发展和扩展你的网站。既然你收听我的节目,你可能知道 Python,但有时最好专注于你正在创建的内容,而不是自定义构建网站,并多花一个月的时间来启动你的想法。使用 Bluehost 云,你可以获得 100% 的正常运行时间和全天候支持,以确保你的网站在高流量下保持在线。

Bluehost 真正使构建你的梦想网站比以往任何时候都更容易。那么,还有什么阻止你呢?你已经有了愿景。

让它成为现实,立即访问 talkpython.fm/bluehost 并开始吧。感谢 Bluehost 对节目的支持。Python 有一个 C API,C# 可以共享、传递 C API。

它们相似,但并不相同。是的。我可以有一个在 Python 中的年龄函数,它被 C# 调用吗?

不,是的,只是一个测试。

很容易。我不运行完成之类的,然后继续。

是的,我很想这样做。但是 Python 端没有 API 来管理事件循环,也没有将事件循环嵌入到另一个进程中或编写自定义事件循环的 API。它被添加到标准库中的方式,被添加到另一个模块中。有一些提案在草案 PEP 中,但还没有被接受。如果它们被接受了,它们可能在 3.14 中,我们不能……那将是兼容的……是的。

我时间上还没到。你发现了吗?我认为我会的。事件循环和线程很难处理,对吧?比如,如果它不存在,你必须创建一个,但如果它存在,它不希望你创建另一个。但是没有一个很好的 API 来检查它是否存在。如果你没有直接访问它,你如何获得它,它总是让我感觉有点难以完全控制。

这在 Python 版本之间会发生变化,就像你说的那样。最近它变得更容易了,比如现在有一个 `asyncio.run()` 函数,我必须获取事件循环,创建一个。现在你可以直接运行当前的……仍然很棘手。

编写自定义事件循环,然后给它一些可运行的对象,比如轮询它们并从另一个运行时中调用它们的想法,我只是无法弄清楚 API。我很想这样做。如果有人知道,请告诉我。

嗯,UV 循环……是的,某种程度上,但也许他们只是交换了……

一些部分。

不是全部。是的。我知道你从 C# 设置事件循环,我在步骤中看到,安装 Python。

将你的项目放在 C# 文件中。使用类型提示你的 Python 函数,对吧?这是他谈到的非常重要的事情。

安装 CSnakes 运行时包。所以也许从这一点开始,让我们谈谈一些步骤。这个 CSnakes 运行时,这是一个新的 NuGet 包,你把它添加到你的项目中。我读对了吗?

是的。所以我们基本上有两个二进制文件,领导者。一个将为你完成所有代码生成。所以从 .NET 的角度来看,拥有解决方案中的多个不同项目并不罕见,其中一个项目可能只是一堆服务,它们将与数据库或其他任何东西通信。

所以你可能把它作为一个……这将是你放置想要使用 Python 文件的地方,为你生成 C# 包装器。所以你有一个包含它的项目,然后我们有第二个二进制文件,它实际上是在你的应用程序运行时使它工作。现在我们将其作为一个简单的合并包发布,仅供查看。但这就是我们区分编译器和运行时的原因,因为它们是不同的概念,一个是编译,另一个显然是运行。

运行时……好的。

使用该运行时包,文档中更多地讨论了这一点,只需几个步骤即可确保 C# 编译器能够找到 Python 文件。所以我们实际上在 C# 编译管道内部做了一些事情来生成代码,然后将其添加到其中。这也意味着我们可以尽早发现你是否会有兼容性问题。

所以如果某些东西的类型是我们不理解的类型,或者 Python 代码在语法上是模糊的,我学到的是,有一个浪费。你可以编写有效的 Python 代码,它非常模糊,并且应该有一些……一些你可以做的事情。但是请不要。是的,所以你实际上有一个编译步骤,而不是运行时持久化。我认为,再次从 .NET 的角度来看,我们使用静态类型检查,这个编译步骤,这个信念,编译器已经完成了很多检查,给我们提供了应该工作的代码,而不是像动态类型系统那样,在运行时出现错误,或者像我经历的那样,出现很多惊喜。

如果你正在做 C++,如果你编译,至少它以某种方式组合在一起,这是肯定的。Python 是什么情况?我们不是 Fisher,但我们很确定。

所以在 Python 类型提示中,你可以撒谎,我可以说一个类型提示,一个整数列表,而实际上它是一个字符串,它有数字字符。谁知道呢?对吧?如果我对 C# 代码撒谎会发生什么?它崩溃了。

不要这样做……是的,它只会崩溃。因为它必须假设你告诉它的内容是正确的。在 .NET 中,如果我有一个整数列表,我给它一个跨度,它就像我实际上想要……

一个字符串列表。

它会崩溃……是的。因为例如,如果它返回一个字符串列表,但你实际上返回一个整数列表,为了给你返回一个 C# 中的字符串列表,我们首先检查它是否是一个列表,如果不是,或者它不是一个序列类型,比如它可能是一个……如果你从列表继承了自定义类,或者你创建了一个序列,比如,如果不是这样,那么我们会引发异常。

它不会只是崩溃……我开玩笑的。我们做了很多测试。有一个完整的测试环境围绕着撒谎的类型提示和东西,它们完全无效。对于字符串,你知道,我们需要查看 Python 字符串,然后复制它。

大多数 C# 使用 UTF-8。我不知道,大多数 Python 使用 UTF-16。你知道,我们需要从一个转换为另一个。如果它……如果它首先不是一个字符串,那永远不会工作。所以我们会在那里引发一个异常并说……

你告诉我们……

这是一个字符串。

我们试图使我们引发的异常成为 .NET 异常,它们与我们覆盖的问题的概念相关。同时,它实际上会给出……我想象如果 Python 中的某些东西也失败了,比如你类型注释它是一个字符串列表……

而我们……我知道一个整数列表,我们给了你一个字符串列表。好吧,Python 解释器会在那里失败。所以我们也会将 Python 异常作为 .NET 异常的内部异常显示出来。所以你可以……你可以实际到达 Python 中失败的代码行。

我认为这实际上包括……它没有……

它会引发它。C# 作为 .NET 中的内部异常。所以我们在异常上做了很多工作,大部分是因为我们必须调试我们正在开发的东西,以及……

其他人……

会有的。你可以看到这个。

那个冰。你可以看到所有的帧变量等等。所以你可能想要从调试器中看到的东西,你可以在 .NET 端看到。所以如果你在调试器中运行它,它在 Python 中崩溃了,比如它引发了一个异常或其他什么。你会看到 Python 异常及其所有细节,然后你会看到内部异常等等,以及局部变量、全局变量和任何其他帧信息。

是的,当我看到它时,我印象非常深刻,它集成得多么好,我感觉从两个方向都很自然,而不仅仅是附加上去的。另一个步骤是代码,源代码生成。这感觉像 Visual Studio 20XX 年。

Visual Studio 的最后一个版本是……是 2022 年吗?好的。我之所以提到这一点,是因为很多人使用 Visual Studio,尤其是在 Python 领域,我认为在 VS Code 中也是如此。

通过有时在 VS 代码中打开,我看到您可能需要安装商店扩展程序才能获得更好的开发体验。但是我们必须这样做,因为源代码生成实际上是在商店编译管道内部一步一步运行的,我们需要知道如何找到这些文件,我们可以通过只进行目录扫描来告诉它。

所有以 PY 结尾的内容都会过去。

这效率不高。而且,如果您只是使用了您想要的其他模块的模块,那么您如何才能实际创建您想要公开的 Python 表面区域,而不是公开所有内容呢?所以我们在 C 内部。

S. Prod 文件中。所以这是关于如何编译更复杂应用程序的说明。然后我们可以添加注释到导出结构中,例如,这是一个 Python 文件,并且该文件已编译为 sea shop 以便拾取,以便当源生成器到达它时,它会说,好吧。

我将找到所有您所说的我需要知道的其他文件,然后我可以读取它们。它实际上对内部如何读取该文件进行了一些优化,而不是必须进行文件 I/O 或任何类似操作。所以它只是更物理地感知它们,并且它会使它们可用,以便它们可以生成咖喱,就像我说的那样,我不得不尝试猜测您想要哪些 pothon 文件。它们必须位于特殊的持有者中,或者必须以某种方式命名,或者我们只是目录扫描所有内容,我们就会得到您想要的内容。

是的,您需要非常小心地处理要执行此操作的对象,因为它们需要类型注释,并且您不希望只公开某些代码的全部内容。也许是在谈论表面区域,对吧?因为人们有这些受保护的私有内部、受保护的内部。

因此,Anthony 的最后一步是实例化 Python 环境。这感觉几乎就像一个 import 语句。然后你使用命名空间样式。稍后你想。

谈谈这个,比如将 Python 嵌入到 .NET 进程中。您需要创建一个 Python 环境。我们必须知道一些事情,例如 Python 安装在哪里?哪个版本?您有虚拟环境吗?它就像全局环境一样吗?您有额外的软件包吗?我们需要知道的一些额外的事情,您需要输入,您需要在其他地方设置这些。

例如,如果存在虚拟环境,您将激活它,如果您正在运行 Python,那么您将设置哪个版本以及它安装在哪里。所以在 .NET 中,您没有执行这些步骤。因此,我们需要您告诉我们,您想要创建哪个版本的 Python,它在哪里?我们创建了一个希望代码来帮助您找到它。ADV 团队一直在解决类似的问题,例如分割 Python 并运行 Python。所以我用来查看他们如何解决它,我们可能会尝试查看将 UVN 作为查找价格的一种机制,因为这是一个非常复杂的问题。

我想和你谈谈的是,你必须有一个假日,所以我必须获得依赖项。

我们不会为您存储时间。

是的,但是使用 UVVV 给它一个 Python 版本,它会配置并只在它的缓存中运行它,也许您甚至可以将其作为编译时资源,这里有一些有趣的可能性。

关于虚拟环境支持使用 conor 或 pip 作为包管理器,具体取决于您想要什么。您可以说这是我的 requirements 文件,它将使用 pip 安装它们,就像我可以为您做这些事情一样,因为我们有点像在做这个,是的,您在本地开发它,并且您在本地运行它,但是它将部署到其他地方。

因此,您不希望必须登录到生产服务并像在命令行中那样创建虚拟环境,就像 15 或 20 年前人们所做的那样。因此,您应该能够以编程方式说明此项目需要虚拟环境,它需要这些包和存储,并且需要此版本的 Python。然后,您可以使用与您的单元容器一起提供的 Python 版本,例如,或者您可以从某个地方提取一个。

您实际上可以从 new ga 下载它,就像您实际上可以将其作为依赖项添加到其中一样。对于 Windows 开发来说,这是一个非常简单的解决方案,对于 Mac waz 来说也是如此。如果您从 python.org 安装它,例如使用官方安装程序,您只需告诉它,您只需说使用 mo 的 Python 并提供版本号。

然后对于 Linux,情况要复杂得多,因为没有标准。所以是的,我们已经支持在 Linux 上查找它的各种方式。一旦您将 Python 环境放入进程中,就应该只有一个。

Python 本身不希望您在同一个操作系统进程中启动多个部分环境,如果您这样做,您将遇到各种问题,因此它基本上被放入单例中。然后,您可以通过创建它的实例来导入模块。然后,在该模块上,您可以调用该模块中的其他函数的方法。

例如,如果您有一个名为 demo DPPY 的文件,那么在您的环境中,您将看到一个工厂,有效地像导入 demo 并返回它的实例一样。在这个实例上,您可以使用方法,即示例中的其他函数,您在屏幕上看到的这个函数称为 hello world,它在名为 demo.py 的文件中具有参数 a。所以您只需说环境 demo,就像创建一个 demo 实例一样。

然后,在实例上,您可以调用 .hello_world 并传入参数,它会将结果返回给您。所以实际上,只需几行代码,四五行,您就可以在一个 AA 函数中启动环境。因此,我们确实尝试通过遵循最适合两个平台的模式来最大限度地减少代码。

所以,您可以使用这个很酷的东西。例如,如果您有一个单例,这对于构想来说很棒,但是使用服务解释器,您可以并行创建 AD。一件引人注目的事情,也许在这个方面有点出乎意料,就像您有 module = enva demo,对吧,您在这里哭泣。但在 Python 中,这个名为 demo 的东西,您是否会使用一个模式来命名变量。

与模块名称相同,这可能来自我们不是狗,但是是的,我们可能缺少的是,因为这是我一直在做的事情,只是标记代码,所以当我们从依赖注入的中国获得 ipad 环境时,然后像 demo 方法这样的东西被公开,它们将返回一个 II demo。我认为它是或不是 demo.opy 文件的表示,这很重要,是的,是的。然后,所有方法都导出到该模块上。如果我谈论的是从 Python 看待它,好吧,模块就是那个 Python 文件,当我这样说时,我会得到它,就像我想要该文件中实例的实例一样。

是的,他确实如此。老实说,作为 Python 开发人员,我对它的感觉并不重要,重要的是人们对它的感觉。当他们看到这是 C# 好东西时,他们的感受如何,那是他们的代码。他们的世界碰巧是 CSnakes 使与 Python 交互变得非常容易和可能。是的。

所以我想也许我们可以花几分钟时间谈谈这个,因为我看到一个例子,您展示了将 open telemetry 和 asp.net 以及 postgres 和各种东西集成在一起,而不仅仅是一个 hello world 类型的例子,这展示了一些非常酷的东西,但可能无法说服人们他们可以用它做很酷的事情。有了这个,你赢了。也许您可以带我们了解一些移动部件,以便人们了解更现实的示例。

是的。所以我们还有一个演示,这是一个网络应用程序医学项目。我们认为这是一个更现实的集成示例。所以您有一个网络应用程序,在 unh 中。好吧,前端只是 API 是用 .NET 编写的。您使用 postgres 作为数据库,并且您有一个想要添加到您的应用程序的功能,您想使用一些 Python 代码,一些路径代码。我们正在研究每个人是否都喜欢 a,其中 MO 只是。

足够真实,它只是真实的。

足够了,演示就像当您发现新的 API 项目时,这是一个天气应用程序。所以从 di,非常多。

在家,创建一个。

人们会期望的。现在,另一方面,我们也说,好吧,让我们现实地看待人们想要做什么,因为像现代应用程序一样,您将用 Python 编写 API,并且您知道您有 API 端点,您可以路由和配置它们,您可能会有一个关系数据库,如 postgresql 服务器或其他东西作为存储环境。

然后现在我们支持 open telemetry ary 平台四分之一,我可能可以在一秒钟内解释。所以 open telemetry 是一个新的标准,用于跟踪和检测以及应用程序的整体可观察性,无论使用哪种语言,我的意思是 .NET,您可以拥有。例如,对于此应用程序,有人向 Web 应用程序发出请求,您会看到请求进来以及所有计时,并且它不会记录任何内容或任何崩溃,或者它都与同一个关联。现在,如果我们要启动并运行一个 Python 函数,在 Python 中,您会执行 import logging,您知道 logging logging info 或 logging 或 era,那么它会去哪里?因为您在 Python 中查看的东西会消失。

进入空气,因为进入。

容器的终端输出不容易管理。我们从这个角度来看,人们用 .NET 编写大型企业应用程序。因此,他们会期望他们进行日志记录,并且所有这些都在同一个系统上。

因此,此演示基本上表示,在 .NET 中,API 端点之一运行 Python 中的一个函数,该函数启动它,使用 pandas 作为数据帧来查看天气,或者它计算一些平均值和移动平均值等等,以返回本周天气的预测,我们只是使用 sea 向数据发送数据,因为它很容易,因为它是我要输入的,然后它实际上不是,所以每个人都是 aam python。在 Python 代码中,我们使用 open telemetry ary 来显示它与数据库对话的跟踪,我们使用 Python 日志记录来记录信息。

所有跟踪信息都放入同一个跟踪中。因此,从 .NET 开始到结束,您会看到请求到来。在前端,您会看到 .NET 代码正在执行,您会看到 Python de,然后如果 pison 崩溃或日志记录条目正在进行,它们都在同一个环境中。

因此,在 open telemetry 仪表板中,您会看到整个跟踪跨越 .NET 和 Python,都在同一个环境中。您只需看到日志记录,就像您通常在 .NET asp 中一样,它带有一个免费的 open telemetry 仪表板。您可以像医生容器一样在本地运行它。非常方便,可以节省您启动 yager 或其他东西的麻烦。

是的,这真的很酷,因为您有一个 .NET API。但是当它被调用时,它使用这些代码生成器 CSnakes、Python 和集成。但是很多东西只是流过,例如 postgres 连接字符串流过 open kilometer 上下文,通过日志记录关闭对话,Python 追溯。

您的示例只是您的特殊连接和内容。但是 psycho PG 2。但是您可以使用几乎任何 RM 或任何东西,对吧?没有断裂。

强度在那里。是的,您使用 pandas 和 psychopath 的技巧是,任何将它部署到生产环境中的人,现在在容器中或在不同的环境中都很难,因为例如,使用哪个二进制文件?而且导入它很难,而且您知道您需要 lip q,而且有点像一个夜晚。

因此,它证明了这一点,就像这是一个您可以做到的真实的事情。它展示了一些在 Python 中很实用的东西,以及在 .NET 中很实用的东西,这是一个带有跟踪和日志记录的适当应用程序。所有这些,你知道,所有这些技巧,在 pi 内部,我们使用 pandas,你知道,我们使用 no pie,这是同一个扩展。

我们使用 psychopath,它依赖于一堆派对库,并将它们组合在一起。它看起来和感觉都像您看到演示工作一样,您永远不会知道天气预测来自 Python,甚至不知道它是以这种方式运行的。它看起来就像它是作为同一个 PM 的一部分实现的。

函数。其中一个库和它就走了,但它们都像这样连接在一起。我想我们快到时间了,但我认为我们接受了这个,这是一个 Web 示例。那么像 WPF 或 mari 或任何其他顶级应用程序呢?

可能吗?是的,因为现在因为现代 UI 烟花,你给了一个非常。

非常快速的定义给听众。你可能认为那是。

一部分,它是一种……算是Zamora的下一个演变,人们用这种方式进行跨平台……我的确不是Zamora方面的专家,但其理念是你可以拥有某种东西,它在……应用程序体验方面更具可移植性,你想以此为目标,比如Android或iOS上的移动应用程序,或者像那样在Windows上运行桌面应用程序。

所以,就像一个级别的可能性,在UI概念之间构建。但从根本上说,这只是在幕后运行Donut。因此,没有理由我们不能在那里加入一些CSnakes,并让它与某种后端服务一起工作,你可以使用它,而这是我谈论过的任何事情,你试图让它们成为像在你身边运行的本地模型一样。

你从Hugging Face或其他地方提取一个模型,它只是在本地运行以进行某种AI推理或任何你想要解决的场景。但这样你就无需调用扩展服务。现在,虽然像本地模型一样运行是可行的,但我们没有这样做。

它只是用Python更容易做到这一点。你会发现的所有示例,大多数情况下都是用Python编写的,所以尝试然后说,好吧,这在黑客马拉松中效果很好。

那么,等效的Seashop是什么?我该如何找到等效项?我不这样做,而我在这里阅读等等。

到目前为止,没有,就像CSnakes一样,你可以公开该模型,在纯Python API中创建一些表面区域。然后,这将由一个.NET应用程序控制。所以它有,所以你绝对可以做到这一点,直到实体,你可以在移动设备上做到这一点。

我认为这可能有点牵强,仅仅是因为我们必须将Python打包到a中,你可能会受到移动操作系统运行时的限制,才能进行那种引导加载。但至少像一个测试桌面应用程序,或者如果你想用像AvaloniaUI这样的跨平台UI框架来处理。Avalonia是一个跨平台的.NET UI框架。

这意味着你可以同时为Linux、Windows和macOS构建。然后,你可以在它的引擎盖下运行CSnakes,然后拥有这种用户体验。它利用Python来解决某些问题,Python更适合解决某些问题,而.NET更适合解决另一组问题,但就在这个应用程序内部,作为最终用户,你不会知道。这就是实际发生的事情。

非常引人注目,非常引人注目。我们的无限性。让我们以最后一件事结束。我认为这一点很重要,我们还没有真正谈到它。

你们开始谈话时说这对于数据科学来说非常重要,对吧?数据科学用例。我开玩笑说复制一百万个项目来向列表中添加一个项目。但是,跨.NET端和Python端共享数据(例如NumPy)怎么样?

所以,我们专门解决了这个问题,以便缓冲区协议是几年前的一个特性。它是一种API,用于像NumPy或Numba这样的东西的内部结构。重点是,缓冲区协议希望我们支持它。因此,如果你的函数返回一个缓冲区(这是一种类型),那么我们可以直接访问该缓冲区的内部内容,而无需转换数据。在现实中,这意味着如果你的函数返回一个NumPy数组,并且该数组中的内部值是特定类型,例如32位整数,那么你可以将它们作为32位整数访问,而无需复制数据。

这也在.NET内部直接进行。只需抓取……

它的实际内存,然后说,得到了它,让我们开始吧。然后,这是你正确的?数据也是。因此,你可以这样做,你实际上可以直接从.NET修改数组中的值,而无需来回复制。我们几乎支持NumPy支持的所有低级类型。

这意味着你不需要对数组进行.tolist()操作,对吧?因为这真的很昂贵,而且.NET也不支持NumPy使用的类似低级数值类型。它将与32位整数和32位或64位浮点数一起工作,例如。

所以,如果你在NumPy中以32位浮点数存储二维材料,然后你想将其发送到.NET,我不知道,比如挑选一些特殊的值或在.NET中进行维度计算,那么你可以将其作为缓冲区返回。文档中有关于如何执行此操作的信息。然后,你可以直接在内存中访问这些值。所以它非常快。如果你甚至想用.NET中的值填充缓冲区,那么你可以使用writeable buffer span来做到这一点。

太棒了。所以这确实也利用了数据科学的好处。

就像如果你跨不同编程语言的运行时共享内存,永远不会出错一样。

我看到其他人可能会……

走得很远,首先确保我们真正支持的范围……是的,没错。

好的。不,我认为这很棒。我认为这很棒。AvaloniaUI的UI内容是我正在关注的内容,它真的很有趣。

所以,最后的想法,你们,那些说“哦,我的天哪,我无法相信我们终于可以用我们的.NET应用程序做到这一点”的人,以及其他一些事情,他们是否准备好了,有人可以接手这个项目?这是一个好主意吗?

他们应该等待吗?.NET方面,任何可能的API更改。根据反馈,我们现在尽量保持稳定。所以我们正在进行兼容的更改。是的,我们只是想看到更多用例,并获得更多人的反馈。所以,请提取它,获取样本,使用它们,做一些事情,告诉我们你做了什么,并给我们任何反馈。

恭喜。这看起来真的,真的很好。绝对看起来很有希望,我认为它解决了其他尝试遇到的许多问题。

所以,这是另一集Talk Python To Me。感谢我们的赞助商。请务必查看他们的产品。这确实有助于支持节目。

本集由Posit赞助,来自Shiny的制造商,共享和部署你使用Python、R、Streamlit、Dash、Shiny、FastAPI、Flask、Quarto报告、仪表板和API创建的所有数据项目。Posit Connect支持所有这些。通过访问talkpython.fm/posit免费试用Posit Connect,本集由Bluehost提供。

你需要一个网站吗?最好使用Bluehost,他们的AI可以在几分钟内构建你的WordPress网站。并且有内置工具……

优化你的增长。不要等待,访问talkpython.fm/bluehost开始并提升你的Python技能。我们在Talk Python中有最大的Python视频课程目录之一。

我们的内容范围从真正的初学者到高级主题,如内存和异步。最棒的是,没有订阅费用,自己查看一下,网址是training.talkpython.fm。请务必订阅该节目,打开你最喜欢的播客应用程序,搜索“Python”。

我们应该就在顶部。你也可以找到iTunes订阅地址talkpython.fm/itunes,Google Play订阅地址talkpython.fm/play,以及直接RSS订阅地址talkpython.fm/rss。我们直播录制的大部分内容。

而且,如今,如果你想成为节目的一部分,并让你的评论在节目中播出,请务必订阅我们的YouTube频道talkpython.fm/youtube。我是你的主持人Michael Kennedy。非常感谢收听。我真的很感激。现在,出去写一些Python代码吧。