从零开始 Spring Boot 46:@Lookup

news2024/12/25 4:02:21

从零开始 Spring Boot 46:@Lookup

spring boot

图源:简书 (jianshu.com)

在前文中,我介绍了 Spring Bean 的作用域(Scope),且讨论了将一个短生命周期的 bean (比如request作用域的 bean)注入到长生命周期的 bean (比如singleton作用域的 bean)时所面临的问题,此类问题都需要我们对短生命周期的 bean 通过代理注入来解决。

实际上,即使都是长生命周期的bean,比如singleton作用域和prototype作用域的 bean,注入也存在一些问题。

注入问题

这里用一个示例说明将 prototype 作用域的 bean 注入 singleton 作用域的 bean 会出现什么问题:

@Value
public class Book {
    String name;
    String author;
    String isbn;
}

public class BookStore {
    @Autowired
    private Book book;

    public Book getBook(){
        return book;
    }
}

@Configuration
public class WebConfig {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public Book book() {
        return new Book("哈利波特与魔法石", "JK罗琳", "9787020033430");
    }

    @Bean
    public BookStore bookStore() {
        return new BookStore();
    }
}

在这个例子中,BookStore bean 的作用域是单例,Book的作用域是原型。这是我们故意为之,因为我们想通过getBook方法从书店中获取图书时每次都获取到一本新书。

但实际测试就会发现结果并不是我们预期的那样:

@SpringJUnitConfig(classes = {WebConfig.class})
public class BookStoreTests {
    @Test
    void testBookInject(@Autowired BookStore bookStore) {
        var book1 = bookStore.getBook();
        var book2 = bookStore.getBook();
        Assertions.assertSame(book1, book2);
    }
}

两次调用获取到的是同一个Book对象。

这是因为虽然Book bean 的作用域是原型,但将Book注入到BookStore这个单例 bean 中的行为仅会发生一次——在BookStore bean 被创建后。之后每次调用getBook获取Book对象都是直接获取BookStore中的book依赖,而不会再触发注入或者从ApplicationContext中获取 bean。

当然,解决的方式也很容易,只需要改为从ApplicationContext中获取 bean 即可:

public class BookStore2 {
    @Autowired
    private ApplicationContext applicationContext;

    public Book getBook() {
        return applicationContext.getBean(Book.class);
    }
}

现在每次获取到的都是新的Book对象:

@TestConfiguration
public class BookConfig {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public Book book() {
        return new Book("哈利波特与魔法石", "JK罗琳", "9787020033430");
    }
}

@SpringJUnitConfig
public class BookStore2Tests {
    @Configuration
    @Import(BookConfig.class)
    static class Config {
        @Bean
        public BookStore2 bookStore2() {
            return new BookStore2();
        }
    }

    @Test
    void testBookInject(@Autowired BookStore2 bookStore2) {
        var book1 = bookStore2.getBook();
        var book2 = bookStore2.getBook();
        Assertions.assertNotSame(book1, book2);
    }
}

就像我们之前提到的,虽然这样可以解决问题,但并不建议直接使用ApplicationContext,这样会导致我们的代码与 Spring 框架“强耦合”。

为了方便后续的测试用例编写,这里将Book bean 的相关配置拆分出来,并用@Import导入到当前测试用例中,更多的 Spring 测试相关内容,可以阅读我的这篇文章。

为此,Spring 提供了一个@Lookup注解来解决上述问题。

@Lookup

直接看示例:

@Component
public class BookStore3 {
    @Lookup
    public Book getBook() {
        return null;
    }
}

@Lookup标记的 bean 方法,在调用时会被代理,实际上 Spring 会通过ApplicationContext.getBean(Book.class)获取一个 bean 并返回。

  • 注意,这里的BookStore3使用@Component添加 bean 定义,原因在后面说明。
  • 因为用@Lookup标记的方法会被代理,所以这里的getBook方法的内容和返回值无关紧要,实际上充当一个占位桩(stub),因此大多数情况下用@Lookup标记的方法直接返回null即可。

所以,使用@Lookup可以解决诸如“将原型 bean 注入 单例 bean”的问题。

这点可以通过以下测试用例验证:

@SpringJUnitConfig(classes = LookupApplication.class)
public class BookStore3Tests {
    @Test
    void testBookStore3(@Autowired BookStore3 bookStore3) {
        var book1 = bookStore3.getBook();
        var book2 = bookStore3.getBook();
        Assertions.assertNotSame(book1, book2);
    }
}

通过@Lookup方法来获取 bean 的方式也被称作“方法注入”(method injection)。

限制

需要注意的是,使用@Lookup的 bean,必须使用@Component之类的注解直接添加 bean 定义,如果通过@bean方法的方式添加,@Lookup就不会起作用

这点可以通过以下错误示例验证:

public class BookStore4 {
    @Lookup
    public Book getBook() {
        return null;
    }
}

@SpringJUnitConfig
public class BookStore4Tests {
    @Configuration
    @Import(BookConfig.class)
    static class Config {
        @Bean
        public BookStore4 bookStore4() {
            return new BookStore4();
        }
    }

    @Test
    void testBookStore4(@Autowired BookStore4 bookStore4) {
        var book1 = bookStore4.getBook();
        var book2 = bookStore4.getBook();
        Assertions.assertSame(null, book1);
        Assertions.assertSame(null, book2);
    }
}

因为使用@Bean方法添加BookStore4,所以其中@Lookup标记的getBook方法并不会被代理,所以这里bookStore4.get()返回的是null

此外,@Lookup方法返回的类型必须是一个“具体类型”,不能是抽象类,比如:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
@EqualsAndHashCode
public abstract class Book3 {
    private final String name;
    private final String author;
    private final String isbn;

    public Book3(String name, String author, String isbn) {
        this.name = name;
        this.author = author;
        this.isbn = isbn;
    }
}

@Component
public abstract class BookStore7 {
    @Lookup
    public abstract Book3 getBook(String name, String author, String isbn);
}

这里的Book3是一个抽象类,而@Lookup代理并查找Book3类型的 bean 时会忽略抽象类的 bean,所以试图通过getBook方法获取 bean 时会产生一个NoSuchBeanDefinitionException异常:

@SpringJUnitConfig(classes = {LookupApplication.class})
public class BookStore7Tests {
    @Test
    void testBookStore7(@Autowired BookStore7 bookStore7) {
        String bookName = "哈利波特与魔法石";
        String bookAuthor = "JK罗琳";
        String isbn = "9787020033430";
        var book1 = bookStore7.getBook(bookName, bookAuthor, isbn);
        var book2 = bookStore7.getBook(bookName, bookAuthor, isbn);
        System.out.println(book1);
    }
}

abstract

@Lookup还可以用于抽象方法:

@Component
public abstract class BookStore5 {
    @Lookup
    public abstract Book getBook();
}

测试用例与之前的类似,这里不再展示,感兴趣的可以看完整示例。

构造器

利用@Lookup还可以通过相应 bean 的带参构造器来创建对象,比如:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
@Value
@EqualsAndHashCode
public class Book2 {
    String name;
    String author;
    String isbn;
}

@Component
public abstract class BookStore6 {
    @Lookup
    public abstract Book2 getBook(String name, String author, String isbn);
}

这里的Book2不再是通过@Bean方法添加定义,而是用@Component添加 bean 定义。

@Lookup标记的方法需要通过代理创建一个Book2类型的 bean,显然的,Book2对象只能通过包含3个参数的构造器(使用 Lombok 注解@Value生成)来创建。换言之,我们必须“告诉”@Lookup方法Book2构造器所需的参数。要实现这点也很容易,只要在@Lookup方法中添加相应的形参,并在实际调用中传入即可。

下面是实际的测试用例:

@SpringJUnitConfig(classes = {LookupApplication.class})
public class BookStore6Tests {
    @Test
    void testBookStore6(@Autowired BookStore6 bookStore6){
        String bookName = "哈利波特与魔法石";
        String bookAuthor = "JK罗琳";
        String isbn = "9787020033430";
        var book1 = bookStore6.getBook(bookName, bookAuthor, isbn);
        var book2 = bookStore6.getBook(bookName, bookAuthor, isbn);
        Assertions.assertNotSame(book1, book2);
        Assertions.assertEquals(book1, book2);
        Assertions.assertEquals(book1, new Book2(bookName, bookAuthor, isbn));
    }
}

可能这个例子多少有点“多余”,因为完全可以不用@Lookup,而直接在getBook方法中返回new Book2(...)。但是,这里没有直接new而是利用@Lookup让 Spring 创建 bean 并返回的好处在于——创建的Book2对象依然是 Spring Bean,所以在Book2中我们可以使用依赖注入,且使用生命周期回调等。

Provider

使用Provider同样可以解决这里的注入问题。

Provider属于jakarta.inject包,因此和使用@Inject一样,需要添加以下依赖:

<dependency>
    <groupId>jakarta.inject</groupId>
    <artifactId>jakarta.inject-api</artifactId>
    <version>2.0.1</version>
</dependency>

使用Provider完成之前的示例:

@Component
public class BookStore8 {
    @Autowired
    private Provider<Book> bookProvider;

    public Book getBook(){
        return bookProvider.get();
    }
}

这里我们不直接注入Book,而是注入Provider<Book>,并且在需要获取Book类型的 bean 时,通过bookProvider.get()获取。

测试用例:

@SpringJUnitConfig(classes = {LookupApplication.class})
public class BookStore8Tests {
    @Test
    void testBookStore8(@Autowired BookStore8 bookStore8) {
        var book1 = bookStore8.getBook();
        var book2 = bookStore8.getBook();
        Assertions.assertNotSame(book1, book2);
    }
}

所以,使用Provider可以起到@Lookup类似的作用。

@Lookup不同的是,Provider依然可以在@bean方法添加 bean 定义时使用:

public class BookStore9 {
    @Autowired
    private Provider<Book> bookProvider;

    public Book getBook(){
        return bookProvider.get();
    }
}

@SpringJUnitConfig
public class BookStore9Tests {
    @Configuration
    @Import(BookConfig.class)
    static class Config {
        @Bean
        public BookStore9 bookStore9() {
            return new BookStore9();
        }
    }

    @Test
    void testBookStore9(@Autowired BookStore9 bookStore9,@Autowired Book book) {
        var book1 = bookStore9.getBook();
        var book2 = bookStore9.getBook();
        Assertions.assertNotSame(book1, book2);
        Assertions.assertEquals(book1, book2);
        Assertions.assertEquals(book1, book);
    }
}

Provider获取 bean 时逻辑与 @Lookup类似,如果目标 bean 是原型,每次都会获取到一个新的 bean 实例,如果目标 bean 是单例,每次都会获取到同一个 bean 实例。

ObjectFactory

Spring 框架有一个ObjectFactory<T>接口,其ObjectFactory.getObject()每次调用会返回一个泛型类型的对象。

@Component
public class BookStore11 {
    @Autowired
    private ObjectFactory<Book> bookFactory;

    public Book getBook(){
        return bookFactory.getObject();
    }
}

上面这个示例中,如果Book bean 作用域是原型,那每次调用getBook会返回一个新对象,如果Book bean 是单例,那么返回的是同一个Book对象。

总的来说,ObjectFactory的用途与Provider@Lookup是类似的。

Lamda

还可以用 Lamda 表达式的方式来解决此类问题:

@Configuration
public class WebConfig {
	// ...
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public Book book() {
        return new Book("哈利波特与魔法石", "JK罗琳", "9787020033430");
    }

    @Bean
    public Supplier<Book> bookSupplier() {
        return this::book;
    }
}

这里定义了一个 Lamda 表达式的 bean,其实际上就是WebConfig.book()这个方法,而这个方法就是Book@Bean工厂方法。

在书店类中,我们可以直接注入这个 Lamda 表达式:

@Component
public class BookStore12 {
    @Autowired
    private Supplier<Book> bookSupplier;

    public Book getBook(){
        return bookSupplier.get();
    }
}

并且在getBook方法中通过 Lamda 表达式获取Book对象,其本质上是调用WebConfig.book()方法获取Book对象,而后者的调用又会被代理,所以实质上还是通过ApplicationContext.getBean获取 Book 对象。

最终的效果和ProviderObjectFactory等类似,如果Book bean 是单例,每次会获得同一个对象,如果是原型,每次会获得一个新的对象。

特别的,使用 Lamda 表达式会产生一个类似 @Lookup 那样的好处,即我们可以在获取 bean 时指定一些参数:

@FunctionalInterface
public interface GetBookFunction {
    Book get(String name, String author, String isbn);
}

@Component
public class BookStore13 {
    @Autowired
    private GetBookFunction getBookFunction;

    public Book getBook(String name, String author, String isbn) {
        return getBookFunction.get(name, author, isbn);
    }
}

@SpringJUnitConfig
public class BookStore13Tests {
    @Configuration
    @Import(BookStore13.class)
    static class Config {
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        @Bean
        public Book book(String name, String author, String isbn) {
            return new Book(name, author, isbn);
        }

        @Bean
        public GetBookFunction getBookFunction() {
            return this::book;
        }
    }

    @Test
    void testLamdaInject(@Autowired BookStore13 bookStore13){
        String bookName = "哈利波特与魔法石";
        String bookAuthor = "JK罗琳";
        String isbn = "9787020033430";
        var book1 = bookStore13.getBook(bookName, bookAuthor, isbn);
        var book2 = bookStore13.getBook(bookName, bookAuthor, isbn);
        Assertions.assertNotSame(book1, book2);
        Assertions.assertNotNull(book1);
        Assertions.assertNotNull(book2);
        Assertions.assertEquals(book1, book2);
        Assertions.assertEquals(book1, new Book(bookName, bookAuthor, isbn));
    }
}

如果用这种方式获取一个单例 bean,就需要格外小心,此时会产生一些奇怪的现象,比如:

@SpringJUnitConfig
public class BookStore13V2Tests {
    @Configuration
    @Import(BookStore13.class)
    static class Config {
        @Lazy
        @Bean
        public Book book(String name, String author, String isbn) {
            return new Book(name, author, isbn);
        }

        @Bean
        public GetBookFunction getBookFunction() {
            return this::book;
        }
    }

    @Test
    void testLamdaInject(@Autowired BookStore13 bookStore13) {
        String bookName = "哈利波特与魔法石";
        String bookAuthor = "JK罗琳";
        String isbn = "9787020033430";
        var book1 = bookStore13.getBook(bookName, bookAuthor, isbn);
        var book2 = bookStore13.getBook(bookName, bookAuthor, isbn);
        Assertions.assertSame(book1, book2);
        Assertions.assertNotNull(book1);
        Assertions.assertNotNull(book2);
        Assertions.assertEquals(book1, book2);
        Assertions.assertEquals(book1, new Book(bookName, bookAuthor, isbn));
        var book3 = bookStore13.getBook("鳄鱼", "莫言", "123");
        Assertions.assertSame(book1, book3);
        Assertions.assertEquals(book1, book3);
        Assertions.assertNotEquals(book3, new Book("鳄鱼", "莫言", "123"));
        Assertions.assertEquals(book3, new Book(bookName, bookAuthor, isbn));
    }
}

这里需要用@Lazy标记Config.book方法,否则 ApplicationContext 创建后会立即初始化所有的单例 bean,而Book bean 需要3个String参数,实际上并没有String bean 用于注入,就会导致程序运行出错。

此外,这里的Book bean 是单例,其余部分代码基本一致。

但观察测试用例就能发现,无论我们通过getBook方法调用时入参是否都相同,实际上获取到的都是最初创建的 bean。换言之,即使我们用了不同的参数获取 bean(book3),获取到的依然是第一次获取的 bean(book1)。

虽然这样看起来很奇怪,但至少保证了单例作用域的 bean 只会有一个实例。

作用域代理

这篇文章说作用域代理(Scoped Proxy)也会对此类问题有效,但我实际编写用例测试发现即使将Book作用域指定为prototype并添加代理,通过getBook获取到的Book对象依然是同一个对象,不会产生新的对象。

具体见完整示例中的测试用例BookStore10Tests

如果有网友对此类问题有研究,欢迎留言讨论。

The End,谢谢阅读。

本文的完整示例可以从这里获取。

参考资料

  • spring注解@Lookup使用原理和注意点以及其他替换实现方案
  • @Lookup Annotation in Spring | Baeldung
  • Injecting Prototype Beans into a Singleton Instance in Spring | Baeldung
  • 从零开始 Spring Boot 43:DI 注解 - 红茶的个人站点 (icexmoon.cn)
  • 从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)

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

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

相关文章

事务小总结

事务定义 是一个数据库操作序列&#xff0c;这些操作要么全部执行,要么全部不执行&#xff0c;是一个不可分割的工作&#xff08;程序执行&#xff09;单元。事务由事务开始与事务结束之间执行的全部数据库操作组成。 事务特性 原子性(Atomicity)一致性(Consistency)隔离性(…

Linux下 文件删除但是空间未被释放 或者 磁盘已满但找不到对应的大文件 的解决方案

Linux下文件删除但是空间未被释放的解决方案 前言1. 查看当前磁盘占用情况2. 模拟进程占用3. 执行rm -rf 命令删除文件4. 查看被删除但是未释放空间的文件5. 执行清空文件操作 前言 linux磁盘空间已满&#xff0c;手动rm -rf 删除了大文件之后&#xff0c;df -h 查看一下发现空…

操作系统第四章练习题

第一部分 教材习题&#xff08;P152&#xff09; 1、为什么要配置层次式存储器&#xff1f; 设置多个存储器能够使存储器两头的硬件能并行工作;采用多级存储系统,专门是Cache 技术,是减轻存储器带宽对系统性能影响的最佳结构方案;在微处置机内部设置各类缓冲存储器,减轻对存储…

【Android -- 面试】Android 面试题集锦(Java 基础)

Java 基础 1、Java 的类加载过程 jvm 将 .class 类文件信息加载到内存并解析成对应的 class 对象的过程&#xff0c; 注意&#xff1a;jvm 并不是一开始就把所有的类加载进内存中&#xff0c;只是在第一次遇到某个需要运行的类才会加载&#xff0c;并且只加载一次 主要分为三…

【ARMv8 SIMD和浮点指令编程】NEON 比较指令——比较的方方面面

比较指令是常见的一类指令,NEON 中当然也排除,下面涉及比较和测试位两类指令。 1 CMEQ (register) 按位比较相等(向量)。该指令将第一个源 SIMD&FP 寄存器中的每个向量元素与第二个源 SIMD&FP 寄存器中的相应向量元素进行比较,如果比较相等,则将目标 SIMD&…

基于Python+Django+mysql+html通讯录管理系统

基于PythonDjangomysqlhtml通讯录管理系统 一、系统介绍二、功能展示1.用户登陆2.用户注册3.密码修改4.查询5.添加6.修改7.删除 三、其它系统四、获取源码 一、系统介绍 该系统实现了 用户登陆、用户注册、密码修改、查询信息、添加信息&#xff0c;修改信息、删除信息 运行环…

vue2脚手架可视化小项目

vue2脚手架小项目 这个项目又数据可视化可以通过点击按钮修改数据&#xff0c;同时图形的内容也会随着变化这里面也又保存功能&#xff0c;搜索功能&#xff0c;添加功能用到的框架有vue2脚手架&#xff0c;语言有bootstrap&#xff0c;css&#xff0c;也用了echarts 下面会先…

从Web2.0走向Web3.0还有多远?

Web2.0时代给互联网带来了巨大的变革&#xff0c;让用户成为内容的创造者和共享者。然而&#xff0c;随着技术的不断发展和创新&#xff0c;我们正在逐渐迈向Web3.0时代&#xff0c;这将是一个更加去中心化、透明和安全的数字世界。那么&#xff0c;从Web2.0走向Web3.0还有多远…

202322读书笔记|《给你一个大抱抱:治愈系暖心绘本》——如果事与愿违,请相信一切自由安排

&#xff3b;TOC&#xff3d;(202322读书笔记&#xff5c;《给你一个大抱抱&#xff1a;治愈系暖心绘本》——如果事与愿违&#xff0c;请相信一切自由安排 作者白开水DAWAN&#xff0c;治愈系小说&#xff0c;画面明媚有趣轻松&#x1f92a;&#x1f92a;&#x1f618;&#…

learn C++ NO.10——string(3)

引言&#xff1a; 现在是北京时间2023年6月22日的早上8点。又是一年端午&#xff0c;时光如梭。这一年来发生的变化太多了&#xff0c;遥想去年此时&#xff0c;我还沉浸在被大学录取的喜悦中&#xff0c;转眼间大一就过去了。这里我也衷心的祝愿您和您的家人端午安康&#xf…

Triton教程 --- 自定义操作

Triton教程 — 自定义操作 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理速率限制器模型管理 自定义操作 Triton 推理服务器部分支持允许自定义操作的建模框架。 自定义操作可以在构建时或启动时添加到 Triton&…

前端3D Three.js 在本地搭建一个官方网站

上文Web前端 3D开发入门规划 3D效果将不再是桌面应用的专利我们说了说 WEB 3D的一个发展和学习方向 那么 我们还是先搞定文档使用的问题 我们可以访问http://www.yanhuangxueyuan.com/Three.js/这里面可以查到使用方法 但不是最新的 而是别人用官方文档改的 真正的官方文档地址…

30 linux 新建进程的进程号是如何分配的?

前言 呵呵 进程号是我们经常使用到的, 但是 却从来 没有深究过的东西 这里 就来看一下 具体的进程号的生成方式 linux 新建进程是以 fork exec 的形式创建进程的 子进程 是 复制自 父进程 pid 是怎么生成的? 分配 pid 的地方调用堆栈如下, fork 之后, 会为 新进程 申…

全志V3S嵌入式驱动开发(基于usb otg的spi-nand镜像烧入)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过spi nand驱动&#xff0c;但是这个驱动是建立在linux系统从sd卡加载的情况下进行的。开发测试的情况下&#xff0c;这么做或许是可以的…

PWM详解(嵌入式学习)

这里写目录标题 前言定义参数工作原理应用练习 前言 在STM32微控制器中&#xff0c;PWM代表脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;。PWM是一种用于控制电子设备的技术&#xff0c;通过调整信号的脉冲宽度和周期&#xff0c;可以模拟出不同的电压或功率级…

【python】—— 基础语法(一)

序言&#xff1a; 在之前&#xff0c;我们已经对【python】的开发环境进行了相应的安装&#xff0c;紧接着本期我就讲给大家介绍它的相关语法。对于学过【C/C】或者其他语言的小伙伴来说&#xff0c;语法内容学习起来就会掌握的很快了。 目录 前言 &#xff08;一&#xff0…

【架构师】零基础到精通——网关策略

博客昵称&#xff1a;架构师Cool 最喜欢的座右铭&#xff1a;一以贯之的努力&#xff0c;不得懈怠的人生。 作者简介&#xff1a;一名退役Coder&#xff0c;软件设计师/鸿蒙高级工程师认证&#xff0c;在备战高级架构师/系统分析师&#xff0c;欢迎关注小弟&#xff01; 博主小…

vue服务端渲染SSR

一&#xff1a;ssr的理解 1、服务端渲染 Server Side Render SSR解决方案&#xff0c;后端渲染出完整的首屏的dom结构返回&#xff0c;前端拿到的内容包括首屏及完整spa结构&#xff0c;应用激活后依然按照spa方式运行&#xff0c;这种页面渲染方式被称为服务端渲染 (server si…

ISO C++ 26 并发和并行性重大完善

ISO C 委员会举行会议正式通过了 C 26 的时间表。ISO C 标准委员会主席兼微软语言架构师 Herb Sutte&#xff0c;并发和并行小组仍在按计划推进 C26 的 std::execution 和 SIMD 并行 一、 C23 与C 26 开发时间表&#xff1a; 二、C26标准属性三大规则。非正式总结是: [已经在c …

【Python 基础篇】Python数据序列推导式

文章目录 前言一、列表推导式二、字典推导式三、集合推导式总结 前言 在Python中&#xff0c;推导式&#xff08;Comprehension&#xff09;是一种简洁而强大的语法&#xff0c;用于快速创建列表、字典和集合。推导式使得在一个简单的语句中就能生成一个新的数据结构&#xff…