Julia变量的作用域
变量的作用域是变量在代码中可见的范围。变量作用域有助于避免变量命名冲突。这个概念很直观:两个函数都可以有称为x的参数,但这两个x所指的不是同一件事。类似地,在许多其他情况下,不同的代码块可以使用相同的名称而不引用相同的东西。当相同的变量名指向或不指向同一事物时,这种规则称为作用域规则。本章将对它们进行详细说明。
语言中的某些构造引入了范围块,在这些范围块内,规定了某些变量集的作用域。变量的作用域不能是源代码的任意一组集合,相反,它将总是与这些块中的一个相关。在Julia中有两种主要类型的作用域,全局作用域和局部作用域。后者可以嵌套。在Julia中,构造的硬作用域和软作用域之间也有区别,它们会影响是否允许使用同名变量隐藏一个全局变量。作用域构造
引入作用域块的构造有:
构造
作用域类型
允许的位置
module,baremodue
全局
全局
struct
局部(软)
全局
for,while,try
局部(软)
全局,局部
macro
局部(硬)
全局
函数,do块,let块,推导,生成器
局部(硬)
全局,局部
值得注意的是,这个表中没有begin块和if块,它们没有引入新的作用域。这三种类型的作用域遵循不同的规则,下面将对此进行解释。
Julia使用词法作用域,这意味着函数的作用域不是继承自调用者的作用域,而是继承自定义函数的作用域。例如,在以下代码中,foo中的x引用了其模块Bar的全局作用域中的x:juliamoduleBarx1foo()xend;
而不是使用foo的作用域中的x:juliaimport。Barjuliax1;juliaBar。foo()1
因此,词法作用域意味着特定代码段中的变量所引用的内容可以单独从其出现的代码中推断出来,而不依赖于程序的执行方式。嵌套在另一个作用域中的作用域可以看到包含它的所有外部作用域中的变量。但是,外部作用域不能看到内部作用域中的变量。全局作用域
每个模块都引入一个新的全局作用域,它与所有其他模块的全局作用域分开不存在包含所有全局作用域的全局作用域。模块可以通过using或import语句,或通过使用点表示法的授权访问,将其他模块的变量引入其作用域,即每个模块都是一个所谓的命名空间,也是一个将名称与值关联起来的一级数据结构。注意,虽然可以在外部读取变量绑定的值,但只能在它们所属的模块内更改它们。作为一个安全门,你总是可以在模块内执行代码来修改一个变量。这避免了模块绑定在外部不通过调用eval而直接对代码进行修改。juliamoduleAa1aglobalinAsscopeend;juliamoduleBmoduleCc2endbC。ccanaccessthenamespaceofanestedglobalscopethroughaqualifiedaccessimport。。AmakesmoduleAavailabledA。aend;juliamoduleDbaerrorsasDsglobalscopeisseparatefromAsend;ERROR:UndefVarError:anotdefinedjuliamoduleEimport。。AmakemoduleAavailableA。a2throwsanerrorend;ERROR:cannotassignvariablesinothermodules
注意,交互提示符(又名REPL)位于模块Main的全局作用域内。局部作用域
大多数代码块都会引入一个新的局部作用域(完整列表见上表)。一些编程语言要求在使用新变量之前会显式地声明它们。在Julia中也可以使用显式声明:在任何局部作用域中,不管外部作用域中是否已经存在名为x的变量,写入localx就在该作用域中声明了一个新的局部变量。然而,像这样声明每个新的局部变量有点冗长和乏味,因此,与许多其他语言一样,Julia通过在局部作用域中对新变量赋值,从而隐式地将该变量声明为一个新的局部变量。大多数情况下,这是相当直观的,但与许多凭直觉行事的事情一样,细节要比从表面看起来的要微妙得多。
当x发生在局部作用域中时,Julia应用以下规则,根据赋值表达式发生的位置和x在该位置已经引用的内容来决定表达式的含义:现有的局部变量:如果x已经是一个局部变量,则对现有的局部变量x赋值。硬作用域:如果x还不是一个局部变量,并且赋值发生在硬作用域构造中(例如,在let块、函数或宏体、推导或生成器中),则在赋值的作用域中创建一个名为x的新局部变量。软作用域:如果x还不是一个局部变量,并且所有包含赋值的作用域结构都是软作用域(循环、trycatch块或struct块),则行为取决于是否定义了全局变量x:如果全局x未定义,则在赋值范围内创建一个名为x的新局部变量。如果定义了全局x,赋值被认为是歧义的:在非交互式环境中(文件,eval),会打印一个歧义警告,并创建一个新的局部变量。在交互式环境中(REPL,notebook),全局变量x被赋值。
你可能会注意到,在非交互式环境中,硬作用域和软作用域的行为是相同的,只不过在软作用域中,当一个隐式局部变量(即没有使用localx声明)对全局变量造成屏蔽时,会打印一个警告。在交互式环境中,为了方便起见,规则遵循更复杂的启发式。下面的示例将深入讨论这一点。
既然已经知道了规则,让我们看一些例子。假设每个示例都是在一个全新的REPL会话中计算的,这样每个代码段中的惟一全局变量就是在该代码块中分配的全局变量。
我们将从一个优雅而明确的情况开始在硬作用域内赋值,在本例中是一个函数体,此时不存在同名的局部变量:juliafunctiongreet()xhellonewlocalprintln(x)endgreet(genericfunctionwith1method)juliagreet()hellojuliaxglobalERROR:UndefVarError:xnotdefined
在greet函数内部,赋值xhello使x成为函数作用域中的一个新的局部变量。有两个相关的事实:赋值发生在局部作用域中,并且没有现有的局部x变量。因为x是局部的,所以是否存在一个全局变量x并不重要。例如,在定义和调用greet之前,我们先定义了x123:juliax123global123juliafunctiongreet()xhellonewlocalprintln(x)endgreet(genericfunctionwith1method)juliagreet()hellojuliaxglobal123
由于greet中的x是局部的,全局x的值(或不存在这样的值)不受调用greet的影响。硬作用域规则并不关心名为x的全局变量是否存在:在硬作用域中对x的赋值是局部的(除非x在其中被显示地声明为全局的)。
我们要考虑的下一个明确的情况是,已经有一个名为x的局部变量,在这种情况下,x总是赋值给这个已经存在的局部变量x。下面的函数sumto计算从1到n的数字之和:juliafunctionsumto(n)s0newlocalfori1:nssiassignexistinglocalendreturnssamelocalendsumto(genericfunctionwith1method)
与前面的示例一样,在sumto的顶部对s的第一次赋值将导致s成为函数体中的一个新的局部变量。for循环在函数作用域中有自己的内部局部作用域。当ssi出现时,s已经是一个局部变量,因此赋值更新了现有的s,而不是创建一个新的局部变量。我们可以通过调用REPL中的sumto来测试:juliasumto(10)55juliasERROR:UndefVarError:snotdefined
因为s是sumto的本地函数,调用函数对全局变量s没有影响。我们也可以看到在for循环中更新ssi必定更新了由初始化s0创建的s,因为我们得到了从整数1到10的正确的和55。
让我们先深入了解一下for循环体有它自己的作用域这一事实,我们可以写一个稍微更复杂的变体sumtodef,在更新s之前将和si保存在变量t中:juliafunctionsumtodef(n)s0newlocalfori1:ntsinewlocaltstassignexistinglocalsendreturns,isdefined(t)endsumtodef(genericfunctionwith1method)juliasumtodef(10)(55,false)
这个版本像以前一样返回s,但它也使用isdefined宏返回一个布尔值,显示函数的最外层局部作用域中是否定义了一个名为t的局部变量。如你所见,在for循环体之外没有定义t。这还是因为硬作用域规则:由于对t的赋值发生在函数内部,这引入了硬作用域,赋值导致t在它出现的局部作用域内成为一个新的局部变量,即在循环体内部。即使有一个名为t的全局变量,也不会有什么区别硬作用域规则不会受到全局作用域中情况的影响。
让我们来看看软作用域规则所涵盖的一些更模糊的情况。我们将通过将greet和sumtodef函数的主体提取到软作用域环境中来研究这个问题。首先,让我们把greet的主体放到for循环中它是软的,而不是硬的然后在REPL中计算它:juliafori1:3xhellonewlocalprintln(x)endhellohellohellojuliaxERROR:UndefVarError:xnotdefined
由于在执行for循环时没有定义全局x,因此软作用域规则的第一种情况将被应用,x将被创建为for循环的局部变量,因此在循环执行后全局x仍未定义。接下来,让我们考虑将sumtodef的主体提取到全局作用域,将其参数固定为n10:s0fori1:10tsistendsisdefined(t)
这段代码做了什么的?这是个棘手的问题。答案是视情况而定。如果以交互方式输入此代码,则其行为与在函数体中相同。但是,如果代码出现在文件中,它会打印一个歧义警告,并抛出一个未定义变量的错误。让我们先看看它在REPL中的工作情况:julias0global0juliafori1:10tsinewlocaltstassignglobalsendjuliasglobal55juliaisdefined(t)globalfalse
REPL通过判断是否定义了同名的全局变量来确定循环内的赋值是赋值给全局变量还是创建新的局部变量,这种行为近似于函数体内的行为。如果存在同名的全局变量,则赋值操作会更新它。如果不存在全局变量,则赋值将创建一个新的局部变量。在这个例子中,我们看到了这两种情况:没有全局变量t,所以tsi创建一个新的t,它是for循环的局部变量。有一个全局变量s,所以st赋值给它。
第二条解释了为什么循环的执行会改变s的全局值,第一条解释了为什么在循环执行后t仍然是未定义的。现在,让我们对相同的代码进行改造,模拟代码在文件中的情况,然后执行:juliacodes0globalfori1:10tsinewlocaltstnewlocalswithwarningends,globalisdefined(t)global;juliaincludestring(Main,code)Warning:Assignmenttosinsoftscopeisambiguousbecauseaglobalvariablebythesamenameexists:swillbetreatedasanewlocal。Disambiguatebyusinglocalstosuppressthiswarningorglobalstoassigntotheexistingglobalvariable。string:4ERROR:LoadError:UndefVarError:snotdefinedinexpressionstartingatstring:2
这里我们使用includestring来执行代码,就好像它是文件的内容一样。我们也可以将代码保存到文件中,然后在该文件上调用include结果将是相同的。如你所见,这与在REPL中的结果有很大的不同。让我们来分析一下这里发生了什么:在执行循环之前,全局变量s被定义为值0。赋值st发生在软作用域中任何函数体或其他硬作用域构造之外的for循环中。因此,适用于软作用域规则的第二种情况,并且赋值是不明确的,因此会发出警告。继续执行,使s成为for循环体的局部变量。由于s是for循环的局部变量,当tsi被求值时,它是未定义的,从而导致错误。计算到此结束,但如果到达s和isdefined(t),它将返回0和false。
这演示了作用域的一些重要方面:在作用域中,每个变量只能有一种含义,并且该含义与表达式的顺序无关。在循环中表达式st的存在导致s是循环的局部变量,这意味着当它出现在tsi的右边时,它也是局部变量,即使该表达式首先出现并首先执行。你或许会认为循环第一行的s可以是全局的,循环第二行的s可以是本地的,但实际上是不可能的,因为这两行在同一个作用域块,在给定的作用域,每个变量只能有一种含义。关于软作用域
现在,我们已经介绍了所有的局部作用域规则,但是在结束本节之前,应该说明一下为什么在交互和非交互环境中处理模糊的软作用域情况是不同的。人们可能会问两个明显的问题:为何不在所有地方都像在REPL中那样的工作呢?为何不在所有地方都像在文件中那样工作并去掉警告呢?
在Julia0。6,所有全局作用域的工作方式都如同当前的REPL一样:当x发生在一个循环(或trycatch,或struct体)内,但在函数体(或let块或推导)以外,x在循环内是否为局部的取决于是否定义了全局变量x。这种行为具有直观和方便的优点,因为它尽可能接近函数体内部的行为。特别地,当调试函数的行为时,它使得在函数体和REPL之间来回移动代码变得很容易。然而,它也有一些缺点。首先,这是一种相当复杂的行为:多年来,许多人对这种行为感到困惑,并抱怨它既复杂又难以解释和理解。其次,可以说更糟糕的是,它不利于大规模编程。当你在一个地方看到像这样的一小段代码时,很清楚会发生什么:s0fori1:10siend
显然,其目的是修改现有的全局变量s,不然会是什么呢?然而,并非所有现实世界的代码都是如此简短或清晰。我们发现像下面这样的代码经常大量的出现:x123muchlatermaybeinadifferentfilefori1:10xhelloprinntln(x)endmuchlatermaybeinyetanotherfileormaybebackinthefirstonewherex123yx234
现在就不是很明确应该发生什么。因为hello234是一个方法错误,它的意图似乎是让x在for循环中是局部的。但是运行时的值和当时存在的方法不能用来确定变量的作用域。在Julia0。6的行为方式下,会存在有人先写了for循环,它可以很好的工作,但后来当别人在远处(可能在不同的文件中)添加了一个新的全局变量,代码突然改变了含义,它可能会显式地终止,更糟的是,有可能默默地做错误的事情。这种幽灵般的远距离行动是优秀的程序语言设计应该避免的。
因此,在Julia1。0中,简化了作用域规则:在任何局部作用域中,对一个不属于局部变量的名称赋值将创建一个新的局部变量。这完全消除了软作用域的概念,并消除了幽灵行动的可能性。软作用域的删除使大量的bug得到了暴露和修复,这也证明了选择删除它是正确的。但也存在一些不便,如下面的代码所示:s0fori1:10globalsiend
看到全局注释了吗?显然,这种情况是不能容忍的。但严格地说,这种需要global的顶层代码存在两个主要问题:不方便将函数体中的代码复制并粘贴到REPL中进行调试你必须在调试时添加global注释,然后在拷回函数时删除它们。初学者会在编写这类代码时漏掉global,并对代码不能工作感到莫名其妙他们得到的错误是s未被定义,这似乎对碰巧犯这种错误的人没有任何启发效果。
从Julia1。5开始,这段代码在交互式环境中(如REPL或Jupyter笔记本)不需要global注释(就像Julia0。6),在文件和其他非交互式环境中,它打印出非常直接的警告:
在软作用域中对s的赋值是不明确的,因为存在同名的全局变量:s将被视为一个新的局部变量。通过使用locals来消除该警告,或使用globals来赋值给现有的全局变量来消除歧义。
这既解决了上述两个问题,又保留了1。0中大规模编程好处的行为:全局变量对可能遥远的代码含义没有幽灵影响。在REPL中复制粘贴调试工作良好,初学者也不会遇到任何问题,任何时候,如果有人忘记了一个global注释,或者不小心在一个软作用域中用一个局部隐藏了一个现有的全局变量(虽然听起来很奇怪),他们就会得到一个清晰的警告。
这种设计的一个重要属性是,任何在文件中执行而没有警告的代码在新的REPL中都将以相同的方式执行。另一方面,如果你使用一个REPL会话并将其保存到文件中,如果它的行为与在REPL中不同,那么你将得到一个警告。Let块
与对局部变量的赋值不同,let语句在每次运行时分配新的变量绑定。赋值会修改现有值的位置,let会创建新的位置。这种差异通常并不重要,只有在变量通过闭包存活的时间超过其作用域的情况下才会显现。let语法接受逗号分隔的一系列赋值和变量名:juliax,y,z1,1,1;julialetx1,zprintln(x:x,y:y)xislocalvariable,ytheglobalprintln(z:z)errorsaszhasnotbeenassignedyetbutislocalendx:1,y:1ERROR:UndefVarError:znotdefined
赋值是按顺序执行的,在左边的新变量被引入之前,右边的每个变量都在作用域内求值。因此,写letxx是有意义的,因为两个x变量是不同的,有不同的存储空间。下面是一个需要let行为的例子:juliaFsVector{Any}(undef,2);i1;juliawhilei2Fs〔i〕()iglobali1endjuliaFs〔1〕()3juliaFs〔2〕()3
在这里,我们创建并存储了两个返回变量i的闭包。然而,它始终是同一个变量i,因此两个闭包的行为是相同的。我们可以使用let为i创建一个新的绑定:juliaFsVector{Any}(undef,2);i1;juliawhilei2letiiFs〔i〕()iendglobali1endjuliaFs〔1〕()1juliaFs〔2〕()2
由于begin构造不引入新的作用域,所以使用零参数let只引入一个新的作用域块而不创建任何新绑定是很有用的:julialetlocalx1letlocalx2endxend1
因为let引入了一个新的作用域块,所以内部的局部变量x与外部的局部变量x是不同的。循环和推导
在循环和推导式中,在其主体作用域中引入的新变量会在每次循环迭代时重新分配,就像循环主体被一个let块包围一样,如下例所示:juliaFsVector{Any}(undef,2);juliaforj1:2Fs〔j〕()jendjuliaFs〔1〕()1juliaFs〔2〕()2
for循环或推导迭代变量总是一个新变量:juliafunctionf()i0fori1:3emptyendreturniend;juliaf()0
然而,有时可以重用现有的局部变量作为迭代变量。这可以通过添加关键字outer方便地完成:juliafunctionf()i0forouteri1:3emptyendreturniend;juliaf()3常量
变量的一个常见用法是给特定的、不变的值命名。这样的变量只被赋值一次。这个意图可以通过使用const关键字传递给编译器:juliaconste2。718;juliaconstpi3。142;
可以在一个const语句中声明多个变量:juliaconsta,b1,2(1,2)
const声明只能在全局作用域中的全局变量上使用。编译器很难优化涉及全局变量的代码,因为它们的值(甚至它们的类型)几乎在任何时候都可能发生改变。如果全局变量不变,添加const声明就可以解决这个性能问题。
局部常量则不同的。编译器能够自动确定局部变量何时为常量,因此不需要声明局部常量,实际上目前也不支持局部常量。
特殊的顶层赋值,例如由function和struct关键字执行的赋值,默认情况下是常量。
注意,const只影响变量绑定。变量可以绑定到一个可变对象(比如数组),并且该对象仍然可以被修改。另外,当试图给一个被声明为常量的变量赋值时,可能会出现以下情况:如果新值的类型与常量的类型不同,则抛出一个错误:juliaconstx1。01。0juliax1ERROR:invalidredefinitionofconstantx如果新值的类型与常量相同,则会打印警告:juliaconsty1。01。0juliay2。0WARNING:redefinitionofconstanty。Thismayfail,causeincorrectanswers,orproduceothererrors。2。0如果赋值不会导致变量值的改变,则不会给出消息:juliaconstz100100juliaz100100
最后一条规则适用于不可变对象,只要值不发生改变,变量的重新绑定实际上是被忽略的,例如:juliaconsts111julias211juliapointer。(〔s1,s2〕,1)2elementVector{Ptr{UInt8}}:Ptr{UInt8}0x00007f50963238d8Ptr{UInt8}0x00007f5096323938julias1s21juliapointer。(〔s1,s2〕,1)2elementVector{Ptr{UInt8}}:Ptr{UInt8}0x00007f50963238d8Ptr{UInt8}0x00007f5096323938
然而,对于可变对象,会按预期打印警告:juliaconsta〔1〕1elementVector{Int64}:1juliaa〔1〕WARNING:redefinitionofconstanta。Thismayfail,causeincorrectanswers,orproduceothererrors。1elementVector{Int64}:1
请注意,尽管有时可以,但强烈建议不要更改const变量的值,除非在交互使用时为了方便而故意为之。更改常量可能会导致各种问题或意外行为。例如,如果一个方法引用了一个常量,并且在更改该常量之前已经编译过了,那么它将会继续使用原来的值:juliaconstx11juliaf()xf(genericfunctionwith1method)juliaf()1juliax2WARNING:redefinitionofconstantx。Thismayfail,causeincorrectanswers,orproduceothererrors。2juliaf()1
最简单的越狱方法,用手机直接越狱苹果手机苹果手机系统因为会自动杀进程所以容易维持好的机器环境,越狱之后可以体验到更多的自定义功能,不过一般人不建议越狱,除了喜欢追求其他开发功能的人,不过现在很多都是借助平台越狱,PP……
吴京电影电视剧全集吴京最新电影电视剧盘点吴京主演的电影有哪些?吴京,中国影坛八大武术冠军之一!被誉为ldquo;功夫小子rdquo;。满族正黄旗,老姓乌雅氏,生于北京的武术世家。6岁时进入北京武术队,自8岁那年拿了个……
赵本山小姨子写真如此销魂为哪般(组图)最近看了赵本山小姨子于月仙的一几组个人写真,猛然发现,原来ldquo;谢大脚rdquo;还可以这么销魂。都说女人是水做的,这话不假,所以无论什么模样的女人,都有成熟性感销魂如水……
范冰冰疑默认恋情低调是为呵护它怕被搅和最近,随着圈内人巨春雷与好友黄晓明(微信号:lovemingzone)的接连ldquo;失口rdquo;,范冰冰(微信号:fbbstudio916)与李晨的恋情又一次成为全民热……
最美包子妹走红美女受到网友关注(私照曝光)台湾网友热衷追捧各种ldquo;妹rdquo;,例如ldquo;豆花妹rdquo;、ldquo;鸡排妹rdquo;、ldquo;尖叫妹rdquo;等等。日前,在高雄又有一位包子……
紫檀主持星光大道毕福剑欲退居幕后提携紫檀曝毕福剑欲退居幕后提携紫檀主持《星光大道》央视《星光大道》的主持人是谁?当然是毕福剑!最近又多了个名叫ldquo;紫檀rdquo;的女主持人,据说是个ldquo;空降兵r……
女星减肥秘诀大揭秘,范冰冰小S刘嘉玲亲授经验娱乐圈中女星的各类减肥方法如今已备受各界潮流女性的追捧,诸多女星也有过多次的瘦身成功经验,女星想要博得上位,必须要保持完美的身材凹凸有致的体态。范冰冰曾说过,如果她想要继续参演……
MediaTek发布6G愿景白皮书,定义三大基本设计原则S。2022年1月18日,MediaTek发布《6G愿景白皮书》,以时间表、关键技术趋势和工程实现因素三个主题勾勒MediaTek的6G愿景,并基于技术趋势提出三个6G系统设计基本……
赵薇与黄晓明狂亲60遍被问亲吻60遍啥感觉请问赵薇黄晓明ldquo;吻60遍rdquo;是啥感觉刚刚拿到金像奖影后的赵薇,昨天和黄晓明、佟大为一道,在新片《横冲直撞好莱坞》的北京发布会上玩得好欢。赵薇……
如何绕过苹果手机AppleID激活手机?分享两个免费绕ID的有很多人使用iPhone久了以后就会忘记自己的AppleID密码,或者手机长时间不用后忘记了密码,或者捡到了苹果手机后打不开,需要AppleID才能打开,苹果手机刷机以后需要通……
模特台上内衣滑落淡定走完全程一饱眼福模特台上内衣滑落,不惧豪乳走光淡定走完全程。昨日,2015国际旅游小姐大赛驻马店赛区总决赛举行。现场,发生令人意外的一幕,一比基尼美女的的内衣不慎滑落,画面香艳,让观众一饱眼福……
虚拟币要被彻底封杀了5月19日,央行与三大协会率先亮剑,在彻底封杀比特币等虚拟货币的号角响起后,有幸存者等着看那个时候国家再出手,又一轮更猛烈的围剿虚拟货币一夜之间开始。6……