Gradle基础学习(六) 认识任务Task

理解Gradle中的任务

Gradle的构建过程基于任务(Task)的概念,而每个任务都可以包含一个或多个动作(Action)。

任务是构建中执行的一些独立的工作单元,例如编译类、创建JAR、生成Javadoc或将存档发布到仓库。

我们使用gradle taskName或gradlew taskName来执行任务,比如build:

$ ./gradlew build 

项目中所有可用任务都是来自Gradle插件和构建脚本。

比如在子项目构建脚本中创建的任务:

可用任务清单

我们通过运行tasks任务来列出项目所有可用任务。

$ ./gradlew tasks

让我们以一个非常基本的Gradle项目为例,该项目具有以下结构:

 settings.gradle.kts文件定义了根项目名称和app子项目:

rootProject.name = "HelloGradle"

include("app")

目前app子项目的构建文件暂时是一个空文件。

为了看app子项目中的任务,运行./gradlew :app:tasks 

结果中只能看到少量的辅助任务(Help Tasks),它们是Gradle核心提供的用于分析构建的任务。其他任务,如构建项目或编译代码的任务,则是由插件来添加的。

我们接下来在app构建脚本里添加application插件:

//app/build.gradle.kts

plugins {

    id("application")

}

application插件会添加一些生命周期任务。 现在再运行./gradlew app:tasks,我们看到多了一些和构建相关的任务,比如assemble 和 build任务:

任务结果

当Gradle执行一个任务时,它会在控制台中显示任务的结果标签。

这些标签描述了一个任务是否有要执行的动作,Gradle是否执行了这些动作。动作包括但不限于编译代码、压缩文件和发布存档。

(no label) or EXECUTED

任务执行了动作。

任务有动作,Gradle执行了它们。

任务没有动作,有一些依赖项,Gradle执行了一个或多个依赖项。

UP-TO-DATE

Gradle会检查任务的输入和输出,如果输入没有变化,并且输出已经存在且是最新的,那么任务就不会被执行,并标记为UP-TO-DATE。

任务有输出和输入,但都没有改变。

任务有动作,但是告诉Gradle它不会改变输出。

任务没有动作,有一些依赖项,但所有依赖都是UP-TO-DATE, SKIPPED 或 FROM-CACHE的。

任务没有动作,也没有任何依赖项。

FROM-CACHE

Gradle检测到任务的输出已经在构建缓存中时,会直接从缓存中加载,比如build cache。 

SKIPPED

任务不执行他的动作

任务被跳过可能是因为某个先决条件未满足,或者任务被明确地配置为跳过,比如使用命令行选项--exclude-task排除。

NO_SOURCE

任务不需要执行他的动作

任务有输入和输出,但没有源文件。

FROM-CACHE和UP-TO-DATE都是Gradle优化构建过程的手段,都有助于减少不必要的任务执行,提高构建速度。可能会弄不明白什么时候是UP-TO-DATE或者FROM-CACHE,在本文后面介绍缓存任务的时候,我们再做进一步说明。 

任务组group和描述description

任务组和描述用于组织和描述任务。

Groups

任务组被用来对任务进行分类,当运行./gradlew tasks时,所有任务会被列在各自的组中,这样更容易理解它们的目的和与其他任务的关系,使用group属性设置组。

Descriptions
描述提供了任务功能的简要解释。当运行./gradlew tasks时,描述会显示在每个任务的旁边,帮助开发者了解它的用途以及如何使用它,使用description属性设置描述。

我们回头去看一下前面gradle-project执行tasks的输出结果:

:run任务属于Application分组, 对其描述是 "Runs this project as a JVM application"。 

这个任务在代码中的定义会像这样:

//app/build.gradle.kts

tasks.register("run") {

    group = "Application"

    description = "Runs this project as a JVM application."

}

私有和隐藏任务

Gradle不支持将任务标记为“私有”。

当我们运行:tasks时,默认只会显示那些分配了任务组的任务,即所谓的可见任务,而那些没有分配group的任务,就是隐藏任务, 需要注意,隐藏任务依旧可以被Gradle执行,只是不显示而已。

如下所示,我们创建了一个任务helloTask,执行./gradlew :app:tasks,任务列表里并没有找到helloTask任务。

//app/build.gradle.kts

tasks.register("helloTask") {

    println("Hello")

}

给它分配一个group 

tasks.register("helloTask") {

    group = "Other"

    description = "Hello task"

    println("Hello")

}

任务就出现在了指定的Other分组下 :

执行./gradlew tasks --all 可以显示所有任务,包括隐藏的。 

比如上面的helloHiddenTask,我们没有设置group属性,也显示在了Other分组下。 

分组任务

如果我们想要自定义执行tasks时向用户显示哪些任务,可以对任务分组并设置每个组的可见性。

示例gradle-project虽然只是一个简单的Java应用,列出的可用的任务却非常多,使用构建的开发人员很少直接需要其中的许多任务。

我们可以通过配置tasks任务,限制任务显示到一个特定的分组。

我们修改一下构建脚本,创建一个自己的分组,使用displayGroup属性来指定要显示的任务组。

//app/build.gradle.kts

val myBuildGroup = "my app build"               // Create a group name

tasks.register<TaskReportTask>("tasksAll") {    // Register the tasksAll task

    group = myBuildGroup

    description = "Show additional tasks."

    setShowDetail(true)

    println("register tasksAll")

}

tasks.named<TaskReportTask>("tasks") { 

    displayGroup = myBuildGroup

}

在Gradle中,我们执行tasks任务时,会使用此类型的一个实例TaskReportTask,其中displayGroup属性用来控制要显示的任务组,默认值是null, 可以使用命令行选项 '--group'设置,设置后就只显示这个分组的任务。

任务类别 

Gradle中的任务分为两类:

1. Actionable tasks(可操作任务)

2. Lifecycle tasks(生命周期任务)

可执行任务定义了Gradle应该执行的具体操作。例如,:compileJava 任务,它编译项目的Java代码。这些操作包括创建JAR文件、压缩文件、发布归档文件等。当你应用了一个插件,如java-library,Gradle会自动添加与该插件相关的可执行任务。

生命周期任务定义了一系列的目标(targets),你可以调用这些目标来执行一系列的操作。例如,:build 就是一个常见的生命周期任务,用于构建整个项目。这类任务本身不执行具体的操作(actions)。相反,它们捆绑了可执行任务,当调用生命周期任务时,会触发与之关联的可执行任务。 基础Gradle插件(base Gradle plugin)只添加生命周期任务。这意味着如果你没有添加任何插件,Gradle仍然会提供这些基本的生命周期任务。

我们再看一下之前例子的:tasks结果

如果我们执行:build任务,会看到有好几个任务都被执行了,包括:app:compileJava任务。

可以表述为可执行任务:compileJava捆绑到了生命周期任务:build中。 

增量任务

Gradle任务的一个关键特征是它们的增量性。

Gradle可以重用之前构建的结果。因此,如果我们之前构建过项目,并且只进行了小幅更改,那么重新运行:build将不需要Gradle执行大量工作。

例如,如果我们只修改项目中的测试代码,保持生产代码不变,执行构建将仅重新编译测试代码。Gradle将生产代码的任务标记为UP-TO-DATE,表明自上次成功构建以来,它保持不变:

缓存任务

Gradle可以使用构建缓存来重用过去构建的结果。

要启用此功能,请使用--build-cache命令行参数或在gradle.properties文件中设置org.gradle.caching=true来激活构建缓存。

此优化有可能显著加速项目的构建:

当Gradle可以从缓存中获取一个任务的输出时,它会给这个任务贴上FROM-CACHE的标签。如果经常在分支之间切换,构建缓存很方便。Gradle支持本地和远程构建缓存。

我们通过实际执行其中的compileJava源码编译任务来加深对任务结果为UP-TO-DATE和FROM-CACHE的理解:

我们把之前的gradle-project例子修改一下

//app/build.gradle.kts

//添加执行入口类

application {

    mainClass = "gradle.project.App"

}

//app/src/main/java/gradle/project/App.java

package gradle.project;

public class App {

    public static void main(String[] args) {

        System.out.println("Hello World!");

   }

}

现在app子项目是一个可执行的Java 应用程序,App类是应用的执行入口。

先./gradlew clean删除各子项目build目录,确保项目是干净的状态。

执行compileJava任务编译java,  --build-cache告诉Gradle本次使用构建缓存,需要--info选项显示一些额外的构建信息:

./gradlew --build-cache compileJava --info

Settings evaluated using settings file '/Users/roy/Downloads/gradle-project/settings.gradle'.

Using local directory build cache for the root build (location = /Users/xxx/.gradle/caches/build-cache-1, removeUnusedEntriesAfter = 7 days).

Projects loaded. Root project using build file '/Users/xxx/Downloads/gradle-project/build.gradle'.

> Task :app:compileJava

Stored cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

BUILD SUCCESSFUL in 1s

1 actionable task: 1 executed

 执行成功后,app子项目里多了build目录,输出的信息里也看到了build cache目录默认的位置Gradle User Home/caches/build-cache-1/,在其中缓存了任务的输出。

再次执行

./gradlew --build-cache compileJava --info

> Task :app:compileJava UP-TO-DATE

Build cache key for task ':app:compileJava' is 3804aa4dacefba7c96c077f8de82ae3d

Skipping task ':app:compileJava' as it is up-to-date.

BUILD SUCCESSFUL in 1s

1 actionable task: 1 up-to-date

因为我们没有做任何改动,build目录里已经最新的输出了,所以compileJava任务会跳过,此时任务被标记为UP-TO-DATE。

执行./gradlew clean删除app的build目录,然后再执行

./gradlew --build-cache compileJava --info 

> Task :app:compileJava FROM-CACHE

Build cache key for task ':app:compileJava' is db35214e1886f8b0ebbcc16e2fa7a618

Task ':app:compileJava' is not up-to-date because:

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main has been removed.

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main/gradle has been removed.

  Output property 'destinationDirectory' file /Users/xx/Downloads/gradle-project/app/build/classes/java/main/gradle/project has been removed.

  Output property 'options.generatedSourceOutputDirectory' file /Users/xx/Downloads/gradle-project/app/build/generated/sources/annotationProcessor/java/main has been removed.

Loaded cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

BUILD SUCCESSFUL in 1s

1 actionable task: 1 from cache

任务标记为FROM-CACHE,并且从缓存中加载了上次执行的输出,日志中也有提示:

Loaded cache entry for task ':app:compileJava' with cache key db35214e1886f8b0ebbcc16e2fa7a618

 另外,app的build目录也有了,里面的输出应该就是直接从build cache中拿过来的。

 再再一次执行./gradlew --build-cache compileJava --info, 任务又是UP-TO-DATE了。

> Task :app:compileJava UP-TO-DATE

所以,对于要执行的任务,只要项目里已经存在最新输出,它就是UP-TO-DATE;否则如果任务启用构建缓存,并且在缓存里有最新输出,就是FROM-CACHE。 

可缓存任务和不可缓存任务

在Gradle中并非所有任务都可以或应该被缓存,除了少数内置任务是可缓存外,大部分任务由于各种原因(如不可预测的输出、外部依赖、任务配置等)可能不适合缓存。这些任务通常被标记为non-cacheable。

使用--build-cache选项可以让Gradle启用构建缓存功能。当这个选项被启用时,Gradle会尝试缓存可缓存任务的输出,并在后续构建中重用这些输出。

对于non-cacheable任务,Gradle会忽略构建缓存机制,并总是执行这些任务。通常是因为这些任务的输出可能依赖于不可预测或不可重复的因素,或者任务本身的配置不允许缓存。

开发者也可以显式地配置这类任务使其可缓存。

要确定一个任务是否可缓存,你可以查看任务的输出。如果任务有一个buildCacheable属性,并且它被设置为true,那么该任务就是可缓存的。如果任务没有明确的buildCacheable属性设置,或者它被设置为false,那么该任务就是non-cacheable的。

构建缓存的有效性还取决于构建环境的稳定性和一致性。如果构建环境经常变化(例如,使用了不同的构建机器或文件系统),那么构建缓存的效果可能会受到限制。因此,在使用构建缓存时,确保构建环境的一致性是非常重要的。

开发任务

在开发Gradle任务时,我们有两个选择:

1.使用现有的Gradle任务类型,比如Zip,Copy或Delete

2.创建自己的任务类型,比如MyResolveTask或者CustomTaskUsingToolchains

任务类型就是是Gradle Task类的子类。

对于Gradle任务,有三种状态需要考虑:

1.注册任务 - 在构建逻辑中使用一个任务(由您实现或由Gradle提供)。

2.配置任务 - 定义任务的输入和输出。

3.实现任务 - 创建一个自定义任务类(即自定义类型)

注册通常使用register()方法完成。

配置任务通常使用named()方法完成。

实现任务通常是通过扩展Gradle的DefaultTask类来完成的:

①: 注册Copy类型的myCopy任务。

②: 根据Copy API为注册的myCopy任务配置它所需的输入和输出。

③: 实现一个名为MyCopyTask的自定义任务类型,它扩展了DefaultTask并定义了copyFiles任务操作。

1.注册任务

我们可以通过在构建脚本和插件中注册任务来定义Gradle要执行的操作。

使用字符串作为任务名来定义任务:

//build.gradle.kts

tasks.register("myCopy") {

   doFirst {

        // Task action = Execution code

        // Run before exiting actions

    }

    doLast {

        // Task action = Execution code

        // Run after existing actions

    }

}

register()方法会将myCopy任务添加到TaskCollection中。

2.配置任务

Gradle任务必须经过配置来完成他们的操作。比如一个任务需要ZIP一个文件,我们就必须要配置文件名和文件位置。这可以参考Gradle Zip任务提供的API,学习如何正确进行配置。

在上图示例中,我们注册了一个myCopy任务

tasks.register<Copy>("myCopy")

我们可以在注册的时候就立即用代码块配置任务:

tasks.register<Copy>("myCopy") {

   from("resources")

   into("target")

   include("**/*.txt", "**/*.xml", "**/*.properties")

}

因为这个任务是Copy类型, 是Gradle支持的任务类型,所以可以使用Copy API,如from、to。

之后在需要的地方,都可以通过named()方法查找对应名字的任务来配置:

//build.gradle.kts

tasks.named<Copy>("myCopy") {

    from("resources")

    into("target")

    include("**/*.txt", "**/*.xml", "**/*.properties")

}

 注意,在named()调用时,如果指定的任务还没有注册,就会构建失败。

3.实现任务

Gradle提供了很多任务类型,包括 Delete, Javadoc, Copy, Exec, Tar和Pmd等等,如果都满足不了我们的构建逻辑需求,我们可以实现一个自定义的任务类型。

要创建一个任务类型,需要扩展DefaultTask,然后定义为一个抽象类(不用是具体实现类):

//app/build.gradle.kts
abstract class MyCopyTask extends DefaultTask {}

Gradle 会通过解析 build.gradle 文件中的配置动态创建任务实例,这些实例是任务类的具体实现,它们包含了在配置中设置的所有参数和指定的动作。

task("taskName")与tasks.register("taskName")

在 Gradle 中,task("taskName") 和 tasks.register("taskName") 都被用来创建新的任务,但它们属于不同的 API 并具有一些细微的差别和用法上的考虑。

传统 DSL(领域特定语言)方式:

task("taskName") 是 Gradle 的传统 DSL(领域特定语言)方法,用于在 build.gradle 文件中声明一个任务。这种方式相对直观和简单,适用于简单的任务定义。

任务注册(Tasks API)方式:

tasks.register("taskName") 是 Gradle Tasks API 的一部分,用于以编程方式注册任务。它提供了更多的灵活性和控制,尤其是在需要基于其他任务或项目配置动态创建任务时。

主要区别:传统 DSL 方法在配置阶段就执行了任务的配置代码,而 Tasks API 则允许延迟配置,直到执行阶段才执行配置代码。这有助于避免在配置阶段发生不必要的副作用。

随着 Gradle 的不断进化,Tasks API 被认为是更现代和推荐的方式来创建和注册任务。

tasks.register("taskName") 实现延迟配置的原理主要基于 Gradle 的任务生命周期和任务注册机制。

在 Gradle 构建的生命周期中,任务(Task)的创建和配置是分开的。传统的 task("taskName") 语法在项目的配置阶段(configuration phase)就立即创建并配置了任务。这意味着,即使在任务从未被执行的情况下,其配置代码也会被执行。这有时可能会导致不必要的副作用,比如提前计算了某些值,或者执行了只在任务执行时才需要的逻辑。

相比之下,tasks.register("taskName") 使用了一种不同的方法。这个方法实际上并没有立即创建任务,而是注册了一个任务工厂(TaskFactory)。这个工厂会在任务首次执行时(execution phase)被调用,从而创建任务实例并执行其配置,这就是所谓的“延迟配置”(lazy configuration)。

具体来说,当你调用 tasks.register("taskName") 时,Gradle 会创建一个 TaskRegistration 对象,该对象封装了任务的配置逻辑(即你传递给 register 方法的闭包)。这个 TaskRegistration 对象会被添加到 Gradle 的任务容器中,但不会立即创建任务实例。

当 Gradle 执行阶段到来,并且需要执行名为 "taskName" 的任务时,Gradle 会从任务容器中检索相应的 TaskRegistration 对象,并调用其工厂方法来创建任务实例。此时,闭包中的配置逻辑才会被执行,从而配置新创建的任务实例。

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

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

相关文章

达梦数据库限制用户登录IP测试

达梦数据库创建用户时可以限制登录ip和时间段。 创建测试测试用户 create user test1 identified by Test_1234 ALLOW_IP "192.168.100.101"; 限定该用户只能通过192.168.100.101地址登录数据库 连接测试 上图可见&#xff0c;192.168.100.101客户端可以连接上19…

【数据结构】 二叉树的顺序结构——堆的实现

普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储 。 一、堆的概念及结构 父节点比孩子结点大 是大堆 父节点比孩子结点小 是小堆 堆的性质 堆中某…

java学习笔记反射机制

2.关于反射的理解 Reflection&#xff08;反射)是被视为动态语言的关键&#xff0c;反射机制允许程序在执行期借助于Reflection API取得任何 类的内部信息&#xff0c;并能直接操作任意对象的内部属性及方法。 框架 反射 注解 设计模式。 3.体会反射机制的“动态性” //…

什么是限流?常见的限流算法

目录 1. 什么是限流 2. 常见限流算法 3. 固定窗口算法 4. 滑动窗口算法 5. 漏桶算法 6. 令牌桶算法 7. 限流算法选择 1. 什么是限流 限流&#xff08;Rate Limiting&#xff09;是一种应用程序或系统资源管理的策略&#xff0c;用于控制对某个服务、接口或功能的访问速…

WordPress插件:链接自动识别转为超链接

WordPress插件&#xff1a;链接自动识别转为超链接 <?phpfunction open_links_in_new_tab() {add_filter(the_content, make_clickable);function autoblank($text) {$return str_replace(<a, <a target"_blank", $text);return $return;}add_filter(th…

【Python】selenium爬虫常见用法和配置,以及常见错误和解决方法

欢迎来到《小5讲堂》 这是《Python》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言无执行文件代码报错信息错误路径手动下载自动下载 选项配置Ch…

面试笔记——类加载器

基础 类加载器&#xff1a;用于装载字节码文件(.class文件)运行时数据区&#xff1a;用于分配存储空间执行引擎&#xff1a;执行字节码文件或本地方法垃圾回收器&#xff1a;用于对JVM中的垃圾内容进行回收 类加载器 &#xff1a;JVM只会运行二进制文件&#xff0c;类加载器的…

组件目录存放问题

目录 一、思考引入 二、组件分类 三、组件分类的目的 一、思考引入 .vue文件本质无区别&#xff0c;而路由相关的组件&#xff0c;为什么要放在views目录呢&#xff1f; 二、组件分类 .vue文件分2类&#xff1a;页面组件和复用组件。注意&#xff1a;都是.vue文件&#xff…

调试记录 CPU PCIE 找不到设备,AC 耦合电容的问题

1. 问题 现象&#xff1a; 1. 国产CPU的主板&#xff0c;主板内的PCIE 设备找的到&#xff0c;但是另一块板子上连接的PCIE 设备找不到。 2. 排查问题在哪里的计划 1. 检查原理图先排除信号定义的问题&#xff0c; TXRX是否反接。 2. 示波器检查PCIE 的时钟频率是否正确。 3. …

蒸汽工厂的新翼:数字孪生锅炉引领未来

在飞速发展的工业4.0时代&#xff0c;数字孪生技术已经深入到我们生产生活的方方面面。而对于那些承载着重工业血脉的蒸汽工厂来说&#xff0c;一项新的技术正在悄然改变它们的未来。 走进蒸汽工厂&#xff0c;感受传统与现代的交融 蒸汽工厂&#xff0c;这个充满力量与热情的…

翻译《The Old New Thing》 - Restating the obvious about the WM_COMMAND message

Restating the obvious about the WM_COMMAND message - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20060302-10/?p32093 Raymond Chen 2006年03月02日 关于 WM_COMMAND 消息的显而易见的知识点补充 简要 本文详细解释了 WM_COMMAND 消息…

一文读懂ipv4和ipv6的区别

IPv4和IPv6是互联网协议的两个主要版本&#xff0c;它们在多个方面存在显著的差异。以下是关于IPv4和IPv6之间区别的详细探讨&#xff1a; 一、地址空间 IPv4使用32位地址&#xff0c;理论上可以表示约42.9亿个不同的地址。然而&#xff0c;由于地址分配的不均衡以及网络技术的…

清华团队开发首个AI医院小镇模拟系统;阿里云发布通义千问 2.5:超越GPT-4能力;Mistral AI估值飙升至60亿美元

&#x1f989; AI新闻 &#x1f680; 清华团队开发首个AI医院小镇模拟系统 摘要&#xff1a;来自清华的研究团队最近开发出了一种创新的模拟系统&#xff0c;名为"Agent Hospital"&#xff0c;该系统能够完全模拟医患看病的全流程&#xff0c;其中包括分诊、挂号、…

element ui的确认提示框按钮样式修改

修改确认提示框的默认按钮样式&#xff0c;使用css强制修改 例&#xff1a; js代码&#xff1a; deleteUser(params){this.$confirm("您确定要删除吗&#xff1f;此操作无法撤销并且将永久删除所有数据。", "提示", { type: "warning", cancel…

批量自定义重命名,一键添加顺序编号,文件夹管理更高效!

我们经常需要对文件夹进行管理和整理。然而&#xff0c;当面对大量需要改名的文件夹时&#xff0c;手动逐个修改不仅效率低下&#xff0c;还容易出错。那么&#xff0c;有没有一种方法能够批量自定义重命名文件夹&#xff0c;并在名称后自动添加顺序编号呢&#xff1f;答案是肯…

JS执行原理大揭秘:事件循环Event Loop与宏任务、微任务

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 事件循环概述 异步和单线程 同步任务 异步任务 任务队列 宏任务 微任务…

智慧手术室手麻系统源码,C#手术麻醉临床信息系统源码,符合三级甲等医院评审要求

手麻系统全套源码&#xff0c;C#手术麻醉系统源码&#xff0c;支持二次开发&#xff0c;授权后可商用。 手术麻醉临床信息系统功能符合三级甲等医院评审要求&#xff0c;实现与医院现有信息系统如HIS、LIS、PACS、EMR等系统全面对接&#xff0c;全面覆盖从患者入院&#xff0c;…

精准读取CSV/Excel数据 - 灵活指定行列范围的 Python 解决方案

文章目录 源代码项目简介导入相关库__file_exists 装饰器函数的签名和注释主要功能的实现运行演示读取 Excel 文件 源代码 https://github.com/ma0513207162/PyPrecip。pyprecip\reading\read_api.py 路径下。 项目简介 PyPrecip 是一个专注于气候数据处理的 Python 库&#xf…

9.为什么有时候会“烫烫烫”——之函数栈桢

目录 1. 什么是函数栈帧 2. 理解函数栈帧能解决什么问题呢&#xff1f; 3. 函数栈帧的创建和销毁解析 3.1 什么是栈&#xff1f; 3.2 认识相关寄存器和汇编指令 3.3 解析函数栈帧的创建和销毁 小知识&#xff1a;烫烫烫~ Q&A 1. 什么是函数栈帧 我们在写C语言代码…

大模型面试常考知识点1

文章目录 1. 写出Multi-Head Attention2. Pre-Norm vs Post-Norm3. Layer NormRMS NormBatch Norm 4. SwiGLU从ReLU到SwishSwiGLU 5. AdamW6. 位置编码Transformer位置编码RoPEALibi 7. LoRA初始化 参考文献 1. 写出Multi-Head Attention import torch import torch.nn as nn …