【问题标题】:Perl: "Variable will not stay shared"Perl:“变量不会保持共享”
【发布时间】:2014-10-13 12:23:41
【问题描述】:

我查找了一些处理此警告的答案,但它们都没有帮助我,我也完全不了解 Perl 在这里做了什么。这就是我想要它做的事情:

sub outerSub {
  my $dom = someBigDOM;
  ...
  my $otherVar = innerSub();
  return $otherVar;

  sub innerSub {
    my $resultVar = doStuffWith($dom);
    return $resultVar;
  }
}

所以基本上,我有一个存储在 $dom 中的大 DOM 对象,如果可能的话,我不想在堆栈中传递它。在 outerSub 中,正在发生需要来自 innerSub 的结果的事情。 innerSub 需要访问 $dom。执行此操作时,我收到此警告“变量 $dom 将不会保持共享”。

我不明白的:

  1. 此警告是否与我有关?我的预期逻辑会在这里起作用还是会发生奇怪的事情?

  2. 如果不能按预期工作:是否可以这样做?使本地变量对嵌套子可见?还是将它作为参数传递更好?还是声明一个“我们的”变量更好?

  3. 如果我将它作为参数推送,是否会将整个对象及其所有数据(可能有几个 MB)推送到堆栈上?或者我可以传递一些类似参考的东西吗?还是 Perl 自己将那个参数作为引用处理?

  4. "Variable $foo will not stay shared" Warning/Error in Perl While Calling Subroutine 中,有人谈到了一个匿名的子系统,这将使这成为可能。我不明白它是如何工作的,从未使用过类似的东西。

  5. 我完全不明白那个解释(可能是因为英语不是我的第一语言):“当调用内部子程序时,它会看到外部子程序变量的值,就像它之前和期间一样第一次调用外部子例程;在这种情况下,在第一次调用外部子例程完成后,内部子例程和外部子例程将不再共享该变量的公共值。":

“对外部子程序的第一次调用完成?是什么意思”
我的意思是:首先我调用外部潜艇。外潜艇调用内潜艇。外部潜艇当然仍在运行。一旦外子完成,内子也将完成。那么当内部子已经完成时,这仍然适用吗?那么“第一个”电话呢? “第二个”电话什么时候发生...对不起,这个解释让我困惑不已。

很抱歉有很多问题。也许有人至少可以回答其中一些。

【问题讨论】:

    标签: perl variables local


    【解决方案1】:

    你需要有一个匿名子程序来捕获变量:

    my $innerSub = sub  {
      my $resultVar = doStuffWith($dom);
      return $resultVar;
    };
    

    例子:

    sub test {
        my $s = shift;
    
        my $f = sub {
            return $s x 2;
        };  
    
        print $f->(), "\n";
    
        $s = "543";
    
        print $f->(), "\n";
    }
    
    test("a1b");
    

    给予:

    a1ba1b
    543543
    

    【讨论】:

    • 请问:为什么?好的,就是这样,但为什么它没有按预期工作,为什么它与匿名潜艇一起工作?
    • 这是我解释这一点的尝试/猜测 :-) 编译器以不同的方式处理匿名子。在您的示例中,子例程/函数的返回值被分配给标量。使用代码引用或匿名子例程,代码在调用时和定义时运行。然后,范围界定更加“细化”。几个经典的解释:perlmonks.org/?node_id=66677(Perl Monks 节点 66677)和plover.com/~mjd/perl/FAQs/Namespaces.html(应对范围)确实有助于理解这个特性。
    【解决方案2】:

    如果您想最小化将参数传递给 subs 的大小数量,use Perl references。缺点/功能是子可以更改引用的参数内容。

    my $dom = someBigDOM;
    my $resultVar = doStuffWith(\$dom);
    
    
    sub doStuffWith {
       my $dom_reference = shift;
       my $dom_contents = $$dom_reference;
       #...
    }
    

    【讨论】:

    • 一位同事昨天告诉我,Perl 默认会传递引用。所以调用 doStuffWith($dom) 实际上应该只在堆栈上放一个引用。这与如果传递的变量在 sub 内部更改,则原始 var 更改的事实是一致的。但我不知道 Perl 到底在做什么,它可能只是做了一些奇怪的事情,只会让它看起来像是传递了一个 ref。
    • 不,参数是通过副本传递的。如果你想要一个引用,你必须使用显式 Perl 引用(带反斜杠)
    • 谢谢米格尔。你知道为什么在 sub 中改变传递的 var 的值会改变原来的 var 吗?我正在努力理解 Perl 是如何运行的......
    • 这个modperlbook.org/html/14-2-5-Passing-Variables.html 页面说,取消引用 sub 内的参数没有什么意义,因为它将原始 var 的内容复制到新的内存块。因此,据我所知,要么直接在 sub 内部使用 ref,总是说“$$dom_refernce = ...”,或者根本不将其作为 ref 传递。
    • perldoc perlsub 更清楚:The array @_ is a local array, but its elements are aliases for the actual scalar parameters. In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not possible to update)... 所以是的,它们作为“副本”传递,但在内部仍然作为参考。仅当您在shift 他们离开@_ 时才会进行复制。这就是为什么可以使用 @_[0] = ... 来更改 sub 中的原始 var。
    【解决方案3】:

    简而言之,第二次及以后调用outerSub 的$dom 变量将与innerSub 使用的变量不同。您可以通过以下方式解决此问题:

    {
        my $dom;
        sub outerSub {
            $dom = ...
            ... innerSub() ...
        }
        sub innerSub {
            ...
        }
    }
    

    或者这样做:

    sub outerSub {
        my $dom = ...
        *innerSub = sub {
            ...
        };
        ... innerSub() ...
    }
    

    或者这个:

    sub outerSub {
        my $dom = ...
        my $innerSub = sub {
            ...
        };
        ... $innerSub->() ...
    }
    

    所有变量最初都是预分配的,innerSub 和outerSub 共享同一个$dom。当你离开一个作用域时,perl 会检查在作用域中声明的词法变量并重新初始化它们。因此,在第一次调用 outerSub 完成时,它会获得一个新的 $dom。但是,因为命名的 subs 是全局的东西,所以 innerSub 不受此影响,并且一直引用旧的 $dom。所以如果第二次调用outerSub,它的$dom和innerSub的$dom其实是独立的变量。

    因此,要么将声明移出 outerSub,要么使用匿名 sub(在运行时新绑定到词法环境)解决了问题。

    【讨论】:

    • 感谢澄清。这是否意味着一旦 innerSub 访问 $dom,它会以某种方式破坏它?如果与 outerSub 的 $dom 不同,innerSub 在后续调用中会看到什么?
    • 很好的解释。尽管如此,在我看来,Perl 正在做一些不正确的事情。由于命名的 sub 是在一个块内声明的,它不应该是一个全局的东西,而只是“对块来说是全局的”,换句话说就是本地的。
    • 不要忘记在匿名子的末尾添加分号(在开头明显更改之后)。
    • 我认为理解这一点的关键是,innerSub 在任何意义上都不是真正的“内部”——它与 outerSub 处于同一水平——除了 $dom 当时在范围内的定义。所以innerSub 和outerSub 不共享$dom,因为outerSub 在每次调用时都会有一个新版本,而innerSub 将简单地引用定义时范围内的外部$dom - 它们不共享。
    【解决方案4】:

    http://www.foo.be/docs/perl/cookbook/ch10_17.htm 之后,您应该定义一个本地 GLOB,如下所示:

    local *innerSub = sub {
        ...
    } 
    #You can call this sub without ->
    innerSub( ... ) 
    

    请注意,即使显示警告,结果仍与预期相同:未在内部子范围内定义的变量在外部子范围内被修改。我看不出这个警告是关于什么的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 2016-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多