Năng suất có nghĩa là gì trong PHP?


232

Gần đây tôi đã vấp phải mã này:

function xrange($min, $max) 
{
    for ($i = $min; $i <= $max; $i++) {
        yield $i;
    }
}

Tôi chưa bao giờ thấy yieldtừ khóa này trước đây. Đang cố chạy mã tôi nhận được

Lỗi phân tích cú pháp: lỗi cú pháp, T_VARIABLE không mong muốn trên dòng x

Vậy yieldtừ khóa này là gì? Nó có hợp lệ không? Và nếu có, làm thế nào để tôi sử dụng nó?

Câu trả lời:


355

yield

Các yieldtừ khóa trở lại dữ liệu từ một chức năng máy phát điện:

Trung tâm của hàm tạo là từ khóa suất. Ở dạng đơn giản nhất, một câu lệnh lợi tức trông giống như một câu lệnh return, ngoại trừ việc thay vì dừng thực thi hàm và trả về, thì thay vào đó, cung cấp một giá trị cho vòng lặp mã trên trình tạo và tạm dừng thực thi hàm tạo.

Hàm tạo là gì?

Hàm tạo là một cách hiệu quả và nhỏ gọn hơn để viết Iterator . Nó cho phép bạn xác định một hàm (của bạn xrange) sẽ tính toán và trả về các giá trị trong khi bạn đang lặp qua nó :

foreach (xrange(1, 10) as $key => $value) {
    echo "$key => $value", PHP_EOL;
}

Điều này sẽ tạo ra đầu ra sau đây:

0 => 1
1 => 2

9 => 10

Bạn cũng có thể kiểm soát $keytrong foreachbằng cách sử dụng

yield $someKey => $someValue;

Trong hàm tạo, $someKeylà bất cứ thứ gì bạn muốn xuất hiện $key$someValuelà giá trị trong $val. Trong ví dụ của câu hỏi đó $i.

Sự khác biệt của các chức năng bình thường là gì?

Bây giờ bạn có thể tự hỏi tại sao chúng ta không chỉ đơn giản sử dụng rangehàm riêng của PHP để đạt được đầu ra đó. Và đúng là bạn. Đầu ra sẽ giống nhau. Sự khác biệt là cách chúng tôi đến đó.

Khi chúng tôi sử dụng rangePHP, sẽ thực hiện nó, tạo ra toàn bộ mảng các số trong bộ nhớ và returnrằng toàn bộ mảng với foreachvòng lặp mà sau đó sẽ đi qua nó và đầu ra các giá trị. Nói cách khác, ý foreachchí sẽ hoạt động trên chính mảng. Các rangechức năng và foreachchỉ "nói chuyện" một lần. Hãy nghĩ về nó giống như nhận được một gói trong thư. Người giao hàng sẽ đưa cho bạn gói hàng và rời đi. Và sau đó bạn mở gói toàn bộ, lấy ra bất cứ thứ gì trong đó.

Khi chúng ta sử dụng hàm tạo, PHP sẽ bước vào hàm và thực thi nó cho đến khi nó đáp ứng được kết thúc hoặc một yieldtừ khóa. Khi nó gặp a yield, sau đó nó sẽ trả lại bất cứ giá trị nào tại thời điểm đó cho vòng lặp bên ngoài. Sau đó, nó quay trở lại chức năng máy phát điện và tiếp tục từ nơi nó mang lại. Vì bạn xrangegiữ một forvòng lặp, nó sẽ thực thi và mang lại cho đến khi $maxđạt được. Hãy nghĩ về nó giống như foreachvà máy phát điện chơi bóng bàn.

Tại sao tôi cần điều đó?

Rõ ràng, máy phát điện có thể được sử dụng để làm việc xung quanh giới hạn bộ nhớ. Tùy thuộc vào môi trường của bạn, việc thực hiện range(1, 1000000)sẽ gây tử vong cho tập lệnh của bạn trong khi tương tự với trình tạo sẽ hoạt động tốt. Hoặc như Wikipedia đặt nó:

Bởi vì các máy phát điện chỉ tính toán các giá trị mang lại của chúng theo yêu cầu, chúng rất hữu ích trong việc biểu diễn các chuỗi sẽ tốn kém hoặc không thể tính toán cùng một lúc. Chúng bao gồm ví dụ như chuỗi vô hạn và luồng dữ liệu trực tiếp.

Máy phát điện cũng được cho là khá nhanh. Nhưng hãy nhớ rằng khi chúng ta nói về nhanh, chúng ta thường nói với số lượng rất nhỏ. Vì vậy, trước khi bạn chạy đi và thay đổi tất cả mã của bạn để sử dụng các trình tạo, hãy làm một điểm chuẩn để xem nó có ý nghĩa như thế nào.

Trường hợp sử dụng khác cho Máy phát điện là coroutines không đồng bộ. Các yieldtừ khóa không chỉ trả lại giá trị nhưng nó cũng chấp nhận chúng. Để biết chi tiết về điều này, xem hai bài viết blog tuyệt vời được liên kết dưới đây.

Từ khi nào tôi có thể sử dụng yield?

Các trình tạo đã được giới thiệu trong PHP 5.5 . Cố gắng sử dụng yieldtrước phiên bản đó sẽ dẫn đến các lỗi phân tích khác nhau, tùy thuộc vào mã theo từ khóa. Vì vậy, nếu bạn gặp lỗi phân tích cú pháp từ mã đó, hãy cập nhật PHP của bạn.

Nguồn và đọc thêm:


1
Xin hãy giải thích những lợi ích của yeildnó đã qua, giả sử, một giải pháp như thế này: ideone.com/xgqevM
Mike

1
Ah, tốt, và các thông báo tôi đã tạo ra. Huh. Chà, tôi đã thử nghiệm mô phỏng Trình tạo cho PHP> = 5.0.0 với lớp trình trợ giúp và vâng, hơi khó đọc hơn, nhưng tôi có thể sử dụng điều này trong tương lai. Chủ đề thú vị. Cảm ơn!
Mike

Không đọc được nhưng sử dụng bộ nhớ! So sánh bộ nhớ đã sử dụng để lặp lại return range(1,100000000)for ($i=0; $i<100000000; $i++) yield $i
emix 6/10/2015

@mike vâng, điều đó đã được giải thích trong câu trả lời của tôi. Trong bộ nhớ ví dụ khác của Mike hầu như không phải là vấn đề vì anh ta chỉ lặp lại 10 giá trị.
Gordon

1
@Mike Một vấn đề với xrange đó là việc sử dụng các giới hạn thống kê của nó là hữu ích cho việc lồng nhau (ví dụ: tìm kiếm trên một đa tạp n chiều, hoặc một quicksort đệ quy bằng cách sử dụng máy phát điện chẳng hạn). Bạn không thể lồng các vòng lặp xrange vì chỉ có một phiên bản duy nhất của bộ đếm. Phiên bản Yield không gặp phải vấn đề này.
Shayne

43

Hàm này đang sử dụng năng suất:

function a($items) {
    foreach ($items as $item) {
        yield $item + 1;
    }
}

gần giống như cái này mà không có:

function b($items) {
    $result = [];
    foreach ($items as $item) {
        $result[] = $item + 1;
    }
    return $result;
}

Sự khác biệt duy nhất là a()trả về một trình tạob()chỉ là một mảng đơn giản. Bạn có thể lặp lại trên cả hai.

Ngoài ra, cái đầu tiên không phân bổ một mảng đầy đủ và do đó ít đòi hỏi bộ nhớ hơn.


2
addt ghi chú từ các tài liệu chính thức: Trong PHP 5, một trình tạo không thể trả về giá trị: làm như vậy sẽ dẫn đến lỗi biên dịch. Một tuyên bố trả về trống là cú pháp hợp lệ trong một trình tạo và nó sẽ chấm dứt trình tạo. Kể từ PHP 7.0, trình tạo có thể trả về các giá trị, có thể được truy xuất bằng Trình tạo :: getReturn (). php.net/manual/en/language.generators.syntax.php
Programmer Dancuk

Đơn giản và súc tích.
John Miller

24

ví dụ đơn giản

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $v)
    echo $v.',';
echo '#end main#';
?>

đầu ra

#start main# {start[1,2,3,4,5,6,7,8,9,]end} #end main#

ví dụ nâng cao

<?php
echo '#start main# ';
function a(){
    echo '{start[';
    for($i=1; $i<=9; $i++)
        yield $i;
    echo ']end} ';
}
foreach(a() as $k => $v){
    if($k === 5)
        break;
    echo $k.'=>'.$v.',';
}
echo '#end main#';
?>

đầu ra

#start main# {start[0=>1,1=>2,2=>3,3=>4,4=>5,#end main#

Vì vậy, nó trở lại mà không làm gián đoạn chức năng?
Lucas Bustamante

22

yieldtừ khóa phục vụ cho định nghĩa của "trình tạo" trong PHP 5.5. Ok, máy phát điện là gì?

Từ php.net:

Các trình tạo cung cấp một cách dễ dàng để thực hiện các trình vòng lặp đơn giản mà không cần chi phí cao hoặc phức tạp khi thực hiện một lớp thực hiện giao diện Iterator.

Trình tạo cho phép bạn viết mã sử dụng foreach để lặp lại một tập hợp dữ liệu mà không cần xây dựng một mảng trong bộ nhớ, điều này có thể khiến bạn vượt quá giới hạn bộ nhớ hoặc cần một lượng thời gian xử lý đáng kể để tạo. Thay vào đó, bạn có thể viết một hàm tạo, giống như một hàm bình thường, ngoại trừ việc thay vì quay lại một lần, một trình tạo có thể mang lại số lần cần thiết để cung cấp các giá trị được lặp đi lặp lại.

Từ nơi này: máy phát điện = máy phát điện, các chức năng khác (chỉ là một chức năng đơn giản) = chức năng.

Vì vậy, chúng rất hữu ích khi:

  • bạn cần làm những việc đơn giản (hoặc những điều đơn giản);

    Trình tạo thực sự đơn giản hơn nhiều sau đó thực hiện giao diện Iterator. Mặt khác, nguồn, là máy phát điện ít chức năng hơn. so sánh chúng .

  • bạn cần tạo ra lượng dữ liệu LỚN - tiết kiệm bộ nhớ;

    thực sự để tiết kiệm bộ nhớ, chúng ta chỉ có thể tạo dữ liệu cần thiết thông qua các hàm cho mỗi lần lặp và sau khi lặp lại sử dụng rác. Vì vậy, ở đây điểm chính là - mã rõ ràng và có thể hiệu suất. xem những gì tốt hơn cho nhu cầu của bạn.

  • bạn cần tạo chuỗi, phụ thuộc vào các giá trị trung gian;

    điều này đang mở rộng suy nghĩ trước đó. máy phát điện có thể làm cho mọi thứ dễ dàng hơn so với các chức năng. kiểm tra ví dụ Fibonacci và thử tạo chuỗi mà không có trình tạo. Ngoài ra, máy phát điện có thể hoạt động nhanh hơn trong trường hợp này, ít nhất là vì lưu trữ các giá trị trung gian trong các biến cục bộ;

  • bạn cần cải thiện hiệu suất.

    chúng có thể hoạt động nhanh hơn sau đó hoạt động trong một số trường hợp (xem lợi ích trước đó);


1
Tôi không hiểu làm thế nào máy phát điện làm việc. lớp này thực hiện giao diện iterator. từ những gì tôi biết, các lớp lặp cho phép tôi định cấu hình cách tôi muốn lặp qua một đối tượng. ví dụ, ArrayIterator lấy một mảng hoặc đối tượng để tôi có thể sửa đổi các giá trị và khóa trong khi lặp lại nó. Vậy nếu các trình lặp có được toàn bộ đối tượng / mảng thì làm thế nào để trình tạo không phải xây dựng toàn bộ mảng trong bộ nhớ ???
dùng3021621

7

Với yieldbạn có thể dễ dàng mô tả các điểm dừng giữa nhiều tác vụ trong một chức năng. Đó là tất cả, không có gì đặc biệt về nó.

$closure = function ($injected1, $injected2, ...){
    $returned = array();
    //task1 on $injected1
    $returned[] = $returned1;
//I need a breakpoint here!!!!!!!!!!!!!!!!!!!!!!!!!
    //task2 on $injected2
    $returned[] = $returned2;
    //...
    return $returned;
};
$returned = $closure($injected1, $injected2, ...);

Nếu task1 và task2 có liên quan cao, nhưng bạn cần một điểm dừng giữa chúng để làm việc khác:

  • bộ nhớ trống giữa các hàng cơ sở dữ liệu xử lý
  • chạy các tác vụ khác cung cấp sự phụ thuộc vào tác vụ tiếp theo, nhưng không liên quan bằng cách hiểu mã hiện tại
  • thực hiện cuộc gọi không đồng bộ và chờ kết quả
  • và như thế ...

sau đó, trình tạo là giải pháp tốt nhất, vì bạn không phải chia mã của mình thành nhiều lần đóng hoặc trộn mã đó với mã khác hoặc sử dụng các cuộc gọi lại, v.v ... Bạn chỉ cần sử dụng yieldđể thêm điểm dừng và bạn có thể tiếp tục từ đó điểm dừng nếu bạn đã sẵn sàng.

Thêm điểm dừng mà không có máy phát điện:

$closure1 = function ($injected1){
    //task1 on $injected1
    return $returned1;
};
$closure2 = function ($injected2){
    //task2 on $injected2
    return $returned1;
};
//...
$returned1 = $closure1($injected1);
//breakpoint between task1 and task2
$returned2 = $closure2($injected2);
//...

Thêm điểm dừng với máy phát điện

$closure = function (){
    $injected1 = yield;
    //task1 on $injected1
    $injected2 = (yield($returned1));
    //task2 on $injected2
    $injected3 = (yield($returned2));
    //...
    yield($returnedN);
};
$generator = $closure();
$returned1 = $generator->send($injected1);
//breakpoint between task1 and task2
$returned2 = $generator->send($injected2);
//...
$returnedN = $generator->send($injectedN);

lưu ý: Rất dễ mắc lỗi với máy phát điện, vì vậy hãy luôn viết bài kiểm tra đơn vị trước khi bạn thực hiện chúng! lưu ý2: Sử dụng các trình tạo trong một vòng lặp vô hạn cũng giống như viết một bao đóng có độ dài vô hạn ...


4

Không có câu trả lời nào ở trên cho thấy một ví dụ cụ thể bằng cách sử dụng các mảng lớn được điền bởi các thành viên không phải là số. Dưới đây là một ví dụ sử dụng một mảng được tạo bởi explode()trên một tệp .txt lớn (262 MB trong trường hợp sử dụng của tôi):

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

$path = './file.txt';
$content = file_get_contents($path);

foreach(explode("\n", $content) as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Đầu ra là:

Starting memory usage: 415160
Final memory usage: 270948256

Bây giờ so sánh nó với một tập lệnh tương tự, sử dụng yieldtừ khóa:

<?php

ini_set('memory_limit','1000M');

echo "Starting memory usage: " . memory_get_usage() . "<br>";

function x() {
    $path = './file.txt';
    $content = file_get_contents($path);
    foreach(explode("\n", $content) as $x) {
        yield $x;
    }
}

foreach(x() as $ex) {
    $ex = trim($ex);
}

echo "Final memory usage: " . memory_get_usage();

Đầu ra cho kịch bản này là:

Starting memory usage: 415152
Final memory usage: 415616

Tiết kiệm sử dụng bộ nhớ rõ ràng là đáng kể (emMemoryUsage -----> ~ 270,5 MB trong ví dụ đầu tiên, ~ 450B trong ví dụ thứ hai).


3

Một khía cạnh thú vị, đáng để thảo luận ở đây, là năng suất bằng cách tham khảo . Mỗi lần chúng ta cần thay đổi một tham số sao cho nó được phản ánh bên ngoài hàm, chúng ta phải truyền tham số này bằng tham chiếu. Để áp dụng điều này cho các trình tạo, chúng ta chỉ cần thêm một dấu và &vào tên của trình tạo và cho biến được sử dụng trong lần lặp:

 <?php 
 /**
 * Yields by reference.
 * @param int $from
 */
function &counter($from) {
    while ($from > 0) {
        yield $from;
    }
}

foreach (counter(100) as &$value) {
    $value--;
    echo $value . '...';
}

// Output: 99...98...97...96...95...

Ví dụ trên cho thấy cách thay đổi các giá trị lặp trong foreachvòng lặp thay đổi $frombiến trong trình tạo. Điều này là do $fromđược mang lại bởi tham chiếu do ký hiệu và trước tên máy phát. Do đó, $valuebiến trong foreachvòng lặp là tham chiếu đến $frombiến trong hàm tạo.


0

Đoạn mã dưới đây minh họa cách sử dụng trình tạo trả về kết quả trước khi hoàn thành, không giống như cách tiếp cận không tạo trình tạo truyền thống trả về một mảng hoàn chỉnh sau khi lặp lại đầy đủ. Với trình tạo bên dưới, các giá trị được trả về khi sẵn sàng, không cần đợi một mảng được điền đầy đủ:

<?php 

function sleepiterate($length) {
    for ($i=0; $i < $length; $i++) {
        sleep(2);
        yield $i;
    }
}

foreach (sleepiterate(5) as $i) {
    echo $i, PHP_EOL;
}

Vì vậy, không thể sử dụng năng suất để tạo mã html trong php? Tôi không biết những lợi ích trong một môi trường thực tế
Giuseppe Lodi Rizzini

@GiuseppeLodiRizzini điều gì khiến bạn nghĩ vậy?
Brad Kent
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.