Engee 文档

执行的顺序

Julia有各种用于控制执行顺序的结构。

前五个执行顺序控制机制对于高级编程语言来说是典型的,不像任务('Task'),其提供非本地执行顺序,允许在临时挂起的计算之间切换。 这是一个非常有用的功能。:异常处理和协同多任务都是在Julia中使用tasks实现的。 在日常编程中,不需要直接使用任务,但它们显着简化了一些问题的解决方案。

复合表达式

有时,使用一个表达式很方便,其中按顺序计算多个子表达式,并返回最后一个子表达式的结果。 要做到这一点,Julia有两个构造:begin`块和通过;`链。 两个复合表达式构造的值都是最后一个子表达式的结果。 下面是"开始"块的一个例子:

julia> z = begin
           x = 1
           y = 2
           x + y
       end
3

由于这些表达式非常简单,它们可以很容易地写在一行中,并且使用`;`的链语法在这里会很方便。:

julia> z = (x = 1; y = 2; x + y)
3

此语法对于简洁的函数单行定义特别有用,这在本章中有描述 功能。 虽然’begin’块通常是多行的,并且通过';'的链是单行的,但这不是一个严格的要求。:

julia> begin x = 1; y = 2; x + y end
3

julia> (x = 1;
        y = 2;
        x + y)
3

条件计算

条件计算允许您根据逻辑表达式的值计算或不计算代码的部分。 条件构造`if'-'elseif'-'else’的语法排列如下。

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<y’具有值’true`,则对相应的块进行评估;否则,对条件表达式`x>y`进行评估,如果为`true',则对相应的块进行评估。 如果表达式都不为true,则计算’else’块。 以下是它在实践中的工作原理。

julia> function test(x, y)
           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
       end
test (generic function with 1 method)

julia> test(1, 2)
x is less than y

julia> test(2, 1)
x is greater than y

julia> test(1, 1)
x is equal to y

"Elseif"和"else"块是可选的。 可以有任意数量的"elseif"块。 计算`if`-elseif-else`构造中的条件表达式,直到为其中一个获得值`true,然后计算相应的块。 后续条件表达式或块不会被计算。

'If’块是"开放的",即它们不形成局部区域。 这意味着在`if`表达式中定义的新变量可以在`if`块之后使用,即使它们之前没有定义。 因此,上面的"测试"函数可以定义为:

julia> function test(x,y)
           if x < y
               relation = "less than"
           elseif x == y
               relation = "equal to"
           else
               relation = "greater than"
           end
           println("x is ", relation, " y.")
       end
test (generic function with 1 method)

julia> test(2, 1)
x is greater than y.

'Relation’变量在`if`块中声明,但在它之外使用。 但是,使用此功能时,必须为每个可能的代码执行路径确定变量的值。 如果对上述函数进行以下更改,则在执行过程中会发生错误:

julia> function test(x,y)
           if x < y
               relation = "less than"
           elseif x == y
               relation = "equal to"
           end
           println("x is ", relation, " y.")
       end
test (generic function with 1 method)

julia> test(1,2)
x is less than y.

julia> test(2,1)
ERROR: UndefVarError: `relation` not defined in local scope
Stacktrace:
 [1] test(::Int64, ::Int64) at ./none:7

'If’块也返回一个值,从许多其他语言的经验来看,这似乎是不寻常的。 这只是选定分支中最后执行的语句返回的值。

julia> x = 3
3

julia> if x > 0
           "positive!"
       else
           "negative..."
       end
"positive!"

请注意,非常短(单行)的条件语句通常使用速记计算方案在Julia中表示,这将在下一节中描述。

与C,MATLAB,Perl,Python和Ruby不同,但就像在Java和许多其他更强类型的语言中一样,如果条件表达式返回"true"或"false"以外的任何内容,则会发生错误。:

julia> if 1
           println("true")
       end
ERROR: TypeError: non-boolean (Int64) used in boolean context

错误消息指示条件表达式的类型不正确。: `Int64'而不是必需的 'Bool'

所谓的"三元运算符"`?:`与`if`-elseif-`else`的语法密切相关,但是当根据条件需要选择其中一个表达式的值而不是执行代码块时使用它。 之所以如此,是因为在大多数语言中,它是唯一接受三个操作数的运算符。:

a ? b : c

"A"之前的表达?'是条件的表达式。 如果条件’a`为’true`,则表达式`b`在`:`之前求值,如果它等于’false`,则表达式`c`在`:'之后求值。 注意’周围的空格?:'是必需的:表达式’a?b:c’不是有效的三元表达式(但是,在’之后?`,并且在':'之后,让我们假设行首的字符)。

理解这一点的最简单方法是使用一个例子。 在前面的示例中,'println’调用在所有三个分支中执行。:唯一的选择是哪个文字字符串显示在屏幕上。 使用三元运算符,这可以写得更简洁。 为了清楚起见,让我们先尝试从两个选项中进行选择。:

julia> x = 1; y = 2;

julia> println(x < y ? "less than" : "not less than")
less than

julia> x = 1; y = 0;

julia> println(x < y ? "less than" : "not less than")
not less than

如果表达式’x<y’为true,则整个三元表达式的结果为字符串`"小于"`;否则,结果为字符串`"不小于"'。 要从原始示例中选择三个选项来重现这种情况,您需要构建一个三元运算符链。:

julia> test(x, y) = println(x < y ? "x is less than y"    :
                            x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)

朱莉娅>测试(1,2)
x小于y

朱莉娅>测试(2,1)
x大于y

朱莉娅>测试(1,1)
x等于y

为了简化链的构造,操作员从右到左链接。

需要注意的是,就像`if`-elseif-else’的情况一样,只有当条件表达式分别返回`true`或`false`时,才会计算:`之前和之后的表达式。:

julia> v(x) = (println(x); x)
v (generic function with 1 method)

julia> 1 < 2 ? v("yes") : v(no)
yes
"yes"

julia> 1 > 2 ? v("yes") : v(no)
no
no

根据缩写方案计算

Julia中的运算符`&&`和`||对应于逻辑运算AND和OR和通常用于此目的。 但是,它们还有一个属性-根据_conducted scheme_进行计算的能力:第二个参数可能无法计算,如下所述。 (也有按位运算符'&'和|',它们可以用作逻辑运算符和和或不根据缩写方案进行计算。 但是,请记住,在计算顺序中,'&'和||具有比&&`和`|/'更高的优先级。)

根据缩写方案的计算与条件计算非常相似。 这种方案对于具有逻辑运算符`&&`和`||的大多数命令式编程语言来说是典型的:在一系列相关的逻辑表达式中,仅计算确定整个链的最终逻辑值所需的最小 在某些语言(如Python)中,这些运算符被称为`and(&&)和`or`(||)。 即表示如下。

  • 在表达式’a&&b`中,只有当`a`为`true`时才计算子表达式’b'。

  • 在表达式’a`/b`中,只有当`a`为`false`时才计算子表达式’b'。

原因是,如果`a`是`false`,那么`a&&b`将具有值`false',而不管`b`的值如何。 同样,如果`a`为`true`,则`a||b’将具有值true,而不考虑`b`的值。 运算符'&&'和|||从左到右链接,但&&`优先于`//`。 这种行为可以在实践中进行测试。:

julia> t(x) = (println(x); true)
t (generic function with 1 method)

julia> f(x) = (println(x); false)
f (generic function with 1 method)

朱莉娅>t(1)&&t(2)
1
2
真的

朱莉娅>t(1)&&f(2)
1
2
错误

julia>f(1)&&t(2)
1
错误

julia>f(1)&&f(2)
1
错误

朱莉娅>t(1)//t(2)
1
真的

朱莉娅>t(1)//f(2)
1
真的

julia>f(1)//t(2)
1
2
真的

julia>f(1|//f(2)
1
2
错误

您可以自己尝试`&&`和`||`运算符的不同组合,以了解绑定如何发生和应用优先级。

这个方案经常在Julia中用作非常短的`if`语句的替代方案。 而不是`if<cond><statement>end`,你可以写`<cond>&&<statement>`(可以解释为然后)。 同样,而不是’如果! <语句>结束’你可以写`|| < statement>`(可以解释为或相同的)。

例如,用于计算阶乘的递归子程序可以定义为:

julia> function fact(n::Int)
           n >= 0 || error("n must be non-negative")
           n == 0 && return 1
           n * fact(n-1)
       end
fact (generic function with 1 method)

julia> fact(5)
120

julia> fact(0)
1

julia> fact(-1)
ERROR: n must be non-negative
Stacktrace:
 [1] error at ./error.jl:33 [inlined]
 [2] fact(::Int64) at ./none:2
 [3] top-level scope

没有缩短计算方案的逻辑运算可以使用按位逻辑运算符执行,这在章节中进行了描述 数学运算和初等函数'&`和|'。 这些是支持中缀运算符语法的普通函数,但总是计算它们的参数。:

julia> f(1) & t(2)
1
2
false

julia> t(1) | t(2)
1
2
true

就像`if`,`elseif`或三元运算符中使用的条件表达式一样,操作数'&&'和|//`必须具有布尔值(`true`或`false')。 在条件链中除了最后一个元素之外的任何地方使用布尔值以外的值都会导致错误。:

julia> 1 && true
ERROR: TypeError: non-boolean (Int64) used in boolean context

相反,任何类型的表达式都可以在条件链的末尾使用。 它将根据以前的条件计算并返回。:

julia> true && (x = (1, 2, 3))
(1, 2, 3)

julia> false && (x = (1, 2, 3))
false

重复计算:循环

组织表达式的重复计算有两种结构’while’循环和`for’循环。 下面是一个`while’循环的例子:

julia> i = 1;

julia> while i <= 3
           println(i)
           global i += 1
       end
1
2
3

在’while’循环中,计算条件表达式(在这种情况下,'i<=3),并且,只要它是`true,就会一遍又一遍地计算`while’循环的主体。 如果条件表达式在第一次求值时为"false",则永远不会求值"while"循环的主体。

"For"循环使记录重复计算变得更容易。 从上面的’while’循环的例子中可以看出,通常需要在循环中保留一个计数器,为此使用`for`循环更方便:

julia> for i = 1:3
           println(i)
       end
1
2
3

这里`1:3’是一个对象 `range',表示数字1,2,3的序列。 'For’循环遍历这些值,依次将它们分配给变量’i'。 一般来说,'for’构造可以迭代任何"可迭代"对象(或"容器"),从像`1:3`或`1:3:13’这样的范围('StepRange'代表每三个整数1,4,7,。..,13)多达更多功能的容器,如数组,包括 用户定义的迭代器,或外部包。 对于范围以外的容器,关键字'∈'或'=`通常用作`in’字符的完全等效替代,这使得代码更清晰。:

julia> for i in [1,4,0]
           println(i)
       end
1
4
0

julia> for s ∈ ["foo","bar","baz"]
           println(s)
       end
foo
bar
baz

在手册的以下部分中,将介绍和讨论各种类型的可迭代容器(例如,参见章节 多维数组)。

'For`和’while’循环之间的一个相当重要的区别是这个变量的范围。 一个新的迭代变量总是输入到’for’循环的主体中,而不管外部作用域中是否有一个具有该名称的变量。 因此,一方面,变量’i’不需要在循环之前声明。 另一方面,它将在循环外部不可访问,并且不会影响具有相同名称的外部变量。 要检查,您将需要一个新的交互式会话或具有不同名称的变量。:

julia> for j = 1:3
           println(j)
       end
1
2
3

julia> j
ERROR: UndefVarError: `j` not defined in `Main`
julia> j = 0;

julia> for j = 1:3
           println(j)
       end
1
2
3

julia> j
0

使用’for outer’来更改此行为并重新使用现有的局部变量。

一个变量的作用域是什么的详解 `outer'以及它在Julia中的工作原理,请参阅章节 可变区域

有时需要在条件设置为false之前终止`while`循环的执行,或者在迭代对象结束之前中止`for`循环的执行。 为此,您可以使用关键字’break`:

julia> i = 1;

julia> while true
           println(i)
           if i >= 3
               break
           end
           global i += 1
       end
1
2
3

julia> for j = 1:1000
           println(j)
           if j >= 3
               break
           end
       end
1
2
3

如果没有’break’关键字,上面的`while`循环的执行将永远不会自行完成,而在`for`循环中,迭代将发生多达1000次。 由于"中断",这两个周期的退出都过早发生。

在其他情况下,中止迭代并立即进入下一个迭代可能会很有用。 关键字’继续’用于此。:

julia> for i = 1:10
           if i % 3 != 0
               continue
           end
           println(i)
       end
3
6
9

这是一个有点做作的例子,因为通过反转条件并将`println`调用放在`if`块中,可以以更明显的方式获得相同的结果。 在实践中,关键字’continue’后面跟着更复杂的计算,并且通常有几个’continue’调用点。

几个嵌套的"for"循环可以组合成一个外部循环,以获得迭代对象的笛卡尔积。:

julia> for i = 1:2, j = 3:4
           println((i, j))
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

使用此语法时,仍然可以引用迭代对象中的外部循环变量;例如,表达式’for i=1:n,j=1:i`将有效。 但是,这样一个循环内的`break’运算符导致整个嵌套循环集的退出,而不仅仅是内部循环。 每次执行内循环时,两个变量('i`和`j`)都为当前迭代分配值。 因此,在后续迭代中,分配给`i`的值将不可用。:

julia> for i = 1:2, j = 3:4
           println((i, j))
           i = 0
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

如果我们为每个变量使用关键字`for`重写这个例子,结果将是不同的:第二个和第四个值将包含`0'。

在单个’for’循环中,您可以使用函数同时迭代多个容器 '拉链':

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
           println((j,k))
       end
(1, 4)
(2, 5)
(3, 6)

在帮助下 `zip'创建一个迭代器,它是传输容器的元素的元组。 "Zip"迭代器按顺序遍历嵌套迭代器,选择 它们中的每一个的th元素是on -for循环的第三次迭代。 当任何嵌套迭代器中的元素用完时,'for`循环的执行停止。

异常处理

发生意外情况时,可能无法向调用方返回有意义的值。 异常可能导致程序终止并输出诊断错误消息,或者如果程序员已提供处理此类异常条件,则执行代码中的任何操作。

嵌入的"异常"对象

异常('Exception')在发生意外情况时调用。 下面列出的所有内置"异常"对象都会中断正常的执行顺序。

'例外`

'ArgumentError'

'BoundsError'

'CompositeException'

'DimensionMismatch`

'DivideError'

'DomainError'

'EOFError'

'ErrorException'

'无情的恐怖'

'InitError'

'InterruptException'

`InvalidStateException'

'KeyError'

'LoadError'

'OutOfMemoryError'

xref:base/base.adoc#Core.ReadOnlyMemoryError['ReadOnlyMemoryError'`

'RemoteException'

'方法错误`

'OverflowError'

xref:base/base.adoc#Base.Meta.ParseError['元。ParseError'`

'系统错误'

'TypeError'

`UndefRefError'

`UndefVarError'

'StringIndexError'

例如,在应用函数时 `sqrt'负实值发生异常 'DomainError':

julia> sqrt(-1)
ERROR: DomainError with -1.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]

您可以定义自己的异常,如下所示。

julia> struct MyCustomException <: Exception end

功能 '扔'

可以使用函数显式创建异常 '扔'。 例如,可以定义一个仅对非负数有意义的函数,以便在传递负参数时,它输出(`throw')异常 'DomainError':

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be non-negative"))
f (generic function with 1 method)

julia> f(1)
0.36787944117144233

julia> f(-1)
ERROR: DomainError with -1:
argument must be non-negative
Stacktrace:
 [1] f(::Int64) at ./none:1

请注意 'DomainError'不是异常,而是一种异常类型。 必须调用它来获取`Exception’对象。:

julia> typeof(DomainError(nothing)) <: Exception
true

julia> typeof(DomainError) <: Exception
false

某些类型的异常还采用一个或多个参数,这些参数在错误消息中使用。:

julia> throw(UndefVarError(:x))
ERROR: UndefVarError: `x` not defined

这样的机制可以很容易地为自定义异常类型实现,就像为 `UndefVarError':

julia> struct MyUndefVarError <: Exception
           var::Symbol
       end

julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")

建议错误消息以小写字母开头。 例子::

'size(A)==size(B)//throw(DimensionMismatch("a的大小不等于B的大小"))`

比…​…​

'size(A)==size(B)//throw(DimensionMismatch("A的大小不等于B的大小"))`。

但是,有时将第一个字母保持大写是有意义的,例如,如果函数参数以大写字母开头。:

'size(A,1)==size(B,2)||throw(DimensionMismatch("A有第一维。.."))`.

错误

功能 `error'创建异常 `ErrorException',中断正常的执行顺序。

假设您需要立即中止执行,如果试图提取负数的平方根。 为此,您可以定义函数的更严格版本。 `sqrt',如果参数为负,则返回错误:

julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
fussy_sqrt (generic function with 1 method)

julia> fussy_sqrt(2)
1.4142135623730951

julia> fussy_sqrt(-1)
ERROR: negative x not allowed
Stacktrace:
 [1] error at ./error.jl:33 [inlined]
 [2] fussy_sqrt(::Int64) at ./none:1
 [3] top-level scope

如果用另一个函数的负值调用’fussy_sqrt’函数,而不是进一步执行调用函数,则会在交互式会话中立即返回带有错误消息的控件。:

julia> function verbose_fussy_sqrt(x)
           println("before fussy_sqrt")
           r = fussy_sqrt(x)
           println("after fussy_sqrt")
           return r
       end
verbose_fussy_sqrt (generic function with 1 method)

julia> verbose_fussy_sqrt(2)
before fussy_sqrt
after fussy_sqrt
1.4142135623730951

julia> verbose_fussy_sqrt(-1)
before fussy_sqrt
ERROR: negative x not allowed
Stacktrace:
 [1] error at ./error.jl:33 [inlined]
 [2] fussy_sqrt at ./none:1 [inlined]
 [3] verbose_fussy_sqrt(::Int64) at ./none:3
 [4] top-level scope

"Try/catch"运算符

'Try/catch`运算符允许您检查异常并正确处理通常导致应用程序崩溃的情况。 例如,在下面的代码中,具有这样一个参数的平方根提取函数通常会引发异常。 通过将其放在`try/catch’块中,可以避免这种情况。 你决定如何处理异常:记录它,返回一个占位符值,或者,在这种情况下,只需在屏幕上显示文本。 在选择处理意外情况的方法时,请记住,'try/catch`块比用于相同目的的条件分支慢得多。 以下是使用"try/catch"块进行异常处理的其他示例。

julia> try
           sqrt("ten")
       catch e
           println("You should have entered a numeric value")
       end
You should have entered a numeric value

'Try/catch`运算符还允许您将`Exception`对象保存在变量中。 在下面有点人为的例子中,如果`x`是索引对象,则计算第二个元素`x`的平方根;否则,假设`x`是实数,并返回其平方根。:

julia> sqrt_second(x) = try
           sqrt(x[2])
       catch y
           if isa(y, DomainError)
               sqrt(complex(x[2], 0))
           elseif isa(y, BoundsError)
               sqrt(x)
           end
       end
sqrt_second (generic function with 1 method)

julia> sqrt_second([1 4])
2.0

julia> sqrt_second([1 -4])
0.0 + 2.0im

julia> sqrt_second(9)
3.0

朱莉娅>sqrt_second(-9)
错误:DomainError with-9.0:
sqrt使用负实参数调用,但只有在使用复杂参数调用时才会返回复杂结果。 尝试sqrt(Complex(x))。
[医]堆垛机:
[...]

请注意,'catch’后面的字符总是被解释为异常的名称,所以在一行中编写`try/catch`表达式时要小心。 如果出现错误,下面的代码不会返回值`x`:

try bad() catch x end

相反,使用分号或在`catch`后面插入换行符:

try bad() catch; x end

try bad()
catch
    x
end

"Try/catch"结构非常有用,因为它允许您立即将深度嵌套的计算转移到调用函数堆栈中更高的级别。 有些情况下,即使在没有错误的情况下,旋转堆栈并将值转移到更高级别的能力也很有用。 对于更复杂的错误处理,Julia具有函数 '重新思考', '后退', 'catch_backtrace''current_exceptions'

'else’表达式

兼容性:Julia1.8

此功能需要不低于Julia1.8的版本。

有时不仅要正确处理错误,还要确保只有在"try"块成功完成时才执行某些代码。 为此,在’catch’块之后,您可以指定’else’子句,该子句仅在之前没有发生错误时执行。 在`try`块中包含此代码的优点是,进一步的错误不会被’catch`子句拦截而没有任何反应。

local x
try
    x = read("file", String)
catch
    # обработка ошибок чтения
else
    # действия с x
end

每个`try`,catch,`else`和`finally`子句都引入了自己的作用域块,因此如果变量仅在`try`块中定义,则在`else`或`finally’子句中不可用。:

    julia> try
               foo = 1
           catch
           else
               foo
           end
    ERROR: UndefVarError: `foo` not defined in `Main`
    Suggestion: check for spelling errors or missing imports.
Чтобы сделать переменную доступной в любом месте внешней области, используйте [ключевое слово `local`](@ref local-scope) вне блока `try`.

"最后"表达

在完成更改状态或使用资源(如文件)的代码的执行后,通常需要执行某些清理操作(例如,关闭文件)。 异常可能会使此任务复杂化,因为它们可能导致代码块的执行过早中断。 'Finally’关键字确保在退出给定代码块时执行特定代码,而不考虑退出方法。

例如,通过这种方式,您可以确保关闭打开的文件。:

f = open("file")
try
    # действия с файлом f
finally
    close(f)
end

当控制从`try`块转移时(例如,由于执行`return`语句或由于正常终止),执行`close(f)'函数。 如果由于异常而从`try`块退出,则会传递此异常。 'Catch’块也可以与`try`和`finally’结合使用。 在这种情况下,`finally`块将在`catch’块处理错误后执行。

任务(协程)

任务是一个执行顺序函数,提供灵活的暂停和恢复计算。 这里提到它们只是为了完整性;请参阅章节中的完整描述 异步编程