
通常,在解决分析和数据准备问题时,会编写一次性脚本,而根本没有提供其支持和开发。 这种方法有权利存在,特别是在学生社区中。 但是,如果有不止一个人使用该代码,或者如果该代码需要维护一个以上的工作日,则不能选择以文件堆的形式组织工作。
因此,今天我们将讨论一个重要的主题,例如以Julia语言从头开始创建项目,如何填充它以及存在哪些技术工具来支持开发。
专案
如前所述,一次性脚本或Jupyter笔记本有权在一个人的桌面上存在,尤其是在将编程语言用作高级计算器时。 但是这种方法完全不适用于应该开发并运行多年的项目。 当然,Julia作为技术平台,拥有的工具可为开发人员提供这种机会。
对于初学者来说,有几点要点。 Julia有一个用于包管理的Pkg模块。 任何Julia库都是一个模块。 如果该模块未包含在Julia基本工具包中,它将作为单独的包装发出。 对于每个软件包,都有一个项目文件Project.toml
,其中包含对该项目及其对其他软件包的依赖性的描述。 还有另一个文件Manifest.toml
,与Project.toml
不同,该文件是自动生成的,并且包含所有必需依赖项的列表以及程序包版本号。 Toml文件格式是Tom的“显而易见的最小语言” 。
软件包命名规则
根据文档 ,程序包名称可能包含拉丁字母和数字。 选择此名称的方式应使大多数Julia用户(而不是狭窄领域的专家)都清楚。
- 应避免在不同区域使用不同的术语和有争议的收缩。
- 请勿在包名中使用单词Julia。
- 提供某些功能以及在其中声明的新类型的程序包应以复数形式命名。
◦DataFrames提供一个DataFrame类型。
◦BloomFilters提供BloomFilter类型。
◦同时,JuliaParser不提供新类型,新功能是JuliaParser.parse()函数。 - 使用全名的透明度和可理解性应优先于缩写。 与
RndMat
或RMT
相比, RandomMatrices
歧义性RndMat
。 - 名称可以对应于主题区域的细节。 范例:
◦Julia没有自己的图形绘图包。 而是有Gadfly
, PyPlot
, Winston
等软件包,每个软件包都实现自己的使用方法和方法。
◦同时, SortingAlgorithms
提供了使用排序算法的完整接口。 - 如果程序包是某些第三方库的包装,它们将保留该库的名称。 范例:
CPLEX.jl
是CPLEX
库的包装。
MATLAB.jl
提供了用于从Julia激活MATLAB
的接口。
同时,git存储库的名称通常带有后缀“ .jl”。
包生成
创建包的最简单方法是使用Julia内置的生成器生成包。 为此,在控制台中,您需要转到应在其中创建软件包的目录,然后运行julia并将其置于软件包管理模式中:
julia> ]
最后一步是通过指定我们要给软件包的名称来启动软件包生成器。
(v1.2) pkg> generate HelloWorld
结果,新目录出现在当前目录中,与软件包名称相对应,可以使用tree
命令(如果已安装)查看其组成:
shell> cd HelloWorld shell> tree . . ├── Project.toml └── src └── HelloWorld.jl 1 directory, 2 files
在这种情况下,对于一个设计良好的项目,我们看到的文件数量很少但不足,有关更多详细信息,请参见https://julialang.imtqy.com/Pkg.jl/v1/creating-packages/ 。
创建包的另一种方法是使用PkgTemplates.jl生成器。 与内置生成器不同,它使您可以立即生成一组完整的服务文件来为软件包提供服务。 唯一的缺点是它本身必须作为软件包安装。
在其帮助下创建程序包的过程如下。 我们连接一个包装:
julia> using PkgTemplates
我们创建一个模板,其中包括作者列表,许可证,Julia的要求,持续集成系统的插件列表(来自PkgTemplates
文档的PkgTemplates
):
julia> t = Template(; user="myusername",
我们得到了模板:
Template: → User: myusername → Host: github.com → License: ISC (Chris de Graaf, Invenia Technical Computing Corporation 2018) → Package directory: ~/code → Minimum Julia version: v0.7 → SSH remote: No → Commit Manifest.toml: No → Plugins: • AppVeyor: → Config file: Default → 0 gitignore entries • Codecov: → Config file: None → 3 gitignore entries: "*.jl.cov", "*.jl.*.cov", "*.jl.mem" • Coveralls: → Config file: None → 3 gitignore entries: "*.jl.cov", "*.jl.*.cov", "*.jl.mem" • GitHubPages: → 0 asset files → 2 gitignore entries: "/docs/build/", "/docs/site/" • TravisCI: → Config file: Default → 0 gitignore entries
现在,使用此模板,我们只需指定包名称即可创建包:
julia> generate(t, "MyPkg1")
在最低版本中,模板可能如下所示:
julia> t = Template(; user="rssdev10", authors=["rssdev10"]) Template: → User: rssdev10 → Host: github.com → License: MIT (rssdev10 2019) → Package directory: ~/.julia/dev → Minimum Julia version: v1.0 → SSH remote: No → Add packages to main environment: Yes → Commit Manifest.toml: No → Plugins: None
如果我们从此模板创建一个名为MyPkg2的包:
julia> generate(t, "MyPkg2")
然后我们可以直接从Julia中检查结果:
julia> run(`git -C $(joinpath(t.dir, "MyPkg2")) ls-files`); .appveyor.yml .gitignore .travis.yml LICENSE Project.toml README.md REQUIRE docs/Manifest.toml docs/Project.toml docs/make.jl docs/src/index.md src/MyPkg2.jl test/runtests.jl
应注意以下字段:
user="myusername"
,是git注册条目的名称。dir
软件包代码放置的目录。 如果未指定,它将在开发目录~/.julia/dev
。 此外,根据unix文件系统的规则, ~/.julia
是隐藏的。
创建项目后,将生成足够的文件集并创建一个git存储库。 此外,所有生成的文件将自动添加到该存储库。
项目中的典型文件位置
我们将从https://en.wikibooks.org/wiki/Introducing_Julia/Modules_and_packages借用具有典型文件排列及其内容的图片,但是我们将对其进行扩展:
Calculus.jl/
我们添加deps
目录可能包含正确组装软件包所必需的文件。 例如, deps/build.jl
是在安装软件包时自动运行的脚本。 该脚本可以包含用于数据准备(下载数据集或执行预处理)的任何代码或工作所需的其他程序。
应该注意的是,一个项目中只能有一个主模块。 也就是说,在上面的示例中- Calculus
。 但是,在同一示例中,存在通过include
连接的Derivative
嵌套模块。 注意这一点。 include
将文件包含为文本,而不是模块,这是using
或import
发生的。 最后两个功能不仅包括模块,而且还强制Julia将其编译为单独的实体。 此外,Julia将尝试在依赖项包中找到该模块,并发出警告,指出Project.toml
缺少该模块。 因此,如果我们的任务是对函数进行分层访问,并通过名称空间对它们进行分隔,那么我们将通过include
包含文件,并通过一个点激活该模块,以指示其本地关联性。 那就是:
module Calculus include("derivative.jl") import .Derivative ... end
从Derivative
模块导出的derivative
函数将通过Calculus.Derivative.derivative()
提供给我们
Project.toml项目文件
项目文件是一个文本文件。 它的主要部分在描述https://julialang.imtqy.com/Pkg.jl/v1/toml-files/中公开
生成文件后,其中所有必需的字段都已经存在。 但是,您可能需要更改部分说明,更改程序包的组成,版本和不同操作系统或配置的特定依赖关系。
主要字段是:
name = "Example" uuid = "7876af07-990d-54b4-ab0e-23690620f79a" version = "1.2.5"
name-根据命名规则选择的程序包的名称。 uuid
是一个统一标识符,可以由程序包生成器或任何其他uuid
生成器生成。 version
-软件包版本号,格式为三个小数点,中间用句点分隔。 这符合语义版本2.0.0的格式。 在声明的1.0.0版之前,可以在程序界面中进行任何更改。 发布此版本后,程序包所有者必须遵守兼容性规则。 任何兼容的更改都应在次要编号中反映出来(右)。 不兼容的更改必须伴随高编号的更改。 自然,没有对版本控制规则的自动控制,但是不遵守该规则只会导致以下事实:程序包的用户将开始大量停止使用并迁移到其作者遵守此规则的程序包。
所有软件包依赖项都在[deps]
部分中提供。
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
本节包含我们程序包的直接依赖项列表。 级联依赖关系反映在Manifest.toml
文件中,该文件会在项目目录中自动生成。 所有依赖项都由=
对表示。 并且,通常,这部分没有手。 为此,提供了Pkg
软件包的功能。 而且,通常,这是通过REPL
完成的,将其切换到程序包管理模式- ]
。 下一步-操作add
, rm
, st
等,但始终在当前包的上下文中。 如果没有,则需要执行activate .
。
Manifest.toml
可以保存在git
版本控制系统中。 这种具有两个文件的方法使您可以在软件产品测试期间在依赖关系树中严格修复软件包,此后可以确保如果我们的软件包部署在新位置,则相同版本的第三方软件包将在同一位置重复进行。 或者相反,如果没有Manifest.toml
将有机会使用满足基本条件的任何可用版本。
[compat]
部分允许您指定我们需要的软件包的特定版本。
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" [compat] Example = "1.2" julia = "1.1"
包由先前在[compat]
节中使用的名称标识。 julia
表示Julia本身的版本。
指定版本时,适用https://julialang.imtqy.com/Pkg.jl/dev/compatibility/中列出的规则。 但是,在语义版本控制中指定了相同的规则。
有几个版本控制规则。 例如:
[compat] Example = "1.2, 2"
表示[1.2.0, 3.0.0)
范围内的任何版本均适用,但不包括3.0.0
。 这与一个更简单的规则完全一致:
[compat] Example = "1.2"
而且,仅指定版本号是"^1.2"
的缩写形式。 其应用示例如下:
[compat] PkgA = "^1.2.3" # [1.2.3, 2.0.0) PkgB = "^1.2" # [1.2.0, 2.0.0) PkgC = "^1" # [1.0.0, 2.0.0) PkgD = "^0.2.3" # [0.2.3, 0.3.0) PkgE = "^0.0.3" # [0.0.3, 0.0.4) PkgF = "^0.0" # [0.0.0, 0.1.0) PkgG = "^0" # [0.0.0, 1.0.0)
如果我们需要指定更严格的限制,则必须使用带有代字号的表格。
[compat] PkgA = "~1.2.3" # [1.2.3, 1.3.0) PkgB = "~1.2" # [1.2.0, 1.3.0) PkgC = "~1" # [1.0.0, 2.0.0) PkgD = "~0.2.3" # [0.2.3, 0.3.0) PkgE = "~0.0.3" # [0.0.3, 0.0.4) PkgF = "~0.0" # [0.0.0, 0.1.0) PkgG = "~0" # [0.0.0, 1.0.0)
好吧,当然,可以提供等号/不等式的指示:
[compat] PkgA = ">= 1.2.3" # [1.2.3, ∞) PkgB = "≥ 1.2.3" # [1.2.3, ∞) PkgC = "= 1.2.3" # [1.2.3, 1.2.3] PkgD = "< 1.2.3" # [0.0.0, 1.2.2]
可以在[targets]
部分中为依赖项指定几个选项。 传统上,在1.2版之前的Julia中,它用于指定使用软件包和运行测试的依赖关系。 为此,在[extras]
部分中指定了其他软件包,并在[targets]
中列出了具有软件包名称的目标配置。
[extras] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Markdown", "Test"]
从Julia 1.2开始,建议您仅为test/Project.toml
添加一个单独的项目文件。
其他依赖
可以通过deps/build.jl
连接其他依赖关系,但是,Julia项目结构中提供了Artifacts.toml
文件。 Pkg.Artifacts
项目管理Pkg.Artifacts
提供了用于自动加载其他依赖项的功能。 此类文件的示例:
# Example Artifacts.toml file [socrates] git-tree-sha1 = "43563e7631a7eafae1f9f8d9d332e3de44ad7239" lazy = true [[socrates.download]] url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.gz" sha256 = "e65d2f13f2085f2c279830e863292312a72930fee5ba3c792b14c33ce5c5cc58" [[socrates.download]] url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.bz2" sha256 = "13fc17b97be41763b02cbb80e9d048302cec3bd3d446c2ed6e8210bddcd3ac76" [[c_simple]] arch = "x86_64" git-tree-sha1 = "4bdf4556050cb55b67b211d4e78009aaec378cbc" libc = "musl" os = "linux" [[c_simple.download]] sha256 = "411d6befd49942826ea1e59041bddf7dbb72fb871bb03165bf4e164b13ab5130" url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-linux-musl.tar.gz" [[c_simple]] arch = "x86_64" git-tree-sha1 = "51264dbc770cd38aeb15f93536c29dc38c727e4c" os = "macos" [[c_simple.download]] sha256 = "6c17d9e1dc95ba86ec7462637824afe7a25b8509cc51453f0eb86eda03ed4dc3" url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-apple-darwin14.tar.gz" [processed_output] git-tree-sha1 = "1c223e66f1a8e0fae1f9fcb9d3f2e3ce48a82200"
由于进一步的描述取决于特定的用例,因此我们将不作更详细的介绍。 库函数artifact_hash
, download
, create_artifact
, bind_artifact
。 有关更多详细信息,请参见文档https://julialang.imtqy.com/Pkg.jl/dev/artifacts/ 。
主要代码实现与调试
当然,我们在创建程序包时显式或隐式指定开发目录。 但是,如有必要,我们可以更改它。 如果软件包是由PkgTemplates
使用默认参数生成的,请在~/.julia/dev
查找它。 尽管目录是隐藏的,但仍可以通过文件导航器中的直接链接进行过渡。 例如,对于Finder中的MacOS,可通过按Command + Shift + G来完成。 如果该软件包是在任何其他目录中创建的,则只需在文本编辑器中将其打开。 使用Julia代码的最佳编辑器是Atom和uber-juno
插件支持的所有功能。 在这种情况下,您将获得具有代码自动格式设置的文本编辑器,用于交互式代码执行的REPL控制台,仅执行所选代码片段并查看结果(包括显示图形)的功能。 还有一步一步的调试器。 虽然,我们必须承认目前它非常慢,所以当前的调试模式-首先,我们认为我们要检查并放置调试输出,然后运行测试进行测试。
建议您查看动态编程语言的常见设计模式 。 此外,还有“使用Julia 1.0的动手设计模式。TomKwong”一书及其示例代码 。 在实施程序时,您应该考虑《 Julia Style Guide》编程风格的建议。
在调试的复杂性中,可以注意到Revise.jl
包。 只能在交互式模式下在文件.julia/config/startup.jl
设置其激活,在该模式下,可以从Atom编辑器运行REPL。 修订允许您在不重新启动REPL会话的情况下编辑包中的功能代码,并且在我们的测试中每次使用/ import的运行都会启用这些更新。
为了有效进行开发,建议并行开发主要代码并进行测试以对其进行测试。 这使您仅可以实现真正需要的功能,因为否则,在测试中显然会存在不必要的功能。 因此,必须将它们删除。 本质上,Julia没有提供任何有关开发原理的特定内容。 但是,此处重点强调通过单元测试进行开发,因为Julia编译代码的速度很慢,并且在逐步调试模式下,性能会大大降低。 也就是说,这取决于测试的开发,测试的组织,正在开发的软件包被调试和验证的速度。
测验
典型的测试位置是测试目录。 test/runtests.jl
文件是所有测试的起点。
关于上述示例,文件的典型形式为:
using Calculus
建议根据测试功能的分组来开发特定测试的文件。 例如,在上述Calculus
模块中,可能会存在用于计算导数,积分等的各种算法,用位于不同文件中的各种测试对它们进行测试是合乎逻辑的。
对于单元测试,Julia从基础库集中提供了“ Test
模块。 @test
宏在此模块中定义,其目的是验证指定语句的正确性。 范例:
julia> @test true Test Passed julia> @test [1, 2] + [2, 1] == [3, 3] Test Passed julia> @test π ≈ 3.14 atol=0.01 Test Passed
注意近似比较运算符≈
的完整形式。
检查异常选择的语句是@test_throws
。 示例-创建一个数组并访问其后的索引:
julia> @test_throws BoundsError [1, 2, 3][4] Test Passed Thrown: BoundsError
一个有用的构造是@testset
。 它允许您将单个语句分组为逻辑连接的测试。 例如:
julia> @testset "trigonometric identities" begin θ = 2/3*π @test sin(-θ) ≈ -sin(θ) @test cos(-θ) ≈ cos(θ) @test sin(2θ) ≈ 2*sin(θ)*cos(θ) @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2 end; Test Summary: | Pass Total trigonometric identities | 4 4
对于通过@testset
声明的每个集合,将形成其自己通过的测试表。 测试套件可以嵌套。 如果测试成功通过,则发布汇总表;如果测试失败,则发布汇总表-对于每组测试,将发布其自己的统计信息。
julia> @testset "Foo Tests" begin @testset "Animals" begin @testset "Felines" begin @test foo("cat") == 9 end @testset "Canines" begin @test foo("dog") == 9 end end @testset "Arrays" begin @test foo(zeros(2)) == 4 @test foo(fill(1.0, 4)) == 15 end end Arrays: Test Failed Expression: foo(fill(1.0, 4)) == 15 Evaluated: 16 == 15 [...] Test Summary: | Pass Fail Total Foo Tests | 3 1 4 Animals | 2 2 Arrays | 1 1 2 ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.
@test_broken
, @test_skip
.
. julia
:
--code-coverage={none|user|all}, --code-coverage Count executions of source lines (omitting setting is equivalent to "user") --code-coverage=tracefile.info Append coverage information to the LCOV tracefile (filename supports format tokens). --track-allocation={none|user|all}, --track-allocation Count bytes allocated by each source line (omitting setting is equivalent to "user")
code-coverage
— . ( ), . , . .cov
. .
:
- function vectorize(str::String) 96 tokens = str |> tokenizer |> wordpiece 48 text = ["[CLS]"; tokens; "[SEP]"] 48 token_indices = vocab(text) 48 segment_indices = [fill(1, length(tokens) + 2);] 48 sample = (tok = token_indices, segment = segment_indices) 48 bert_embedding = sample |> bert_model.embed 48 collect(sum(bert_embedding, dims=2)[:]) - end
track-allocation
— . , , , , .mem
.
:
- function vectorize(str::String) 0 tokens = str |> tokenizer |> wordpiece 6766790 text = ["[CLS]"; tokens; "[SEP]"] 0 token_indices = vocab(text) 11392 segment_indices = [fill(1, length(tokens) + 2);] 1536 sample = (tok = token_indices, segment = segment_indices) 0 bert_embedding = sample |> bert_model.embed 170496 collect(sum(bert_embedding, dims=2)[:]) - end
, . , , , , . , . .
— :
julia --project=@. --code-coverage --track-allocation test/runtests.jl
— Profile.jl
@profile
. https://julialang.org/blog/2019/09/profilers . @noinline
, . , fib
fib_r
.
julia> @noinline function fib(n) return n > 1 ? fib_r(n - 1) + fib_r(n - 2) : 1 end julia> @noinline fib_r(n) = fib(n) julia> @time fib(40) 0.738735 seconds (3.16 k allocations: 176.626 KiB) 165580141 julia> using Profile julia> @profile fib(40) 165580141 julia> Profile.print(format=:flat, sortedby=:count) Count File Line Function 12 int.jl 52 - 14 int.jl 53 + 212 boot.jl 330 eval 5717 REPL[2] 1 fib_r 6028 REPL[1] 2 fib julia> count(==(0), Profile.fetch()) 585
@profile fib(40)
. Profile.print(format=:flat, sortedby=:count)
. , , , fib_r
fib
, . , :
julia> Profile.print(format=:tree) 260 REPL[1]:2; fib(::Int64) 112 REPL[1]:1; fib_r(::Int64) 212 task.jl:333; REPL.var"##26#27" 212 REPL.jl:118; macro expansion 212 REPL.jl:86; eval_user_input 212 boot.jl:330; eval ╎ 210 REPL[1]:2; fib ╎ 210 REPL[1]:1; fib_r ╎ 210 REPL[1]:2; fib ╎ 210 REPL[1]:1; fib_r ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ...
. PProf.jl, .

. https://github.com/vchuravy/PProf.jl .
doc
. https://habr.com/ru/post/439442/
, , Julia .
Project.toml
, . , , - , , .
, , . , — . :
, , . , , git clone, . PackageCompiler.jl
. , , - .
C
- , , ( - , ), deps, deps/build.jl
. . , , , . , , , , . , , build.jl
, :
. julia --project=@.
Julia Project.toml
. , — build.jl
, executable
. , julia --project=@. build.jl
.
Pkg.activate(".")
( Project.toml
).
Pkg.build()
, C-, . deps/build.jl
, .
Pkg.test()
. , -, , . -, , . coverage=true
. , . build.jl
.
, . , PkgTempletes
. — Gitlab CI, Travis CI, GitHub , .
结论
, , Julia. , , . , — , -, , . , .
参考文献