2009-02-15 70 views
11

"Is returning a whole array from a Perl subroutine inefficient"這個問題中,如果沒有必要,兩個人建議不要優化。作爲一般規則,優化會增加複雜性,如果不需要,簡單更好。但在這個特定情況下,返回一個數組和數組ref,我沒有看到增加任何複雜性,我認爲界面設計的一致性會更重要。因此,我幾乎總是這樣做:爲什麼你不應該返回一個數組ref?

sub foo 
{ 
    my($result) = []; 

    #....build up the result array ref 

    $result; 
} 

是否有原因我不應該這樣做,即使是小的結果?

+2

在「我的($結果)= [];」,爲什麼parens? – ysth 2009-02-15 18:28:00

+1

同樣,爲了一致性的緣故,我總是做 my($ foo); my($ bar,$ baz); 我從來沒有做'我的$ foo',因爲那麼我的一些「我的」是在標量環境中,有些在列表環境中。因此,我有時會做 my($ cnt)= scalar(@array); 我肯定會驅使一些人發瘋.... – 2009-02-16 18:14:56

回答

21

如果數組引用與接口的其餘部分不一致,則不應返回數組引用。如果你使用的其他東西都返回列表而不是引用,那麼不要讓其他程序員記住異常。

除非您有大型列表,否則這實際上是一個微型優化問題。如果這是你程序的瓶頸,你應該很幸運。

就複雜程度而言,參考和列表之間的差異在複雜度上如此之低,以至於如果程序員正在爲此付出努力,那麼您會遇到更大的問題。複雜的算法和工作流程很複雜,但這只是語法。在說了所有這些之後,我傾向於使所有的東西都返回引用,並使接口與之保持一致。

+1

這幾乎是我的想法,但我想確保沒有「最佳實踐」我不知道的答案。 – 2009-02-16 18:28:14

0

我不確定在這種情況下返回引用是否更有效;即Perl拷貝子程序返回的數據嗎?

一般來說,如果你的數組完全在子例程中構造,那麼返回一個引用沒有明顯的問題,否則數組將被拋棄。但是,如果引用在返回之前也在其他地方傳遞,則可能有兩個相同引用的副本,並且可能會在一個位置進行修改,但不會在其他位置進行修改。

+1

是的,perl拷貝子程序返回的數據。 – ysth 2009-02-15 18:26:14

+0

你真的沒有參考文獻的副本。引用指向相同的數據,所以如果通過一個引用更改數據,當通過另一個引用訪問數據時會看到更改。 – 2009-02-15 21:08:41

+0

是的,通過引用的副本,我指的是引用本身(可以改爲引用其他內容),而不是它引用的數據。 – 2009-02-16 07:33:13

7

編號除「返回$結果」外爲清楚起見。

我記得測試了這些效率,並且對於小陣列性能的差異很小。對於大型數組,返回參考的速度更快。

這對於小型結果來說確實是一件方便的事情。你寧願這樣做:

($foo,$bar) = barbaz(); 

後的迴歸:

$foobar = barbaz(); 
$foobar->[0]; # $foo 
$foobar->[1]; # $bar 

另一種方法返回一個參考:

($foo,$bar) = @{barbaz()}; 

作爲一項規則,一旦你決定要往哪個方向走,只是爲你保留模塊,因爲它使從一種方法切換到另一種方法變得困惑。

我通常返回相似事物列表的數組引用,以及響應由兩到四個不同元素組成的數組。除此之外,我做了一個散列,因爲並不是所有的調用者都會關心所有的響應元素。

+0

已投票,但很少挑剔:我會`($ foo,$ bar)= @ {barbaz()}` – kmkaplan 2009-02-15 14:25:07

0

當你習慣使用代碼作爲第一個片段中Mathieu Longtinanswer你必須編寫醜陋的代碼作爲第二片斷或這與其說是更好的代碼:

my ($foo,$bar) = @{barbaz()}; 

我覺得這是最大的缺點返回時引用而不是數組。如果我想返回少量不同種類的值。我習慣於返回數組並直接賦值給變量(例如,用於在Python中執行的操作)。

my ($status, $result) = do_something(); 
if ($status eq 'OK') { 
    ... 

如果值的量越大以及各種種類我用於返回哈希REF(更好地爲重構)

my ($status, $data, $foo, $bar, $baz) = 
    @{do_something()}{qw(status data foo bar baz)}; 
if ($status eq 'OK') { 
    ... 

如果返回值是相同種類的,比陣列或陣列的返回根據金額的不同,ref值得商榷。

1

我不認爲你應該只會使用一種或兩種方法。但是,您應該保持每個模塊或一組模塊的一致性。

這裏有一些例子來思考空間:

sub test1{ 
    my @arr; 
    return @arr; 
} 
sub test2{ 
    my @arr; 
    return @arr if wantarray; 
    return \@arr; 
} 
sub test3{ 
    my %hash; 
    return %hash; 
} 
sub test4{ 
    my %hash; 
    return %hash if wantarray; 
    return \%hash; 
} 
sub test5{ 
    my %hash; 
    return $hash{ qw'one two three' } if wantarray; 
    return \%hash; 
} 
{ 
    package test; 
    use Devel::Caller qw'called_as_method'; 
    sub test6{ 
    my $out; 
    if(wantarray){ 
     $out = 'list'; 
    }else{ 
     $out = 'scalar'; 
    } 
    $out = "call in $out context"; 
    if(called_as_method){ 
     $out = "method $out"; 
    }else{ 
     $out = "simple function $out"; 
    } 
    return $out; 
    } 
} 

我可以看到有可能在未來的項目中使用的許多信息,但其中一些是相當無意義。

0

返回一個數組給出了一些很好的優點:

my @foo = get_array(); # Get list and assign to array. 
my $foo = get_array(); # Get magnitude of list. 
my ($f1, $f2) = get_array(); # Get first two members of list. 
my ($f3,$f6) = (get_array())[3,6]; # Get specific members of the list. 

sub get_array { 
    my @array = 0..9; 

    return @array; 
} 

如果返回數組裁判,你必須寫一些潛艇做同樣的工作。另外,一個空數組在一個布爾上下文中返回false,但是一個空數組ref不會。

if (get_array()) { 
    do_stuff(); 
} 

如果返回數組裁判,那麼你要做的:

if (@{ get_array_ref() }) { 
    do_stuff(); 
} 

除非get_array_ref()未能返回一個引用,說來代替,而民主基金價值,你有一個程序停止崩潰。下列情況之一會有所幫助:

if (@{ get_array() || [] }) { 
    do_stuff(); 
} 

if (eval{ @{get_array()} }) { 
    do_stuff(); 
} 

因此,如果需要的速度好處,或者如果你需要一個數組引用(也許你希望允許對象的集合元素的直接操作 - 呸,但有時它一定會發生),返回一個數組ref。否則,我發現標準陣列的好處值得保留。

更新:記住從例程返回的內容並不總是數組或列表。您返回的內容是return後面的內容,或上次操作的結果。您的返回值將在上下文中進行評估。大多數情況下,一切都會好起來的,但有時你會得到意想不到的行爲。

sub foo { 
    return $_[0]..$_[1]; 
} 

my $a = foo(9,20); 
my @a = foo(9,20); 

print "$a\n"; 
print "@a\n"; 

比較:

sub foo { 
    my @foo = ($_[0]..$_[1]); 
    return @foo; 
} 

my $a = foo(9,20); 
my @a = foo(9,20); 

print "$a\n"; 
print "@a\n"; 

所以,當你說「返回數組」可以肯定你真的是「返回數組」。注意你從例程中返回的東西。

0

有沒有原因我不應該這樣做,即使是小的結果?

沒有perl特定的原因,這意味着返回對本地數組的引用是正確和高效的。唯一的缺點是調用函數的人必須處理返回的數組ref,並使用箭頭->或取消引用等訪問元素。因此,調用者稍微麻煩一點。

2

如果數組是在函數內部構造的,則沒有理由返回數組;只是返回一個引用,因爲調用者保證只有一個副本(它剛剛創建)。

如果函數正在考慮一組全局數組並返回它們中的一個,那麼如果調用者不會修改它,則可以返回一個引用。如果調用者可能修改陣列,並且這不是,那麼函數應該返回一個副本。

這確實是一個獨特的Perl問題。在Java中,您總是返回一個引用,並且該函數可以通過最終化數組及其包含的數據來防止數組被修改(如果這是您的目標)。在python引用被返回並且沒有辦法阻止它們被修改;如果這很重要,則會返回對副本的引用。

1

我只想對關於處理數組引用的笨拙語法的想法發表評論,而不是列表。正如布賴恩所說,如果系統的其他部分使用列表,你真的不應該這樣做。在大多數情況下這是不必要的優化。

但是,如果情況並非如此,並且您可以自由創建自己的風格,那麼可以使編碼減少臭味的一件事是使用autobox。通過編碼像這樣

my ($name, $number) = @{ $obj->get_arrayref() }; 

autobox變成SCALARARRAYHASH(以及others)到 「包」,這樣你可以編寫代碼:

my ($name, $number) = $obj->get_arrayref()->items(0, 1); 

,而不是稍微笨拙:

sub ARRAY::slice { 
    my $arr_ref = shift; 
    my $length = @$arr_ref; 
    my @subs = map { abs($_) < $length ? $_ : $_ < 0 ? 0 : $#$arr_ref } @_; 
    given (scalar @subs) { 
     when (0) { return $arr_ref; } 
     when (2) { return [ @{$arr_ref}[ $subs[0]..$subs[1] ] ]; } 
     default { return [ @{$arr_ref}[ @subs ] ]; } 
    } 
    return $arr_ref; # should not get here. 
} 

sub ARRAY::items { return @{ &ARRAY::slice }; } 

請記住,autobox要求您實現所有的行爲ÿ你想從這些。 $arr_ref->pop()沒有工作,直到你定義sub ARRAY::pop除非你用autobox::Core

6

我會從這裏the other question複製我的答案的相關部分。

經常忽略的第二個考慮因素是接口。如何使用返回的數組?這很重要,因爲整個數組解引用在Perl中有點糟糕。例如:

for my $info (@{ getInfo($some, $args) }) { 
    ... 
} 

這很醜。這好多了。

for my $info (getInfo($some, $args)) { 
    ... 
} 

它也適用於映射和grepping。

my @info = grep { ... } getInfo($some, $args); 

但是,如果你要挑出單個元素返回一個數組引用都可以得心應手:

my $address = getInfo($some, $args)->[2]; 

這比簡單:

my $address = (getInfo($some, $args))[2]; 

或者:

my @info = getInfo($some, $args); 
my $address = $info[2]; 

但是在那個時候,你笑uld質疑@info是否真的是一個列表或一個散列。

my $address = getInfo($some, $args)->{address}; 

與數組vs數組引用不同,沒有必要選擇通過散列引用返回散列。像上面的代碼一樣,哈希引用允許方便的簡寫。與數組vs對照相比,它使得迭代器的情況更簡單,或者至少避免了中間變量。

for my $key (keys %{some_func_that_returns_a_hash_ref}) { 
    ... 
} 

你不應該做的是有getInfo()返回標量上下文中的數組引用,並在列表環境的數組。這混淆了數組長度的標量上下文的傳統用法,這會讓用戶感到驚訝。

我想補充一點,儘管一切都做得很好,X是一個很好的經驗法則,但在設計一個好的界面時它並不是最重要的。有點過頭了,你可以很容易地滾動其他更重要的問題。

最後,我會插入我自己的模塊,Method::Signatures,因爲它提供了一個妥協方法來傳遞數組引用,而不必使用數組ref參數。

use Method::Signatures; 

method foo(\@args) { 
    print "@args";  # @args is not a copy 
    push @args, 42; # this alters the caller array 
} 

my @nums = (1,2,3); 
Class->foo(\@nums); # prints 1 2 3 
print "@nums";  # prints 1 2 3 42 

這是通過Data::Alias的魔法完成的。

1

上述答案中的一個重要的省略:不返回對私人數據的引用!

例如:

package MyClass; 

sub new { 
    my($class) = @_; 
    bless { _things => [] } => $class; 
} 

sub add_things { 
    my $self = shift; 
    push @{ $self->{_things} } => @_; 
} 

sub things { 
    my($self) = @_; 
    $self->{_things}; # NO! 
} 

是,用戶可以直接用Perl引擎蓋下偷看對象來實現這種方式,但不很容易讓用戶在不知不覺中拍攝自己的腳,例如

my $obj = MyClass->new; 
$obj->add_things(1 .. 3); 

...; 

my $things = $obj->things; 
my $first = shift @$things; 

更好的將是

sub things { 
    my($self) = @_; 
    @{ $self->{_things} }; 
} 
0

由於沒有人提到大約wantarray來回報您的私人數據(也許深)複印件,我將:-)

我認爲一個好的做法是讓調用者決定它想要的結果的上下文。例如,在下面的代碼中,你問perl該子例程被調用的上下文,並決定返回什麼。

sub get_things { 
    my @things; 
    ... # populate things 
    return wantarray ? @things : \@things; 
} 

然後

for my $thing (get_things()) { 
    ... 
} 

my @things = get_things(); 

工作正常,因爲列表環境,並且:

my $things = get_things(); 

將返回數組的參考。

欲瞭解更多有關wantarray的信息,你可能需要檢查perldoc -f wantarray

編輯:我過瞻第一答案,其中提到wantarray之一,但我認爲這是答案仍然是有效的,因爲它使得它更清楚一點。

相關問題