PHP에서 yield는 무엇을 의미합니까?


232

최근 에이 코드를 우연히 발견했습니다.

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

yield키워드를 본 적이 없습니다 . 내가 얻은 코드를 실행하려고합니다.

구문 분석 오류 : 구문 오류, x 행의 예기치 않은 T_VARIABLE

yield키워드 는 무엇 입니까? 유효한 PHP입니까? 그리고 그렇다면 어떻게 사용합니까?

답변:


355

무엇입니까 yield?

yield키워드 발전기 기능에서 데이터를 반환합니다 :

생성기 함수의 핵심은 yield 키워드입니다. 가장 간단한 형식으로, yield 문은 함수의 실행을 중지하고 반환하는 대신 yield가 대신 생성자를 반복하는 코드에 값을 제공하고 생성기 함수의 실행을 일시 중지한다는 점을 제외하면 return 문과 매우 유사합니다.

발전기 기능이란 무엇입니까?

제너레이터 함수는 효과적으로 Iterator 를 작성하는보다 간결하고 효율적인 방법 입니다. 그것은 당신이 함수 (사용자 정의 할 수 있습니다 xrange것) 계산하고 반환동안 당신이 그것을 통해 루프를 :

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

다음과 같은 출력이 생성됩니다.

0 => 1
1 => 2

9 => 10

또한 제어 할 수 있습니다 $key에서를 foreach사용하여

yield $someKey => $someValue;

발전기 기능에, $someKey당신이 나타납니다 원하는대로입니다 $key$someValue의 가치 $val. 질문의 예에서는 $i입니다.

일반 기능과의 차이점은 무엇입니까?

이제 왜 우리가 단순히 그 출력을 달성하기 위해 PHP의 원시 range함수 를 사용하지 않는지 궁금 할 것 입니다. 그리고 바로 당신입니다. 출력은 동일합니다. 차이점은 우리가 어떻게 도착했는지입니다.

우리가 rangePHP 를 사용할 때 , 그것을 실행하고, 메모리에 숫자의 전체 배열을 만들고 return전체 배열foreach루프로 만든 다음 그것을지나 값을 출력합니다. 즉, foreach배열 자체에서 작동합니다. range기능과 foreach한 번만 "대화". 우편물에 패키지를 넣는 것처럼 생각하십시오. 배달원이 패키지를 건네고 떠납니다. 그런 다음 전체 패키지를 풀고 거기에있는 것을 꺼내십시오.

생성기 함수를 사용할 때 PHP는 함수로 들어가서 끝이나 yield키워드에 도달 할 때까지 실행합니다 . a를 만나면 yield그 당시의 값이 무엇이든 외부 루프로 반환합니다. 그런 다음 생성기 기능으로 돌아가서 생성 된 위치부터 계속됩니다. 루프를 xrange보유하고 있기 때문에 for루프가 실행되고 $max도달 할 때까지 생성 됩니다. foreach탁구 를 만드는 발전기와 같은 것을 생각해보십시오 .

왜 그런가요?

분명히, 생성기를 사용하여 메모리 제한을 해결할 수 있습니다. 환경에 따라 range(1, 1000000)유언장을 작성하면 스크립트가 치명적이지만 생성기와 동일하게 작동합니다. 또는 Wikipedia는 다음과 같이 말합니다.

생성기는 요청시에만 산출 된 값을 계산하므로 비용이 많이 들거나 한 번에 계산할 수없는 시퀀스를 나타내는 데 유용합니다. 여기에는 무한 시퀀스 및 라이브 데이터 스트림이 포함됩니다.

발전기도 꽤 빠르다. 그러나 우리가 빨리 이야기 할 때, 우리는 보통 아주 적은 숫자로 이야기하고 있다는 것을 명심하십시오. 따라서 이제 생성기를 사용하기 위해 모든 코드를 변경하고 변경하기 전에 벤치 마크를 수행하여 해당 위치를 파악하십시오.

생성기의 또 다른 사용 사례는 비동기 코 루틴입니다. yield키워드는 값을 반환하지 않습니다뿐만 아니라 그들을 받아 들인다. 이에 대한 자세한 내용은 아래 링크 된 두 개의 우수한 블로그 게시물을 참조하십시오.

언제부터 사용할 수 yield있습니까?

PHP 5.5 에서 생성기가 도입되었습니다 . yield해당 버전 이전 에 사용하려고 하면 키워드 다음에 오는 코드에 따라 다양한 구문 분석 오류가 발생합니다. 따라서 해당 코드에서 구문 분석 오류가 발생하면 PHP를 업데이트하십시오.

출처와 추가 자료 :



1
아, 글쎄, 내가 생성하는 통지. 허. 글쎄, 헬퍼 클래스로 PHP> = 5.0.0 용 생성기를 에뮬레이트하는 실험을 해 보았습니다. 약간 읽기 쉽지는 않지만 나중에 사용할 수 있습니다. 흥미로운 주제. 감사!
Mike

가독성이 아니라 메모리 사용량! 반복 return range(1,100000000)과 사용한 메모리 비교for ($i=0; $i<100000000; $i++) yield $i
emix

@ mike 예, 그것은 이미 내 대답에 설명되어 있습니다. 다른 Mike의 예제에서 메모리는 10 개의 값만 반복하기 때문에 거의 문제가되지 않습니다.
Gordon

1
@Mike xrange의 한 가지 문제는 정적 한계의 사용이 예를 들어 중첩에 유용하다는 것입니다 (예 : n 차원 매니 폴드를 검색하거나 생성기를 사용하는 재귀 퀵 정렬). 카운터의 인스턴스가 하나뿐이므로 xrange 루프를 중첩 할 수 없습니다. 수율 버전에서는이 문제가 발생하지 않습니다.
Shayne

43

이 함수는 yield를 사용하고 있습니다 :

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

없이이 것과 거의 동일합니다 :

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

유일한 차이점은 생성기 와 간단한 배열 만 a()반환 한다는 것 입니다. 둘 다 반복 할 수 있습니다.b()

또한 첫 번째 배열은 전체 배열을 할당하지 않으므로 메모리 요구량이 적습니다.


2
공식 문서의 addt 참고 사항 : PHP 5에서는 생성기가 값을 반환 할 수 없습니다. 그렇게하면 컴파일 오류가 발생합니다. 빈 return 문은 생성기 내에서 유효한 구문이며 생성기를 종료합니다. PHP 7.0부터 Generator는 Generator :: getReturn ()을 사용하여 검색 할 수있는 값을 반환 할 수 있습니다. php.net/manual/en/language.generators.syntax.php
프로그래머 Dancuk

간단하고 간결합니다.
John Miller

24

간단한 예

<?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#';
?>

산출

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

고급 예

<?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#';
?>

산출

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

따라서 함수를 중단하지 않고 반환합니까?
Lucas Bustamante

22

yield키워드는 PHP 5.5에서 "제너레이터"의 정의를 제공합니다. 그렇다면 발전기 는 무엇 입니까?

php.net에서 :

생성기는 반복자 인터페이스를 구현하는 클래스를 구현하는 오버 헤드 나 복잡성없이 간단한 반복자를 쉽게 구현할 수있는 방법을 제공합니다.

생성기를 사용하면 메모리에 배열을 만들 필요없이 foreach를 사용하여 데이터 집합을 반복하는 코드를 작성할 수 있으므로 메모리 제한을 초과하거나 생성하는 데 상당한 시간이 소요될 수 있습니다. 대신 일반 함수와 동일한 생성기 함수를 작성할 수 있습니다. 단, 한 번만 리턴하는 대신 반복되는 값을 제공하기 위해 생성기가 필요한 횟수만큼 생성 할 수 있습니다.

이 장소에서 : 생성기 = 생성기, 다른 기능 (단순한 기능) = 기능.

따라서 다음과 같은 경우에 유용합니다.

  • 간단한 일 (또는 간단한 일)을해야합니다.

    generator는 Iterator 인터페이스를 구현하는 것보다 훨씬 간단합니다. 다른 한편으로, 발전기는 기능이 떨어진다는 것은 당연하다. 그들을 비교하십시오 .

  • 당신은 큰 양의 데이터를 생성해야합니다-메모리 절약;

    실제로 메모리를 절약하기 위해 모든 루프 반복에 대한 함수를 통해 필요한 데이터를 생성 할 수 있으며 반복 후 가비지를 사용합니다. 여기서 주요 요점은 명확한 코드와 아마도 성능입니다. 당신의 요구에 더 나은 것을 참조하십시오.

  • 중간 값에 따라 시퀀스를 생성해야합니다.

    이것은 이전 생각의 연장입니다. 제너레이터는 기능과 비교하여 작업을 더 쉽게 만들 수 있습니다. 피보나치 예제를 확인 하고 발전기없이 시퀀스를 만드십시오. 또한 로컬 변수에 중간 값을 저장하기 때문에 발전기가 더 빨리 작동 할 수 있습니다.

  • 성능을 향상시켜야합니다.

    어떤 경우에는 기능보다 빠르게 작동 할 수 있습니다 (이전 이점 참조).


1
발전기가 어떻게 작동하는지 이해하지 못했습니다. 이 클래스는 반복자 인터페이스를 구현합니다. 반복자 클래스를 사용하면 객체를 반복하는 방법을 구성 할 수 있습니다. 예를 들어 ArrayIterator는 배열이나 객체를 가져 와서 반복하면서 값과 키를 수정할 수 있습니다. 따라서 반복자가 전체 객체 / 배열을 가져 오면 생성기는 어떻게 메모리에 전체 배열을 만들 필요가 없습니까 ???
user3021621

7

함께 yield하면 쉽게 하나의 함수에 여러 작업 사이의 중단 점을 설명 할 수 있습니다. 그게 다입니다. 특별한 것은 없습니다.

$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, ...);

task1과 task2가 관련성이 높지만 다른 작업을 수행하려면 이들 사이에 중단 점이 필요합니다.

  • 데이터베이스 행 처리 사이의 사용 가능한 메모리
  • 다음 작업에 대한 의존성을 제공하지만 현재 코드를 이해하여 관련이없는 다른 작업을 실행
  • 비동기 호출을 수행하고 결과를 기다립니다
  • 등등 ...

생성기를 사용하는 것이 가장 좋습니다. 코드를 여러 개의 클로저로 나누거나 다른 코드와 혼합하거나 콜백 등을 사용할 필요가 없기 때문입니다 yield. 중단 점을 추가하는 데만 사용 하면됩니다. 준비가되면 중단 점.

생성기없이 중단 점 추가 :

$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);
//...

생성기로 중단 점 추가

$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);

참고 : 생성기에서 실수를 저지르기 쉽기 때문에 구현하기 전에 항상 단위 테스트를 작성하십시오! 참고 2 : 무한 루프에서 생성기를 사용하는 것은 길이가 무한한 클로저를 작성하는 것과 같습니다.


4

위의 답변 중 어느 것도 숫자가 아닌 멤버로 채워진 대규모 배열을 사용하는 구체적인 예를 보여주지 않습니다. 다음은 explode()큰 .txt 파일에서 생성 된 배열을 사용하는 예입니다 (사용 사례에서는 262MB).

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

결과는 다음과 같습니다.

Starting memory usage: 415160
Final memory usage: 270948256

이제 yield키워드를 사용하여 비슷한 스크립트와 비교하십시오 .

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

이 스크립트의 출력은 다음과 같습니다.

Starting memory usage: 415152
Final memory usage: 415616

분명히 메모리 사용량이 상당히 절감되었습니다 ( 첫 번째 예에서는 ΔMemoryUsage -----> ~ 270.5MB , 두 번째 예에서는 ~ 450B ).


3

여기서 논의 할 가치가있는 흥미로운 측면은 참조산출하는 것 입니다. 함수 외부에 반영되도록 매개 변수를 변경해야 할 때마다이 매개 변수를 참조로 전달해야합니다. 이것을 생성기에 적용하기 위해 단순히 앰퍼샌드 &를 생성기 이름과 반복에 사용 된 변수에 추가합니다.

 <?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...

위 예제는 foreach루프 내에서 반복 된 값을 변경 $from하면 생성기 내의 변수가 어떻게 변경되는지 보여줍니다 . 이는 생성기 이름 앞의 앰퍼샌드로 인해 참조생성 되기 때문 $from입니다 . 그 때문에 상기 내의 가변 루프는 참조 인 발전기 함수 내의 변수.$valueforeach$from


0

아래 코드는 전체 반복 후 전체 배열을 반환하는 기존의 비 생성기 방식과 달리 생성기를 사용하여 완료 전에 결과를 반환하는 방법을 보여줍니다. 아래 생성기를 사용하면 준비가되면 값이 반환되므로 배열이 완전히 채워질 때까지 기다릴 필요가 없습니다.

<?php 

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

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

PHP에서 HTML 코드를 생성하기 위해 yield를 사용할 수 없습니까? 실제 환경에서 얻을 수있는 이점을 모릅니다
Giuseppe Lodi Rizzini

@GiuseppeLodiRizzini 어떻게 생각하세요?
브래드 켄트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.