Julia 学习笔记——1. 概述
这套笔记主要记录从 Python 和 MATLAB 迁移到 Julia 时需要关注的内容。
需要说明的是,这个系列不是零基础入门教程,而是围绕具体主题展开的整理型笔记,因此不同篇之间会有交叉,不严格追求循序渐进。
除非特别说明,下文默认以 Julia 最新版本 1.12 为准。
概述
Julia 对我而言更像是一种值得单独学习的科学计算语言,而不是 Python 或 MATLAB 的某个附属工具。
它最吸引人的地方很明确:
- 写法上尽量保留动态语言和科学计算语言的易用性;
- 执行上尽量逼近静态编译语言的性能;
- 语言本身直接把数组、线性代数、泛型函数和多重分派当作核心设计对象。
介绍
Julia 是一门面向科学计算的新兴语言,它的设计目标非常明确:希望同时拥有 Python/MATLAB 的易用性,以及 C/C++/Fortran 一类语言的运算效率。
Julia有很多与众不同的特点:
- 从设计定位来说,Julia 具有后发优势,是为 JIT 编译而量身定制的语言,目的是解决现在泾渭分明的动态语言和静态语言之间,优劣不能兼顾,因而必须组合使用的双语言问题。
- 从计算效率的角度,虽然和Python在使用时很相似,但是Julia的计算效率却比Python高很多。Python的科学计算模块Numpy等必须依赖于C/C++编写的底层库,但是Julia自身就可以提供足够的科学计算能力(主要是通过预编译实现的)。
- 从语法设计的角度,Python是按照通用语言设计的,并没有关注于科学计算编程中的语法便利性,Julia和MATLAB、Fortran、R语言的定位类似,充分为科学计算的效率和便利性考虑,提供了足够多的语法糖。这些语法糖其实有利有弊,一个典型的特征是索引从0还是从1开始,选择从1开始的语言具有明显的科学计算定位。
- 作为一种新兴语言,采用了很多较新颖的语法设计,比如抛弃了传统的面向对象编程,仅支持C风格的结构体等。
但是Julia还是有很多争议的,比如:
- Julia的发展时间太短了,主要的语法特性并不稳定;
- 各种基础库不如其它语言全面和健壮,甚至有的基础库还存在低级问题;
- 用户群体相比其他语言太小,相应的教程和社区讨论相对缺乏;
- 第三方库不够丰富,由于跨语言调用比较方便,出现了大量依赖 Python 包的趋势。
Julia最理想的应用场景如下:
- 使用与 Python/MATLAB 相当的简单方式编写 demo 代码;
- 对现有代码进行简单优化,就可以达到与 C/C++/Fortran 相当的性能。
在整个过程中,不需要从动态语言 Python/MATLAB 迁移到静态语言 C/C++/Fortran 的代码重写或者跨语言组合方案,完全可以通过 Julia 自身来实现。
综合考虑,还是值得系统了解一下。
相关网站:
Julia 常识
运行方式
最基本的几种使用方式如下:
- 直接启动 Julia REPL,进入交互模式;
- 执行
.jl脚本文件; - 使用命令行选项直接执行表达式或项目代码。
最常见的命令例如:
1 | julia |
其中:
- 直接输入
julia会进入 REPL; julia hello.jl会执行脚本;julia -e适合快速测试一小段代码。
如果希望脚本执行完成后仍然留在交互环境中,可以使用:
1 | julia -i hello.jl |
这点和 Python 的 python -i script.py 很类似。
脚本与项目
如果脚本依赖当前目录下的 Julia 项目环境,通常会写成:
1 | julia --project=. hello.jl |
这表示使用当前目录下的 Project.toml / Manifest.toml 作为环境。
如果只是随手写的小脚本,没有项目环境要求,那么直接执行 julia hello.jl 就够了。
查看版本
查看 Julia 版本最常见的方式:
1 | julia --version |
输出形如:
1 | julia version 1.12.x |
REPL 和脚本的区别
和 Python 一样,Julia 的 REPL 与脚本执行体验并不完全一样。
最常见的差异包括:
- REPL 会自动显示表达式结果,脚本不会;
- REPL 中有
ans等交互式便利特性; - 某些作用域相关行为在 REPL 和脚本里需要格外注意。
因此,平时可以把它们简单区分为:
- REPL:适合试验和探索;
- 脚本:适合真正保存和执行代码。
迁移视角
默认读者已经熟悉 Python 和 MATLAB,这里不会把所有语法差异逐条铺开,而只保留几个会反复影响后续阅读的结论:
- Julia 的整体定位更接近“高性能科学计算语言”,而不是通用脚本语言,因此数组、线性代数、广播、类型系统和函数分派都处在语言中心。
- 语法观感上,它同时吸收了 Python、MATLAB、Fortran 等语言的一些习惯,但底层执行模型并不接近这些解释型工作流,而是明显更靠近带 JIT 的编译型路线。
- 从 Python 迁移时,最需要重新适应的通常是:1-based 索引、闭区间切片、
*直接表示矩阵乘法、严格得多的类型和方法分派、以及更强的编译参与感。 - 从 MATLAB 迁移时,最需要重新适应的通常是:真正的一维数组、更加严格的作用域与类型语义、模块和包管理、以及不再依赖“脚本工作区 + 向量化优先”的使用习惯。(MATLAB 的语言设计整体已经落后了)
- Julia 里很多“写起来像脚本语言”的代码,背后其实都在为后续的类型推断和特化编译服务,因此写法是否利于推断,会比 Python 和 MATLAB 中重要得多。
- 这套笔记后面各篇会在必要处顺手提一下和 Python / MATLAB 的差异,但默认不再展开基础背景。
如果考虑底层实现,那么 Julia 与 MATLAB/Python 实际上并不算同一路线,反而更适合和 C/C++ 对照来看:C++ 有明确分开的编译期和运行期,而 Julia 则把这两部分更紧密地揉在了一起,这导致用户始终需要编译器参与 Julia 程序的执行。
Julia 使用了一个基于 LLVM 的 JIT 编译器,编译行为对用户基本上是透明的,用户在基本使用时无需考虑这些因素,但是在考虑代码的效率优化以及高性能计算时需要考虑这部分因素。
例如 C++ 的模板实例化完全发生在编译期,而 Julia 则会在函数首次调用时(不是函数定义时)针对不同类型触发专门的方法编译,这当然会带来明显的首次调用开销;在下一次遇到相同类型时,这部分代价通常就不需要再次支付了。
Julia 与 MATLAB/Python 在向量化方面存在截然不同的表现:
- MATLAB 和 Python 的原生 for 循环非常慢,通常必须使用向量化语法,通过调用底层代码来加速;
- Julia 直接使用原生 for 循环通常已经足够高效,虽然也支持向量化,但向量化并不一定天然更快;如果产生额外的数据拷贝,反而可能更慢。对 Julia 来说,向量化更多是表达风格问题,而不是默认的性能捷径。
基本语法
注释
Julia 和 Python 一样使用 # 表示注释,支持多行注释:使用 #= 开始,使用 =# 结束,但是并不常见。
标识符
标识符可以使用字母,下划线或数字组成,对大小写敏感,数字不能在开头。
Julia 实际和 Python 3 一样,可以使用除了内部标识符和运算符之外的绝大多数 Unicode 字符作为标识符,不需要局限于英文字母,而且某些语义相同、形态相似的 Unicode 字符会被视作相同字符。
虽然 Julia 很强调 Unicode 友好,可以在把科学计算公式翻译为代码时更加方便,但在一般工程代码中,仍然更建议把变量名控制在英文、数字和下划线范围内。
变量
在入门阶段,可以认为 Julia 的变量机制和 Python 基本类似,因此不再展开。
Julia 有内置常量或函数,比如 pi 和 ans,甚至允许我们覆盖内置的定义,例如重新赋值 pi=3,但是前提是在此之前我们从未使用过它,否则赋值会报错。
在交互式环境中,上一次的表达式结果会自动存储在特殊的内置变量 ans 中,但是如果我们在第一次就对其赋值,那么 ans 的默认功能就会失效。
常量
与 Python 不同,Julia 支持定义固定类型的常量,例如
1 | const a = 1; |
注意:
- 在定义 const 变量之前,必须确保它不是一个已经定义的变量,否则定义会报错。
- 虽然修改 const 变量的值可能不会报错,但是仍然不建议修改,因为与 C/C++ 类似,常量的修改不符合语义,可能在部分代码的编译中直接固定了常量的值,后续对常量的修改可能不会生效,进而导致产生不符合预期的结果。
const a, b = 1, 2会同时创建两个常量。
命名规范
虽然 Julia 对标识符的限制很少,但是 Julia 文档还是提供了一份命名规范:
- 变量使用小写加下划线
- 常量使用全大写
- 函数和宏的名称使用小写加下划线
- 对于会修改输入变量的函数,名称以
!结尾,这些函数又叫做 “mutating” 或 “in-place” 函数 - 类型和模块名称使用大驼峰
但是 Julia 不鼓励大量使用下划线,除非缺少下划线时的名称难以理解。
shell/REPL
需要介绍一下 Julia 的 shell/REPL 中的相关操作,它内置了多个模式,可以根据当前的提示符来区分:
julia>表示 Julia 的普通模式,以 REPL 形式运行 Julia 代码,这是默认模式,其它特殊模式都可以通过backspace退回到普通模式help?>表示 Julia 的帮助模式,普通模式按?进入,例如在普通模式可以输入?sqrt查询相关内容,在读取?时自动进入帮助模式,在展示帮助信息后自动退出(@v1.12) pkg>表示 Julia 的包管理模式,普通模式按]进入,Julia 提供了一整套包管理的命令,暂不讨论shell>表示 Julia 的 shell 模式,普通模式按;进入,注意这里的 shell 模式并不是 bash/pwsh,而是非常受限的,有很多 shell 的内置命令都不能使用。
在各种模式下(包括执行脚本)都需要注意当前的工作目录:
- 在普通模式,可以使用
pwd()获取当前工作目录,使用cd()可以改变当前工作目录,在 Shell 模式则可以使用常见的目录切换命令。 - 与 MATLAB 类似,对于 julia 脚本,最好在开头把工作目录设置为脚本所在目录(使用
cd(@__DIR__),这样脚本中的文件路径都是相对于脚本所在位置的,保证脚本可靠运行。
Julia 的这种分模式的处理显然吸取了 MATLAB 虚拟终端的设计失败教训。
关于特殊符号的输入:
- 有时需要使用一些 Unicode 字符,在 REPL 中通常直接使用 LaTeX 代码(例如
\div)加上 tab键就可以切换(在 Jupyter Notebook 中也支持)。 - 如果不知道特殊符号应该如何输入,可以在 REPL 中输入
?进入帮助模式,然后粘贴对应的 Unicode 字符,就可以获得关于这个字符的输入方法。
exit() 或 ctrl+d 可以退出 REPL。
输入输出基础
简单介绍一下基本的输入输出操作。
print() 和 println() 可以向控制台输出内容,区别是后者自带一个回车。
1 | print("hello,world!\n") |
也可以直接输出多个字符串,中间不会加入分隔符
1 | print("a","b") # ab |
Julia 支持和 Python 的 f-string 类似的格式化输出方式,在字符串中可以使用 $ 插入变量或表达式,有歧义时需要加小括号,例如
1 | a = 1; |
readline() 可以从控制台获取一行输入数据,得到的是字符串数据,例如
1 | s = readline() |
通常需要对输入数据进行类型转换,可以使用 parse() 函数,例如
1 | s = readline() # "123" |
日志打印
Julia 内置了一些信息输出的宏,包括 @show、@debug、@info、@warn 和 @error,例如
1 | julia> @show "hi" |
注意这里 @debug 的信息默认不会显示出来。
更完整的日志系统由
Logging这个包提供支持。
