Julia 学习笔记——8. 模块与包
前面的几篇基本还是语言语法层面的内容,这一篇开始看 Julia 代码组织和包管理。 需要先说明一下:Julia 的模块系统、项目环境、包管理其实是三个相关但不完全相同的话题: 模块(module):解决名字空间和代码组织问题; 项目环境(environment):解决当前项目依赖哪些包的问题; 包(package):解决代码如何发布、复用、安装的问题。 这几部分在 Julia 里结合得比较紧,但概念上最好还是分开理解。 概述默认读者已经熟悉 Python 和 MATLAB,这里就不再从“什么是模块”这种层面展开,直接讨论 Julia 的实际组织方式。 如果只写单文件脚本,那么模块和包的存在感不会太强;但只要代码规模稍微大一点,就必须开始区分: 哪些东西属于当前模块自己的名字空间; 哪些代码只是拆文件,应该用 include; 哪些依赖来自外部包,应该交给项目环境和 Pkg 管理。 模块基础为什么需要模块如果所有函数、常量、类型都直接定义在全局作用域里,那么项目一大,很快就会出现名称冲突。 Julia 使用 module ... end 来创建模块,例如 12345modu...
Julia 学习笔记——7. 多维数组
数组是 Julia 最核心的主题之一。前面几篇虽然已经零散提过一些数组相关语法,但如果不专门单独整理一篇,很多地方还是会比较乱。 这一篇主要记录: Julia 的数组字面量和创建方式; 索引、切片、视图、形状变换; 常见的数组运算、拼接、遍历和广播; Julia 数组系统里最值得重点记住的设计点。 概述Julia 的 Array 是按列优先(column-major)存储的,这一点和 MATLAB、Fortran 一致,和 Numpy 默认的行优先风格不同。 默认读者已经熟悉 Python/Numpy 或 MATLAB,因此这里不再解释数组的基础概念,直接看 Julia 数组系统里最容易影响实际写法的部分:一维向量和二维矩阵的区分、闭区间切片、默认拷贝而不是默认视图、广播和点语法。 此外 Julia 的数组有几个很鲜明的特点: 真正区分一维向量 Vector 和二维矩阵 Matrix; 索引从 1 开始; 切片范围是闭区间; 普通切片默认会拷贝; 广播和点语法是数组代码的核心写法之一。 数组类型与维度Array{T,N}Julia 的一般数组类型可以写成 Arr...
Julia 学习笔记——6. 函数进阶
前面的函数笔记主要记录函数的基本语法,这一篇继续看 Julia 函数体系里更核心的部分:方法、多重分派、参数化方法、构造函数,以及一些常用的函数式写法。 概述如果说前一篇还只是“怎么把函数写出来”,那么这一篇才真正开始接触 Julia 函数系统最有代表性的部分。 在 Python 中,函数更多只是可调用对象;而在 Julia 中,函数名背后通常对应的是一整组方法实现,调用时再根据参数类型决定走哪一个。 默认读者已经熟悉 Python / MATLAB,因此这里也不再解释高阶函数、匿名函数这些概念本身,直接看 Julia 的实现方式。 函数的参数类型Julia 允许给函数的(一部分或全部)参数加上类型约束,例如 123function add2(x::Int64, y::Int64) return x + yend 正常使用例如 12add2(1, 3) # 4add2(Int64(1), Int64(10)) # 11 注意 Julia 不会对参数进行自动的类型转换,即使转换是安全无损的,例如 1add2(Int32(1), Int32(2)) # error...
Julia 学习笔记——5. 变量作用域与类型系统
Julia 的作用域和类型系统的设计非常规范化。相对于 Python 或 MATLAB,Julia 在这两部分会明显显得更“讲规矩”。 这一篇主要关注两个问题: 变量名在什么地方可见,什么时候会新建局部变量; Julia 的类型系统到底是怎样组织的,平时写代码应该关注到什么程度。 如果只从“写脚本”的角度看,Julia 和 Python 确实有很多相似之处;但一旦开始关心性能、分派和泛型代码,Julia 的作用域和类型系统就会立刻变成核心内容。 变量作用域基本概念Julia 使用词法作用域(lexical scope),也就是一个变量在函数内部能否被访问,取决于函数定义的位置,而不是调用的位置。 例如 1234567x = 10function f() return xendf() # 10 这里 f() 可以访问到外部定义的 x,是因为 x 在 f 定义处可见。 需要注意,Julia 中并不是所有语句都会引入新的作用域: function、let、struct、宏、推导式等会引入新的局部作用域; for、while、try 也会引入局部作用域; if 和 begi...
Julia 学习笔记——4. 函数基础
Julia 中的函数在表面上和 Python 比较接近,都是语言的一等公民:可以赋值给变量、作为参数传递,也可以作为返回值返回。 不过 Julia 的函数体系又远不只是把 def 换成 function 这么简单,语法会比 Python 严格得多。函数返回值、默认参数、关键字参数、元组参数,以及后面会单独展开的多重分派,都是这个语言里非常核心的内容。 函数格式Julia 中最普通的函数定义格式如下: 1234function function_name(parameter_list) function_body return resultend 其中: function 和 end 用来包裹函数定义; 参数列表写在小括号中; 返回值可以显式使用 return,也可以依赖最后一个表达式自动返回。 对于非常简单的函数,也可以使用单行赋值形式,后面会单独讨论。 简单示例简单例子最基本的例子如下 12345function f(x,y) x + yendf(1,2) # 3 和 Python 一样,Julia 的函数不需要额外的“函数句柄”机制,函数对象本身就...
Julia 学习笔记——3. 流程控制
简单过一遍 Julia 中的流程控制,其实和 Python 以及 MATLAB 基本一致。 主要的区别包括: Julia 的条件表达式必须是显式 Bool,不像 Python 和 MATLAB 那样允许各种隐式真值判断; if、begin 这类结构本身都可以作为表达式使用; for 和 while 不只是“执行循环”,还会和局部作用域规则产生联系。 条件语句if 条件语句和其它语言一样,注意其中的条件表达式不需要括号 123456789101112x = 3;y = 2;if x < y println("x is less than y")elseif x > y println("x is greater than y")else println("x is equal to y")end# x is greater than y C/C++ 使用 else if;Python 使用 elif;MATLAB 使用 elseif,Julia 也使用 elseif。 Ju...
Julia 学习笔记——2. 字面量、基本数据类型与运算符
这一篇主要整理 Julia 中最常见的基本数据类型,以及与这些类型直接相关的运算符语义。 整数与浮点数整数Julia 支持固定长度的整数类型,包括有符号和无符号的版本,例如 Int8,Int32,UInt32 等。对于十进制整数字面量,在64位系统中默认使用 Int64 类型整数。可以使用 typeof() 查看字面量的类型(Python 对应的函数为 type()) 1typeof(1) # Int64 类型也是可以作为参数进行运算的,例如使用 typemin() 和 typemax() 直接查看类型的最大最小值 123typemin(Int32) # -2147483648typemin(Int64) # -9223372036854775808typemax(Int64) # 9223372036854775807 注意: Julia 支持使用 _ 作为数字的分隔符,可以提高可读性,例如10_000。 由于整数的位数是固定的,在运算中自然也存在溢出问题,与其它语言中的处理类似,不再赘述。 Python 的整数是无限精度的,Julia 也提供了 BigInt 类型以支持...
Julia 学习笔记——1. 概述
这套笔记主要记录从 Python 和 MATLAB 迁移到 Julia 时需要关注的内容。 需要说明的是,这个系列不是零基础入门教程,而是围绕具体主题展开的整理型笔记,因此不同篇之间会有交叉,不严格追求循序渐进。 除非特别说明,下文默认以 Julia 最新版本 1.12 为准。 概述Julia 对我而言更像是一种值得单独学习的科学计算语言,而不是 Python 或 MATLAB 的某个附属工具。 它最吸引人的地方很明确: 写法上尽量保留动态语言和科学计算语言的易用性; 执行上尽量逼近静态编译语言的性能; 语言本身直接把数组、线性代数、泛型函数和多重分派当作核心设计对象。 介绍Julia 是一门面向科学计算的新兴语言,它的设计目标非常明确:希望同时拥有 Python/MATLAB 的易用性,以及 C/C++/Fortran 一类语言的运算效率。 Julia有很多与众不同的特点: 从设计定位来说,Julia 具有后发优势,是为 JIT 编译而量身定制的语言,目的是解决现在泾渭分明的动态语言和静态语言之间,优劣不能兼顾,因而必须组合使用的双语言问题。...
Numpy 学习笔记——3. 数组的线性代数运算
NumPy 提供了比较丰富的线性代数运算功能,主要集中在 numpy.linalg 模块中,下面罗列一些基础的操作,主要针对一维和二维数组,对于其它高维数组的语义可能不太符合直觉。 重要函数点积 np.dotnp.dot() 函数可以计算两个向量的点积,最基本的用法例如 1234a = np.array([1, 2, 3], dtype=np.float64)b = np.array([4, 5, 6], dtype=np.float64)np.dot(a, b) # np.float64(32.0)# 1*4 + 2*5 + 3*6 = 32 np.dot(a, b)的完整语义需要根据两个数组的维度来区分: 第一类情况: 如果其中一个是标量,那么就是进行(逐元素的)乘法,等效于 np.multiply(a, b) 或 a * b。 第二类情况: 如果 a 是 $N$ 维数组,b 是一维数组,那么会沿着两者的最后一个轴求和,得到 $N-1$ 维数组。 (特例,标准情况)如果两者都是尺寸为 (m,) 的一维数组,那么就是标准的向量内积,结果是一个标量。 (特例)如果 a ...
Numpy 学习笔记——2. 数组的变换和基本运算
数组的拷贝Python 本身有深拷贝和浅拷贝等复杂的概念,对于多层列表等数据结构,对应的行为差异非常明显。但是 Numpy 的 ndarray 数组无论形状如何,在内存中始终是连续存储的,高维数组并不是数组的数组,因此并没有深拷贝和浅拷贝的明显差异。 和其它 Python 数据一样,在函数传参过程中对 ndarray 数组的传递并不会触发任何的拷贝行为。 为了获取一个 ndarray 数组的完整拷贝,可以使用 ndarray.copy() 方法,例如 12345a = np.array([1, 2, 3])b = a.copy()b[0] = 100a # array([1, 2, 3]) 上述测试代码表明,拷贝得到的是完全独立的数组,修改不会相互影响。 也可以使用numpy.copy()函数,例如 12a = np.array([1, 2, 3])b = np.copy(a) ndarray.copy() 方法和 numpy.copy() 函数的行为是基本一致的,都是获取原数组的拷贝,但是两者关于内存排布参数的默认值有细微差异,可以参考具体文档。 数组的视图为了优化计算效...
