【问题标题】:Perl: array goes empty after passing it to a function?Perl:将数组传递给函数后数组变为空?
【发布时间】:2018-02-01 09:27:08
【问题描述】:

我最近正在处理一个规模很大的项目,我正在重写代码以使其更加面向对象,并将所有冗余代码传递到子例程中。

脚本检查数据库中是否存在基因(通过各种方式)。它还可能报告可能的重复项。在报告重复之前,脚本会确保它不是“生物重复”(本质上是相同的生物数据,但在基因组中具有不同的位置,因此不是实际的重复)。为了做到这一点……

 my @gene_ids;
 my @gene_names;                                

 while(my $gene = $geners_bychecksum->next){

        my $gene_name = $gene->gene_name;
        my $gene_id = $gene->gene_id;

        push @gene_ids, $gene_id;
        push @gene_names, $gene_name;


    }

    print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",@gene_names).")\n"; 
    my $solve_multihit = solve_multihit($id, \@gene_names, \@gene_ids, $spc, $species_directory, $dataset);
    print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",@gene_names).")\n"; 

    if($solve_multihit){

        print STDERR "$id\tM\tUPDATE \n";   
        print $report "$id\tM\tUPDATE \n";  
        $countM++;                                                                              

    } else {

        print STDERR "$id\tJ\tALERT CHECKSUM MULTI-HIT\t(".join(",",@gene_names).")\n"; 

    }

这里,$geners_bychecksum 是一个 DBIC 结果集,其中包含来自先前搜索的数据库命中,对于这种情况,它总是有超过 1 个基因。 $id,$spc,$species_directory$dataset 都是来自配置的字符串并在此块之上定义。

solve_multihit 子例程是一个相当复杂的函数,它试图解析多重命中是实际重复还是生物重复。请注意,我将 @gene_names@gene_ids 数组传递给此函数。如果能够解决差异,此函数将返回正确基因的gene_id;如果不是,则为 0。 sub的简化代码可以在以下链接中找到

https://codeshare.io/2EM8qN

实际问题

你可能已经注意到了

print STDERR "$id\tJ\tALERT CHECKSUM MULTI HIT\t(".join(",",@gene_names).")\n";

solve_multihit 子例程调用之前和之后...根据 STDERR,运行函数后数组似乎变空:

BBOV_I005030    J   ALERT CHECKSUM MULTI-HIT    (XP_001609152.1,XP_001609157.1)
BBOV_I005030    J   ALERT CHECKSUM MULTI-HIT    ()
BBOV_I005040    J   ALERT CHECKSUM MULTI-HIT    (XP_001609156.1,XP_001609153.1)
BBOV_I005040    J   ALERT CHECKSUM MULTI-HIT    ()
BBOV_I005050    J   ALERT CHECKSUM MULTI-HIT    (XP_001609154.1,XP_001609155.1)
BBOV_I005050    J   ALERT CHECKSUM MULTI-HIT    ()
BBOV_I005060    J   ALERT CHECKSUM MULTI-HIT    (XP_001609154.1,XP_001609155.1)
BBOV_I005060    J   ALERT CHECKSUM MULTI-HIT    ()
BBOV_I005070    J   ALERT CHECKSUM MULTI-HIT    (XP_001609156.1,XP_001609153.1)
BBOV_I005070    J   ALERT CHECKSUM MULTI-HIT    ()
BBOV_I005080    J   ALERT CHECKSUM MULTI-HIT    (XP_001609152.1,XP_001609157.1)
BBOV_I005080    J   ALERT CHECKSUM MULTI-HIT    ()

为什么会这样?我很确定我可以通过返回数组以及solve_multihit{} 子的结果来解决它,但我想知道为什么它会变空。

PS:报告中的J只是一个case-scenario key code。

【问题讨论】:

  • solve_multihit() 中显然有一些东西修改了输入数组(肯定是@gene_names,也可能还有@gene_ids),但我们只能猜测那个“东西”是什么而没有看到里面有什么子。是否可能使用shiftpop 来访问数组的内容?
  • 我不知道我可以修改输入!我会将子添加到帖子中。谢谢
  • @zdim, Re "函数参数是别名的,如果你改变@_你就会改变它们。",改变@_不会有描述的效果。更改 @{ $_[1] } 会。
  • @ikegami 对,谢谢。这是对最初评论的仓促和笨拙的“修复”,不精确到错误的程度。我正在删除评论。谢谢

标签: arrays perl subroutine bioperl


【解决方案1】:

我可以看到您的代码有两种方法来完成它似乎正在做的数据删除。

@_ 中可用的函数参数是传递给它的数据的别名。因此,如果您更改 @_ 本身(或其元素),您将更改函数外部的数据。

更有可能的是,当您通过引用传递时,您的 sub 可能直接使用它

sub ff {
    my ($rary) = @_;
    @$rary = ();
}

my @data = 1..4;

ff(\@data);

say for @data;  # empty

如果您的处理需要更改它使用的数组,请先制作本地副本

sub ff { 
    my ($rary) = @_;
    my @local_ary = @$ary;
    # now changes to @local_ary do not affect @data in the caller
}

这通常更安全,但它确实引入了在使用引用时不会发生的数据副本。


编辑连同 ikegami 的回答澄清了这一点:splice 对它使用的数组具有破坏性,在这里通过奇怪的语法,它被提供了一个由取消引用的 @_ 参数形成的匿名数组,从而它改变了数据调用者。

splice 在您的工作中没有任何理由。它的目的改变数组。

改为使用传递给子的arrayrefs

sub solve_multihit {
    my ($id, $gene_names, $gene_ids, ...) = @_;
    foreach my $name (@$gene_names) {
        ...
    }
    ...
}

如果您愿意,也可以制作本地副本

sub solve_multihit { 
    my $id = shift;
    my @gene_names = @{ shift @_ };
    ...
}

其中my @gene_names 是此范围内的一个词法变量(在您的情况下为 sub ),对其进行更改不会影响调用范围内的同名变量。

【讨论】:

  • 感谢您的快速响应,但我遗漏了一些东西......我如何在 $gene_names 这样存储后访问元素?对@_ 值的快速调试表明: foreach my $var (values @_){ print STDERR $var."\n"; BBOV_I001860 |数组(0x3fa6180)
  • @LionelUranLandaburu 然后你使用 arrayref。您可以通过$$arrayref[0]$arrayref->[0] 访问元素(代码更复杂时更好)。您可以通过 for my $elem (@$arrayref) { ... } 进行迭代。等等。我建议不要直接使用@_。直接说“不”吧。
  • @LionelUranLandaburu 顺便说一句,您使用的 values 旨在返回散列值,并且在 v5.12 之前只能使用散列。将它与数组一起使用可能会导致混淆。更重要的是,如果你想要一个数组的值,说foreach my $var (@array)就足够了。
  • @LionelUranLandaburu 当您执行\@data 时,您对数组进行引用,得到一个非常像指针的标量变量。该参考是您传递给您的子的内容。然后你需要 dereference 它才能访问元素,我上面的评论就是关于这个的。见perfreftut
  • @LionelUranLandaburu 添加了如何访问 arrayref 元素的示例。
【解决方案2】:
my @gene_names = splice(shift);
my @gene_ids   = splice(shift);

简称

my @gene_names = splice(@{ shift(@_) });
my @gene_ids   = splice(@{ shift(@_) });

splice(@a) 清空数组并返回其内容。没有理由这样做!以上应该是

my @gene_names = @{ shift(@_) };
my @gene_ids   = @{ shift(@_) };

老实说,没有必要复制数组。只需使用提供的参考。

my $gene_names = shift;
my $gene_ids   = shift;

我会提供solve_multihit 的修复版本,但它有许多我无法用我拥有的信息解决的主要问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-06
    • 2020-07-28
    • 2014-05-06
    • 1970-01-01
    • 1970-01-01
    • 2017-09-12
    • 2019-06-25
    相关资源
    最近更新 更多