# Julia 语言概览
一份简单而粗略的语言概览
# 什么是Julia
Julia (opens new window) 是一种为科学计算而生的,开源、多平台、高性能的高级编程语言。
Julia 有一个基于 LLVM (opens new window) 的 JIT (opens new window) 编译器,这让使用者无需编写底层的代码也能拥有像 C 与 FORTRAN 那样的性能。因为代码在运行中编译,你可以在 shell 或 REPL(Read-Eval-Print-Loop,即交互式命令行) (opens new window) 中运行代码,这也是一种推荐的工作流程 (opens new window)。
Julia 是动态类型的。并且提供了为并行计算和分布式运算设计的多重派发 (opens new window)机制。
Julia 允许你通过类似 Lisp 的宏来自动生成代码。
Julia 诞生于 2012 年。
# 交互式命令行
| 上一次运算的结果 | ans |
| 中断命令执行 | [Ctrl] + [C] |
| 清屏 | [Ctrl] + [L] |
| 运行程序文件 | include("filename.jl") |
查找 func 相关的帮助 | ?func |
查找 func 的所有定义 | apropos("func") |
| 命令行模式 | ; |
| 包管理模式 | ] |
| 帮助模式 | ? |
| 查找特殊符号输入方式 | ?☆ # "☆" can be typed by \bigwhitestar<tab> |
| 退出特殊模式 返回到 REPL | 在空行上按 [Backspace] |
| 退出 REPL | exit() 或 [Ctrl] + [D] |
常用特殊符号:
# 在Julia命令行中输入"?☆",显示如下符号:
!, %, &, ', *, +, -, /, :, <, >, \, ^, |, ~, ÷, π, ℯ, ∈, ∉, ∋, ∌, ∘, √, ∛, ∩, ∪, ≈, ≉, ≠, ≡, ≢, ≤ or ≥
# 基础语法
| 赋值语句 | answer = 42 x, y, z = 1, [1:10; ], "A string"x, y = y, x # 交换 x, y |
| 常量定义 | const DATE_OF_BIRTH = 2012 |
| 行尾注释 | i = 1 # 这是一行注释 |
| 多行注释 | #= 这是另一行注释 =# |
| 链式操作 | x = y = z = 1 # 从右向左0 < x < 3 # true 5 < x != y < 5 # false |
| 函数定义 | function add_one(i) return i + 1 end |
| 插入 LaTeX 符号 | \delta + [Tab] # δ |
# 运算符
| 基本算数运算 | +,-,*,/ |
| 幂运算 | 2^3 # 8 |
| 除法 | 3/12 # 0.25 |
| 反向除法 | 7\3 == 3/7 # true |
| 整除 | x ÷ y # 取 x/y 的整数部分 |
| 取余 | x % y 或 rem(x,y) |
| 否定 | !true # false |
| 等于 | a == b-0.0 == 0.0 # true 而不是 isequal(-0.0, 0.0) # false |
| 不等于 | a != b 或 a ≠ b |
| 小于与大于 | < 与 > |
| 小于等于 | <= 或 ≤ |
| 大于等于 | >= 或 ≥ |
| 短路与 | x && y # 仅当x为true的时候才会执行y |
| 短路或 | x || y # 仅当x为false的时候才会执行y |
| 按位取反 | ~x |
| 按位与 | x & y |
| 按位或 | x | y |
| 按位异或(逻辑异或) | x ⊻ y 或 xor(x, y) |
| 按位与(非与) | x ⊼ y 或 nand(x, y) |
| 按位或(非或) | x ⊽ y 或 nor(x, y) |
| 逻辑右移 | x >>> y |
| 算术右移 | x >> y |
| 逻辑/算术左移 | x << y |
| 逐元素运算(点运算) | [1, 2, 3] .+ [1, 2, 3] == [2, 4, 6] # true[1, 2, 3] .* [1, 2, 3] == [1, 4, 9] # true |
| 检测无穷值 | isinf(x) |
| 检测有限值 | isfinite(x) |
| 检测非数值(NaN) | isnan(NaN) # true 而不是 NaN == NaN # false |
| 三元运算符 | a == b ? "Equal" : "Not equal" |
| 对象等价 | a === b |
| 赋值运算符 | += -= *= /= \= ÷= %= ^= &= |= ⊻= >>>= >>= <<= |
# 字符与字符串
| 字符 | chr = 'C' |
| 字符串 | str = "A string" |
| 字符 => 编码 | Int('J') # 74 |
| 编码 => 字符 | Char(74) # 'J' |
| 任意的 UTF 字符 | chr = '\uXXXX' # 4 位 HEXchr = '\UXXXXXXXX' # 8 位 HEX |
| 逐字符迭代 | for c in str println(c) end |
| 字符串拼接 | str = "Learn" * " " * "Julia" |
| 字符插值 | a = b = 2 println("a * b = $(a*b)") |
| 字符串截取 | str = "Hello, world"; str[begin:end-2] # "Hello, wor" |
| 子串包含判断 | occursin("ab", "aba") # trueoccursin(r"a.a", "aba") # trueoccursin(r"a.a", "abba") # falseoccursin(r"a*a", "abba") # true |
| 子串开始判断 | startswith("abc", "ab") # true |
| 子串结束判断 | endswith("abc", "bc") # true |
| 第一个匹配的子串或正则表达式 | findfirst(isequal('i'), "Julia") # 4 |
| 替换字串或正则表达式 | replace("Julia", "a" => "us") # "Julius"replace("The quick foxes run quickly.", "quick" => "", count=1) |
| 收集的最后一个索引值 | lastindex("Hello") # 5 |
| 字符串的长度 | length("Hello") # 5 |
| 正则表达式 | pattern = r"l[aeiou]" |
| 子字符串 | str = "+1 234 567 890" pat = r"\+([0-9]) ([0-9]+)" m = match(pat, str) m.captures # ["1", "234"] |
| 所有匹配 | [m.match for m = eachmatch(pat, str)] |
| 所有匹配的迭代器 | eachmatch(pat, str) |
| 任意值转字符串 | string("a", 1, true) # "a1true"string([1,2,3]) # "[1, 2, 3]"string(255, base=16, pad=4) # "00ff" |
| 格式化输出 | using Printfstr = (@sprintf "%s is %5.1f" "value" 34.567) # "value is 34.6"str = (@sprintf "value is %.3e" 1.23456) # "value is 1.235e+00"str = (@sprintf "value is %.2f" 1.23456) # "value is 1.23"str = (@sprintf "value is %06i" 123) # "value is 000123"str = (@sprintf "%g %g" 1.23 12300000.0) # "1.23 1.23e+07" |
| 原始字符串 | raw"C:\Users\TR" # 支持原始字符串"C:\\Users\\TR" # windows 路径"C:/Users/TR" # 适用于 windows 和 linux 系统 |
| 多行字符串 | """ 这是一段多行字符串 """"""Contains "quote" characters""" # "Contains \"quote\" characters" |
要当心 UTF-8 中的多字节 Unicode 编码:
Unicode_string = "Ångström"
ncodeunits(Unicode_string) # 10,即UTF-8字符串的实际字节数
lastindex(Unicode_string) # 10
length(Unicode_string) # 8
Unicode_string[10] # 'm': ASCII/Unicode U+006d
Unicode_string[9] # ERROR: StringIndexError("Ångström", 9)
Unicode_string[8] # 'ö': Unicode U+00f6
# 遍历UTF-8字符串
for c in Unicode_string
print(c)
end
# 运行结果为:Ångström
# 遍历UTF-8字符串
for i in eachindex(Unicode_string)
println("[$i]: ", Unicode_string[i])
end
#= 运行结果为:
[1]: Å
[3]: n
[4]: g
[5]: s
[6]: t
[7]: r
[8]: ö
[10]: m
=#
字符串是不可变的。
# 数值
| 整数类型 | IntN 和 UIntN, 且 N ∈ {8, 16, 32, 64, 128}, BigInt |
| 浮点类型 | FloatN 且 N ∈ {16, 32, 64} BigFloat |
| 类型的最大和最小值 | typemin(Int8) typemax(Int64) |
| 无穷大 | Inf 或 Inf32 或 Inf16 |
| 负无穷大 | -Inf 或 -Inf32 或 -Inf16 |
| 进制数 | 0b1000 # 二进制0o10 # 八进制0xff # 十六进制Int(0xff) # 255 |
| 有理数 | 2//3 #Float64(2//3) # 0.6666666666666666 |
| 复数类型 | Complex{T<:Real}Complex{Float64} 与 ComplexF64 等价 |
| 复数 | 2.0 + 3imcomplex(2.0, 3) # 2.0 + 3.0imim * im == -1 # true |
| 复数实部 | real(2.0+3im) # 2.0 |
| 复数虚部 | imag(2.0+3im) # 3.0 |
| 复共轭 | conj(1 + 2im) # 1 - 2im |
| 复数的模 | abs(1 + 2im) # 2.23606797749979 |
| 相位角 | rad2deg(angle(1 + im)) # 45.0 |
| 机器精度 | eps() # 等价于 eps(Float64) |
| 整数转浮点 | Float64(1) # 1.0 |
| 浮点转整数 | floor(Int, 2.3) # 向下取整,返回2ceil(Int, 2.3) # 向上取整,返回3 |
| 圆整 | round() # 浮点数圆整 round(Int,2.5) # 整数圆整,返回2round(Int, 2.5, RoundNearestTiesAway) # 与 matlab 等价,四舍五入,返回 3 |
| 类型转换 | convert(TypeName, val) # 尝试进行转换/可能会报错 TypeName(val) # 调用类型构造器转换 Int(2.0) # 2Int(2.5) # 报错Int(round(2.5)) # 2 |
| 数值转字符串 | string(0xff) # "255" |
| 字符串转数值 | parse(Int, "0b1000") # 8parse(Int, "0o10") # 8parse(Int, "0xff") # 255parse(Int, "ff", base=16) # 255parse(Float64, "0xff") # 255.0 |
| 近似判等 | ≈ 或 isapprox(x, y)isapprox(1e-10, 0, atol=1e-8) # trueisapprox(0.1, 0.15; rtol=0.34) # trueisapprox(0.1, 0.15; rtol=0.33) # false |
| 全局常量 | pi # 3.1415... π # 3.1415... ℯ # 2.7182818284590...im # real(im * im) == -1 |
| 更多常量 | using Base.MathConstants |
Julia 不会自动检测数值溢出。 使用 SaferIntegers (opens new window) 包可以得到带溢出检查的整数。
# 随机数
| 函数库 | using TyRandom |
| 随机数流 | using TyRandomrng = MT19937ar(5489) # 梅森旋转算法,等同于 rngrand(rng, 2,3) # 2x3 矩阵,与 MATLAB 的 rand(2,3) 结果一致 |
| 均匀分布的随机数 | using TyRandomrand(2,3) # 2x3 矩阵r = a .+ (b-a)*rand(N) # 生成区间 (a,b) 内的 N 个随机数的公式 |
| 均匀分布的伪随机整数 | using TyRandomrandi((-5,5), 10) # 生成区间 (-5, 5) 的 10 个整数 |
| 正态分布 | using TyRandomr = randn(5, 5) # 5x5 矩阵 |
| 整数的随机排列 | using TyRandomrandperm(6) # 生成一个从 1 到 6 的整数的随机排列 |
| 随机重排 A 中的元素 | shuffle(A) |
# 数组
| 声明数组 | arr = Float64[]arr=[1,2,3] # 向量arr=[1 2 3] # 1x3矩阵arr=[1 2 3]' # 3x1转置矩阵arr=[1 2 3; 4 5 6] # 2x3矩阵 |
| 预分配内存 | sizehint!(arr, 10^4) |
| 访问与赋值 | arr = Any[1,2] arr[1] = "Some text" |
| 数组比较 | a = [1:10;] b = a # b 指向 a a[1] = -99 a == b # true |
| 复制元素(而不是地址)/深拷贝 | b = copy(a) b = deepcopy(a) |
| 从 m 到 n 的子数组 | arr[m:n] |
| n 个 0.0 填充的数组 | zeros(3) # [0.0, 0.0, 0.0]zeros(Int64,n) # [0, 0, 0]zeros(3,3) # 3x3矩阵 |
| n 个 1.0 填充的数组 | ones(3) # [1.0, 1.0, 1.0]ones(Int64,3) # [1, 1, 1] ones(3,3) # 3x3矩阵 |
| n 个 #undef 填充的数组 | Vector{Type}(undef,n) |
| n 个从 start 到 stop 的等间距数 | range(1, step=5, stop=100) # 1:5:496range(1, step=5, length=3) # 1:5:11 |
| 用值 val 填充数组 | fill(value, dims...) # 创建数组fill!(arr, val) # 填充数组 |
| 弹出最后一个元素 | pop!(arr) |
| 弹出第一个元素 | popfirst!(a) |
| 将值 val 作为最后一个元素压入数组 | push!(arr, val) |
| 将值 val 作为第一个元素压入数组 | pushfirst!(arr, val) |
| 删除指定索引值的元素 | deleteat!(arr, idx) |
| 清空数组 | empty!(collection) |
| 数组排序 | sort!(arr) |
| 将 b 连接到 a 后 | append!(a,b) |
| 检查值 val 是否在数组 arr 中 | in(val, arr) 或 val in arr |
| 改变维数 | reshape(1:6, 2, 3) # 将向量改变为 2x3 矩阵 |
| 转化为字符串,并以 delim 分隔 | join([1 2 3], ",") # "1,2,3" |
| 使用范围函数来创建数组 | [1:10...] # 10个元素的向量collect(1:10) # 10个元素的向量 |
| 使用推导式和生成器创建数组 | [n^2 for n in 1:10] # 10个元素的向量 |
| 数组元素类型 | eltype(A) |
| 数组长度 | length(A) |
| 数组维数 | ndims(A) |
| 数组大小 | size(A) # 各个维度的元素数量size(A,n) # 第n维的元素数量 |
| 高效迭代器 | for i in eachindex(A) # linear indexing println("A[", i, "] == ", A[i])end |
| 水平连接 | hcat(A...) 或 cat(A...; dims=2) hcat([1,2], [3,4], [5,6]) # 2x3矩阵 |
| 垂直连接 | vcat(A...) 或 cat(A...; dims=1)vcat([1,2], [3,4]) # [1, 2, 3, 4]vcat([10, 20, 30]', Float64[4 5 6; 7 8 9]) # 3x3数组 |
# 元组
Julia的元组与数组类似,都是有序的元素集合,不同之处在于元组不能修改。
元组使用小括号(...),数组使用方括号[...]。
| 创建元组 | tupl=(5,10,15,20,25,30) |
| 访问元素 | tupl[3:end] # (15, 20, 25, 30) |
| 命名元组 | x = (a = 1, b = 2, c = 3)t_names = (:a, :b, :c);t_values = (1, 2, 3);x = NamedTuple{t_names}(t_values) # 构造命名元组,与前面 x 等同 |
使用.访问元组 | x.a # 返回 1x[:a] # 返回1 |
| 使用 [ ] 访问元组 | tupl[2] # 10x[2] # 2 |
| 判断值是否在元组 | 10 in tupl # true2 in x # true |
| 元组转向量 | collect(x) #[1,2] |
| 向量转元组 | Tuple(Real[1, 2, pi]) # (1, 2, π)tuple(Real[1, 2, pi]...) # (1, 2, π) |
| 遍历元组 | for i in x println(i)end |
| 合并两个命名元组 | merge((a=1, b=2, c=3), (b=4, d=5)) |
| 元组作为函数参数 | function testFunc(x, y, z; a=10, b=20, c=30) println("x = $x, y = $y, z = $z; a = $a, b = $b, c = $c") endoptions = (b = 200, c = 300) # 创建元组testFunc(1, 2, 3; options...) # 元组作为参数传入 |
# 线性代数
想要使用线性代数相关的工具,请用:using LinearAlgebra。
| 单位矩阵 | I # 直接用 I 就好。会自动转换到所需的维数。 |
| 定义矩阵 | M = [1 0; 0 1] |
| 矩阵维数 | size(M) |
选出第 i 行 | M[i, :] |
选出第 j 列 | M[:, j] |
| 水平拼接 | M = [a b] 或 M = hcat(a, b) |
| 竖直拼接 | M = [a ; b] 或 M = vcat(a, b) |
| 矩阵转置 | transpose(M) |
| 共轭转置 | M' 或 adjoint(M) |
| 迹(trace) | tr(M) |
| 行列式 | det(M) |
| 秩(rank) | rank(M) |
| 特征值 | eigvals(M) |
| 特征向量 | eigvecs(M) |
| 矩阵求逆 | inv(M) |
解矩阵方程 M*x == v | M\v 比 inv(M)*v 更好 (opens new window)。 |
| 求 Moore-Penrose 伪逆 | pinv(M) |
Julia 有内置的矩阵分解函数 (opens new window)。
Julia 会试图推断矩阵是否为特殊矩阵(对称矩阵、厄米矩阵等),但有时会失败。 为了帮助 Julia 分派最优的算法,可以声明矩阵具有特殊的结构。如:对称矩阵、厄密矩阵(Hermitian)、上三角矩阵、下三角矩阵、对角矩阵等。
# 控制流与循环
| 条件语句 | if-elseif-else-end |
for 循环 | for i in 1:10 println(i) end |
| 嵌套循环 | for i in 1:10, j = 1:5 println(i*j) end |
| 枚举 | for (idx, val) in enumerate(arr) println("the $idx-th element is $val") end |
while 循环 | while bool_expr # 做点啥 end |
| 退出循环 | break |
| 退出本次循环 | continue |
| 复合表达式 | z = begin x = 1 y = 2 x + yend |
# 函数
函数的详细介绍,请参见 function。
# 向量化
| 点运算符 | [1,2,3] .^ 3 # [1, 8 ,27] |
| 广播 | a = [0 10 20 30]'b = [1 2 3]a .+ b # 返回 4x3 矩阵broadcast(+,a,b) # 等价于 a .+ b |
f.(args...) | A = [1.0, 2.0, 3.0]sin.(A) # 对 A 中每个元素执行 sin 运算 |
@. | Y = [1.0, 2.0, 3.0, 4.0];X = similar(Y); # 预分配@. X = sin(cos(Y)) # 等价于 X .= sin.(cos.(Y)) |
# 字典与集合
字典是一种可变容器模型,且可存储任意类型对象。
| 创建字典 | d = Dict(key1 => val1, key2 => val2, ...) d = Dict(:key1 => val1, :key2 => val2, ...)d = Dict{String, Float64}() # 创建空字典d = Dict("A"=>1, "B"=>2) |
| 所有的键 (迭代器) | keys(d) |
| 所有的值 (迭代器) | values(d) |
| 按键值对迭代 | for (k,v) in d println("key: $k, value: $v") end |
是否存在键 k | haskey(d, k)in("A" => 1, d) # 返回true |
| 将键/值复制到数组 | arr = collect(keys(d)) arr = [k for (k,v) in d] |
| 添加键/值对 | d["C"]=3 |
| 删除键/值对 | delete!(d, "C") |
| 字典转向量 | c = collect(d) # 返回Pair类型的数组 |
| 向量转字典 | Dict(c) # 要求输入是Pair类型的数组 |
| 字典长度 | length(d) |
| 判断字典是否为空 | isempty(d) |
| 清空字典 | empty!(d) |
| 字典排序 | d = Dict("R" => 100, "S" => 220, "T" => 350, "U" => 400, "V" => 575)for k in sort(collect(keys(d))) println("$k => $(d[k])") end |
Set 集合是没有重复的对象数据集,所有的元素都是唯一的。
| 创建集合 | s = Set([1, 2, 3]) |
并集 s1 ∪ s2 | union(s1, s2) |
交集 s1 ∩ s2 | intersect(s1, s2) |
补集 s1 ∖ s2 | setdiff(s1, s2) |
对称差 s1 △ s2 (symmetric difference) | symdiff(s1, s2) |
子集? s1 ⊆ s2 | issubset(s1, s2) |
| 判断元素是否存在集合中 | in(2, s) # true |
| 添加元素 | push!(s, 4) |
| 删除元素 | delete!(s, 4) |
| 遍历集合 | for i in s println(i) end |
| 清空元素 | empty!(s) |
# 日期和时间
日期和时间的详细介绍,请参见日期和时间。
# 高阶函数
在 julia 中,函数都是对象。函数对象可以作为参数传递给其他函数,我们将这些接收函数的函数,称为高阶函数。
| 列表推导 | arr = [f(elem) for elem in coll] [n^2 for n in 1:5] # [1, 4, 9, 16, 25] |
| 将 f 应用到 coll 中的每一个元素上 | map(f, coll)map(x -> x * 2, [1, 2, 3]) # [2, 4, 6] |
| 滤出 coll 中使 f 为真的每一个元素 | filter(f, coll)filter(isodd, 1:10) # [1, 3, 5, 7, 9] |
| 累积计算函数,即将结果继续和序列的下一个元素做累积计算 | reduce(*, [2; 3; 4]) # 24reduce(*, [2; 3; 4]; init=-1) # -24 |
# 类型
关于类型的详细介绍,请参见类型系统。
类型就像是没有方法的类。抽象类型可以拥有子类型,但不能被实例化。具体类型不能拥有子类型。
struct默认是不可变的。不可变类型能改善程序的性能,并且它们是线程安全的,它们在跨线程使用时不需要同步。
| 类型注释 | var::TypeName |
| 类型声明 | struct Programmer name::String birth_year::UInt16 fave_language::AbstractString end |
| 可变类型声明 | 将 struct 替换为 mutable struct |
| 类型别名 | const Nerd = Programmer |
| 类型构造器 | methods(TypeName) |
| 类型实例 | me = Programmer("Ian", 1984, "Julia") me = Nerd("Ian", 1984, "Julia") |
| 子类型声明 | abstract type Bird end struct Duck <: Bird pond::String end |
| 参数化类型 | struct Point{T <: Real} x::T y::T end p = Point{Float64}(1,2) |
| 联合类型 | Union{Int, String} |
| 遍历类型层级 | supertype(TypeName) # 父类 subtypes(TypeName) # 子类 |
| 默认的超类型 | Any |
| 变量类型 | M = [1 2; 3.5 4]typeof(M) # Matrix{Float64} (alias for Array{Float64, 2}) |
| 数组元素类型 | eltype(M) # Float64 |
| 类型判断 | isa(1, Int) # trueisa(1.0, Float64) # true |
| 实数判断 | isreal(5) # trueisreal(5.0) # trueisreal(NaN) # trueisreal(Inf) # trueisreal(5.0 + 0im) # trueisreal(5.0 + 1im) # falseisreal(Any[1 2; 3 4]) # 若数组元素都是实数,返回 true |
| 所有字段 | struct PointF x::Float64 y::Float64endfieldnames(PointF) # (:x, :y) |
| 所有字段类型 | PointF.types # svec(Float64, Float64)PointF.types[1] # Float64 |
类型参数具有不变性(invariant),这意味着即使 Float64 <: Real,
Point{Float64} <: Point{Real} 依旧为假。 与此不同的是,元组类型(Tuple)是协变的(covariant):Tuple{Float64} <: Tuple{Real}。
通过code_typed()函数可以查看 Julia 代码经过类型推断后的内部表示形式(IR)。这个函数常用来确定本地代码中那些地方出现了Any类型而不是特定的类型。
# 缺失值与空值
| 空值(Null) | nothing |
| 缺失数据 | missing |
| 浮点数的非数值 | NaN |
| 滤除缺失值 | collect(skipmissing([1, 2, missing])) # [1,2] |
| 替换缺失值 | replace([1,2,missing, missing], missing => 0) # [1, 2, 0, 0] |
| 检查是否缺失值 | ismissing(x) 而不是 x == missing |
| 检查是否空值 | isnan(NaN) # true 而不是 NaN == NaN # false |
# 异常处理
| 抛出异常 SomeExcep | throw(SomeExcep()) |
| 再次引发当前的异常 | rethrow() |
| 定义新异常 NewExcep | struct NewExcep <: Exception v::String end Base.showerror(io::IO, e::NewExcep) = print(io, "A problem with $(e.v)!") throw(NewExcep("x")) |
| 断言 | @assert iseven(3) "3 is an odd number!" |
| 消息 | @info "An informational message" |
| 警告 | @warn "Something was odd. You should pay attention" |
| 错误 | @error "A non fatal error occurred" |
| 异常处理流程 | try # 进行一些可能会失败的操作 catch ex if isa(ex, SomeExcep) # 处理异常 SomeExcep elseif isa(ex, AnotherExcep) # 处理另一个异常 AnotherExcep else # 处理其余的异常 end finally # 永远执行这些语句 end |
# 模块
模块是独立的全局变量工作区,它们将类似的功能组合到一起。
| 定义 | module NiceStuff export nice, DOG struct Dog end const DOG = Dog() nice(x) = "nice $x"end |
包含文件 filename.jl | include("filename.jl") |
| 加载 | using ModuleName # 导入所有名称 using ModuleName: x, y # 仅导入 x, y using ModuleName.x, ModuleName.y # 仅导入 x, y import ModuleName # 仅导入 ModuleName import ModuleName: x, y # 仅导入 x, y import ModuleName.x, ModuleName.y # 仅导入 x, y |
| 模块导出列表 | # 得到模块导出名称的数组 names(ModuleName) # 包含未导出的、弃用的和编译器产生的名称names(ModuleName, all::Bool) # 也显示从其他模块显式导入的名称 names(ModuleName, all::Bool, imported::Bool) |
| 所属模块 | parentmodule(sin) # Base |
| 用 as 来重命名 | import BenchmarkTools as BTimport CSV: read as rd |
| 裸模块 | # 模块自动包含 using Core、using Base 以及 eval 和 include 函数的定义。如果不需要这些默认定义,可以使用 baremodule 来定义模块(注意:Core 仍然是导入的)baremodule Mod #...end |
| 标准模块 | Core包含了语言内置的所有功能。Base包含了绝大多数情况下都会用到的基本功能。Main是顶层模块,当 julia 启动时,也是当前模块。 |
using 和 import 的一点区别:
使用 using 时,你需要写 function Foo.bar(...) 来给 Foo模块的函数 bar 增添一个新方法; 而使用 import Foo.bar 时,只需写 function bar(...) 就能达到同样的效果。
# 命名空间管理
命名空间管理用于使模块中的名称在其他模块中可用。我们在下面详细讨论相关的概念和功能。
# 合格的名称
全局作用域内的函数、变量和类型的名称,如sin、ARGS和UnitRange始终属于一个模块,称为母模块,例如,可以用parentmodule来找到该模块。
parentmodule(UnitRange) # 返回 Base
也可以通过在它们的模块前面加上前缀来引用它们的父模块之外的这些名称,例如Base.UnitRange。这称为限定名称。父模块可以使用像Base.Math.sin这样的子模块链来访问,其中Base.Math被称为模块路径。由于句法歧义,限定只包含符号的名称,例如运算符,需要插入冒号,例如Base.:+。少数运算符还需要括号,例如Base.:(==)。
# 导出列表
名称(指函数、类型、全局变量和常量)可以通过export添加到模块的导出列表。通常,它们位于或靠近模块定义的顶部,以便源代码的读者可以轻松找到它们,如:
module NiceStuff
export nice, DOG
struct Dog end # singleton type, not exported
const DOG = Dog() # named instance, exported
nice(x) = "nice $x" # function, exported
end
但这只是一个风格建议——一个模块可以在任意位置有多个export语句。
导出构成 API(应用程序接口)一部分的名称是很常见的。在上面的代码中,导出列表建议用户应该使用nice和DOG。然而,由于限定名称总是使标识符可访问,这只是组织 API 的一个选项:与其他语言不同,Julia 没有真正隐藏模块内部的功能。
此外,某些模块根本不导出名称。这通常是因为他们的 API 中使用常用词,这很容易与其他模块的导出列表发生冲突。
# 单独使用 using 和 import
加载模块最常见的方式可能是using ModuleName。
严格来说,声明using ModuleName意味着一个名为ModuleName的模块可用于根据需要解析名称。当遇到当前模块中没有定义的全局变量时,系统会在ModuleName导出的变量中查找,找到就使用。这意味着当前模块中该全局变量的所有使用都将解析为ModuleName中该变量的定义。
继续我们的例子,
using NiceStuff
将加载上面的代码,使NiceStuff(模块名称)、DOG和nice可用。Dog不在导出列表中,但如果名称被模块路径(这里只是模块名称)限定为NiceStuff.Dog,则可以访问它。
重要的是,导出列表只在using ModuleName 的形式下起作用。
相反,
import NiceStuff
仅将模块名称带入作用域。 用户需要使用NiceStuff.DOG、NiceStuff.Dog和NiceStuff.nice来访问其内容。通常,当用户想要保持命名空间干净时,在上下文中使用import ModuleName。正如我们将在下一节中看到的,import NiceStuff等同于using NiceStuff: NiceStuff。
你可以用逗号分隔符来组合相同类型的多个using和import语句,例如:
using LinearAlgebra, Statistics
# 具有特定标识符的 using 和 import
当using ModuleName:或import ModuleName:后跟以逗号分隔的名称列表时,模块会被加载,但 只有那些特定的名称才会被语句带入命名空间。 例如,
using NiceStuff: nice, DOG
将导入名称nice和DOG。
重要的是,模块名称NiceStuff 不会出现在命名空间中。如果要使其可访问,则必须明确列出它,如
using NiceStuff: nice, DOG, NiceStuff
Julia 有两种形式来表示似乎相同的内容,因为只有import ModuleName:f允许在 没有模块路径的情况下向f添加方法。也就是说,以下示例将给出一个错误:
using NiceStuff: nice
struct Cat end
nice(::Cat) = "nice 😸" # 报错
此错误可防止意外将方法添加到你仅打算使用的其他模块中的函数。
有两种方法可以解决这个问题。你始终可以使用模块路径限定函数名称:
using NiceStuff
struct Cat end
NiceStuff.nice(::Cat) = "nice 😸"
或者,你可以import特定的函数名称:
import NiceStuff: nice
struct Cat end
nice(::Cat) = "nice 😸"
# 用 as 来重命名
由import或using引入作用域的标识符可以用关键字as重命名。这对于解决名称冲突以及缩短名称很有用。 例如,Base导出函数名read,但 CSV.jl 包也提供了CSV.read。如果我们要多次调用 CSV 读取,删除 CSV.限定符会很方便。但是,我们指的是Base.read还是CSV.read是模棱两可的:
read; # Base.read
import CSV: read
# WARNING: ignoring conflicting import of CSV.read into Main
重命名提供了一个解决方案:
import CSV: read as rd
导入的包本身也可以重命名:
import BenchmarkTools as BT
as仅在将单个标识符引入作用域时才与using一起使用。例如,using CSV: read as rd有效,但using CSV as C无效,因为它对CSV中的所有导出名称进行操作。
# 处理名称冲突
考虑两个(或更多)包导出相同名称的情况,如:
module A
export f
f() = 1
end
module B
export f
f() = 2
end
using A, B语句有效,但是当你尝试调用f时,你会收到警告:
WARNING: both B and A export "f"; uses of it in module Main must be qualified
ERROR: LoadError: UndefVarError: f not defined
在这里,Julia无法确定您指的是哪个f,因此你必须做出选择。常用的解决方法有以下几种:
(1)只需继续使用限定名称,如A.f和B.f。这使代码的读者可以清楚地了解上下文,特别是如果f恰好重合但在不同的包中具有不同的含义;
(2)使用上面的as关键字重命名一个或两个标识符,例如:
using A: f as f
using B: f as g
会使B.f可用作g。在这里,我们假设您之前没有使用using A,这会把 f 代入命名空间;
(3)当问题中的多个名称确实有相同的含义时,通常一个模块会从另一个模块导入它,或者有一个轻量级的“基础”包,它的唯一功能是定义这样的接口,可以被其他包使用。按照惯例,这些包名以...Base结尾(这与 Julia 的Base模块无关)。
# 子模块和相对路径
模块可以包含 子模块,嵌套相同的语法module ... end。它们可用于引入单独的命名空间,这有助于组织复杂的代码库。请注意,每个module都引入了自己的 作用域,因此子模块不会自动从其父模块“继承”名称。
建议子模块在using和import语句中使用 相对模块限定符 来引用封闭父模块中的其他模块(包括后者)。 相对模块限定符以句点 (.) 开头,它对应于当前模块,每个连续的.都指向当前模块的父级。 如有必要,这应该跟在模块之后,最后是要访问的实际名称,所有名称都以.分隔。
考虑以下示例,其中子模块SubA定义了一个函数,然后在其兄弟模块中进行扩展:
module ParentModule
module SubA
export add_D
const D = 3
add_D(x) = x + D
end
using .SubA # brings `add_D` into the namespace
export add_D # export it from ParentModule too
module SubB
import ..SubA: add_D # 兄弟模块的相对路径
struct Infinity end
add_D(x::Infinity) = x
end
end
请注意,如果你正在评估值,定义的顺序也很重要。 例如:
module TestPackage
export x, y
x = 0
module Sub
using ..TestPackage
z = y # ERROR: UndefVarError: y not defined
end
y = 1
end
# 表达式
Julia 具有同像性:程序被表示为语言本身的数据结构。 实际上 Julia 语言里的任何东西都是一个表达式Expr。
符号 (Symbols) 即驻留字符串 (opens new window),以冒号:为前缀。 相对于其他类型来说,符号效率更高。它也经常用作标识符、字典的键或者数据表里的列名。 符号不能进行拼接。
使用引用:( ... )或块引用quote ... end可以创建一个表达式,就像 parse(str) (opens new window),和Expr(:call, ...)。
x = 1
line = "1 + $x" # 一些代码
expr = Meta.parse(line) # 生成一个 Expr 对象
typeof(expr) == Expr # true
dump(expr) # 打印生成抽象语法(AST)
eval(expr) == 2 # 对 Expr 对象求值: true
# 宏
宏允许你在程序中自动生成代码(如:表达式)。
在 julia 终端中输入@,再连续按两下 Tab 键,可以显示所有内置的宏。
在 julia 终端中输入?,再输入宏名,可以查看该宏的相关帮助。
| 定义 | macro macroname(expr) # 做点啥 end |
| 使用 | macroname(ex1, ex2, ...) 或 @macroname ex1, ex2, ... |
| 性能分析的宏 | @time # 运行时间与内存分配统计 @elapsed # 返回执行用时 @allocated # 查看内存分配 using BenchmarkTools@btime sin(pi) # 在基准测试期间测量的最小运行时间@profview myfunc() # 性能分析并显示火焰图using Profile @profile myfunc() # 性能分析Profile.print() # 显示结果 |
| 类型推断的宏 | @code_warntype(sin(pi)) # 查看类型稳定@code_typed(sin(pi)) #查看类型推断@code_llvm(sin(pi)) # 查看LLVM IR@code_native(sin(pi)) # 查看汇编代码 |
| 测试相关的宏 | using Test @test 1+1 == 2 # 精确相等 @test x ≈ y # 近似相等 isapprox(x, y)@testset "math" begin # 测试分组 @test 1 + 1 == 2 @test 1 - 1 == 0end |
| 报错相关的宏 | @debug "Verbose debugging information. Invisible by default"@info "An informational message"@warn "Something was odd. You should pay attention"@error "A non fatal error occurred" |
| 打印相关的宏 | x=1;y=2;@show x y; # 查看表达式@printf "Decimal two digits %.2f" 1.23456 # 字符串格式化 |
| 路径相关的宏 | @__DIR__ # 当前文件所属文件夹路径@__FILE__ # 当前文件名@__LINE__ # 当前所在行号@__MODULE__ # 当前顶层模块 |
| 其他内置宏 | @async # 异步任务 @which # 查看对特定参数使用的方法/查找函数所在的模块 |
创建 卫生宏 (hygienic macros)的规则:
- 在宏的内部只通过
local声明本地变量。 - 在宏的内部不使用
eval。 - 转义插值表达式以避免宏变大:
$(esc(expr))
# 文件读写
关于文件系统的详细介绍及相关示例,请参见文件系统 。
关于文件读写的详细介绍及相关示例,请参见文件 I/O 。
| 读取流 | stream = stdin for line in eachline(stream) # 做点啥 end |
| 读取文件 | open(filename) do file for line in eachline(file) # 做点啥 end end |
| 读取 CSV 文件 | using CSV data = CSV.File(filename) |
| 写入 CSV 文件 | using CSV CSV.write(filename, data) |
| 保存 Julia 对象 | using JLD save(filename, "object_key", object, ...) |
| 读取 Julia 对象 | using JLD d = load(filename) # 返回对象的字典 |
| 保存 HDF5 | using HDF5 h5write(filename, "key", object) |
| 读取 HDF5 | using HDF5 h5read(filename, "key") |
# 自我检查与反射
| 类型 | typeof(name) |
| 类型检查 | isa(name, TypeName) |
| 列出子类型 | subtypes(TypeName) |
| 列出超类型 | supertype(TypeName) |
| 函数方法 | methods(func) |
| 即时编译的字节码 | code_llvm(expr) |
| 汇编代码 | code_native(expr) |
# 赋值与拷贝
赋值与拷贝的详细介绍,请参见赋值与拷贝。
# 变量作用域
变量作用域的详细介绍,请参见变量作用域。
# 运行外部程序
运行外部程序的详细介绍,请参见运行外部程序。
# 常用易混淆函数
常用易混淆函数的详细介绍,请参见常用易混淆函数。
# 命名规范
- Julia 代码风格主要的约定是:尽量避免使用下划线,除非不用就难于理解。
- 变量名小写或使用蛇形命名(snake_case):
somevariable。 - 常数全部大写:
SOMECONSTANT。 - 函数名小写或使用蛇形命名(snake_case):
somefunction。 - 宏小写或使用蛇形命名(snake_case):
@somemacro。 - 类型名用首字母大写的驼峰命名:
SomeType。 - Julia 代码文件以
.jl为后缀。
更详细的代码风格规范请参阅手册:代码风格指南 (opens new window)
# 其他建议
- 编写 类型稳定 (opens new window) 的代码
- 尽可能使用不可变类型
- 大数组用
sizehint!预分配内存 - 用
arr = nothing释放大数组的内存 - 使用列访问数组,因为多维数组总是以列优先的顺序储存
- 预分配储存结果用的数据结构
- 在实时应用中使用
disable_gc()关闭垃圾收集器 - 避免使用关键字参数的 splat 操作符(
...) - 使用会改变参数的 APIs 以避免复制数据结构。(例如:以
!结尾的函数) - 使用逐元素的数组操作,而不是列表推断(list comprehensions)
- 避免在计算密集的循环中使用
try-catch - 避免在收集(collections)中出现
Any - 避免在收集(collections)中使用抽象类型
- 避免在 I/O 中使用字符串插值
- 不像 R, MATLAB 或 Python,在 Julia 中向量化 (opens new window) 并不会提升运行速度
- 避免在运行时使用
eval