Perl:数组在传递给函数后变为空?

Perl: array goes empty after passing it to a function?

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

脚本检查数据库中是否存在基因(通过各种方式)。它还可能报告可能的重复项。在报告重复之前,脚本会确保它不是 "biological duplicate"(本质上是相同的生物数据,但在基因组中的位置不同,因此不是真正的重复)。为了这样做...

 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 数组传递给此函数。如果能够解决差异,此函数将 return 正确基因的 gene_id;如果不是,则为 0。 sub 的简化代码可以在下面找到 link

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    ()

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

PS:报告中的J只是一个case-scenario键码。

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

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

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

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 没有理由。它的作用改变数组

相反,使用传递给 sub

的 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 ),对其的更改不会影响调用作用域中具有相同名称的变量。

my @gene_names = splice(shift);
my @gene_ids   = splice(shift);

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

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

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

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

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

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