PHP में उपज का क्या मतलब है?


232

मैंने हाल ही में इस कोड पर ठोकर खाई है:

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

मैंने यह yieldकीवर्ड पहले कभी नहीं देखा है । मुझे प्राप्त कोड को चलाने की कोशिश कर रहा है

पार्स त्रुटि: सिंटैक्स त्रुटि, लाइन x पर अप्रत्याशित T_VARIABLE

तो यह yieldकीवर्ड क्या है ? क्या यह वैध PHP भी है? और अगर यह है, तो मैं इसका उपयोग कैसे करूं?

जवाबों:


355

क्या है yield?

yieldकीवर्ड एक जनरेटर समारोह से रिटर्न डेटा:

एक जनरेटर फ़ंक्शन का दिल उपज कीवर्ड है। अपने सरलतम रूप में, एक उपज स्टेटमेंट एक रिटर्न स्टेटमेंट की तरह दिखता है, सिवाय इसके कि फ़ंक्शन के निष्पादन को रोकने और लौटने के बजाय, उपज जनरेटर पर कोड लूपिंग के लिए एक मूल्य प्रदान करता है और जनरेटर फ़ंक्शन के निष्पादन को रोक देता है।

जनरेटर फ़ंक्शन क्या है?

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कीवर्ड से नहीं मिलता है । जब यह एक मिलता है yield, तो यह उस समय जो बाहरी लूप के लिए मान है वह वापस कर देगा। फिर यह जनरेटर फ़ंक्शन में वापस चला जाता है और जहां से यह निकलता है, वहां से जारी रहता है। चूँकि आपका लूप xrangeपकड़ता है for, यह तब तक क्रियान्वित और पैदावार करेगा जब तक $maxकि पहुँच नहीं गया। इसके बारे में सोचो foreachऔर जनरेटर पिंग पोंग की तरह।

मुझे इसकी आवश्यकता क्यों है?

जाहिर है, जनरेटर का उपयोग मेमोरी सीमा के आसपास काम करने के लिए किया जा सकता है। अपने परिवेश के आधार पर, range(1, 1000000)अपनी स्क्रिप्ट को घातक बनाना , जबकि एक जनरेटर के साथ एक ही ठीक काम करेगा। या विकिपीडिया के रूप में इसे डालता है:

क्योंकि जनरेटर केवल मांग पर अपने उपज मूल्यों की गणना करते हैं, वे अनुक्रमों का प्रतिनिधित्व करने के लिए उपयोगी होते हैं जो एक बार में महंगा या असंभव होगा। इनमें अनंत क्रम और लाइव डेटा स्ट्रीम शामिल हैं।

जेनरेटर भी बहुत तेज़ होने वाले हैं। लेकिन ध्यान रखें कि जब हम उपवास के बारे में बात कर रहे होते हैं, तो हम आम तौर पर बहुत कम संख्या में बात कर रहे होते हैं। तो इससे पहले कि आप अब भाग जाएं और जनरेटर का उपयोग करने के लिए अपने सभी कोड को बदल दें, एक बेंचमार्क करें, जहां यह समझ में आता है।

जनरेटरों के लिए एक और उपयोग मामला अतुल्यकालिक कोरटाइन है। yieldकीवर्ड केवल मान वापस नहीं करता है, लेकिन यह भी उन्हें स्वीकार करता है। इस पर जानकारी के लिए, नीचे दिए गए दो उत्कृष्ट ब्लॉग पोस्ट देखें।

कब से उपयोग कर सकता हूं yield?

PHP 5.5 में जेनरेटर पेश किए गए हैं । yieldउस संस्करण से पहले उपयोग करने की कोशिश करने से कीवर्ड के बाद आने वाले कोड के आधार पर विभिन्न पार्स त्रुटियों का परिणाम होगा। इसलिए यदि आपको उस कोड से कोई पार्स त्रुटि मिलती है, तो अपने PHP को अपडेट करें।

स्रोत और आगे पढ़ने:


1
कृपया इस बारे में विस्तार से बताएं कि इसके क्या लाभ yeildहैं, कहते हैं, इस तरह का एक समाधान: ideone.com/xgqevM
माइक

1
आह, ठीक है, और नोटिस मैं उत्पन्न कर रहा था। हुह। खैर, मैंने PHP> = 5.0.0 के लिए जेनरेटर का अनुकरण एक सहायक-वर्ग के साथ किया, और हाँ, थोड़ा कम पठनीय है, लेकिन मैं भविष्य में इसका उपयोग कर सकता हूं। दिलचस्प विषय। धन्यवाद!
माइक

पठनीयता नहीं बल्कि मेमोरी उपयोग! अधिक इस्तेमाल होने वाली मेमोरी की तुलना करें return range(1,100000000)और for ($i=0; $i<100000000; $i++) yield $i
एमिक्स

@ माइक हां, यह पहले से ही हालांकि मेरे जवाब में समझाया गया है। अन्य माइक के उदाहरण में स्मृति शायद ही कोई मुद्दा है क्योंकि वह केवल 10 मानों की पुनरावृत्ति कर रहा है।
गॉर्डन

1
@ माइक के साथ एक समस्या यह है कि उदाहरण के लिए घोंसले के शिकार के लिए स्टैटिक्स की सीमाओं का उपयोग उपयोगिता के लिए है (उदाहरण के लिए, एक डायनामिक मैनिफोल्ड पर खोज या उदाहरण के लिए जनरेटर का उपयोग करके एक पुनरावर्ती क्विकॉर्ट)। आप xrange छोरों को घोंसला नहीं दे सकते क्योंकि इसके काउंटर का केवल एक ही उदाहरण है। यील्ड संस्करण को इस समस्या का सामना नहीं करना पड़ता है।
शायनी

43

यह फ़ंक्शन उपज का उपयोग कर रहा है:

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
आधिकारिक डॉक्स से अतिरिक्त नोट्स: PHP 5 में, एक जनरेटर एक मान वापस नहीं कर सकता: ऐसा करने से एक संकलन त्रुटि हो जाएगी। एक जनरेटर के भीतर एक खाली रिटर्न स्टेटमेंट सिंटैक्स था और यह जनरेटर को समाप्त कर देगा। PHP 7.0 के बाद, एक जनरेटर मानों को वापस कर सकता है, जिसे जनरेटर :: getReturn () का उपयोग करके पुनर्प्राप्त किया जा सकता है। php.net/manual/en/language.generators.syntax.php
प्रोग्रामर डानुक

सरल और संक्षिप्त।
जॉन मिलर

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#

तो, यह फ़ंक्शन को बाधित किए बिना लौटता है?
लुकास Bustamante

22

yieldकीवर्ड PHP 5.5 में "जनरेटर" की परिभाषा के लिए कार्य करता है। ठीक है, फिर एक जनरेटर क्या है ?

Php.net से:

जेनरेटर ओवरहेड या जटिलता के बिना सरल पुनरावृत्तियों को लागू करने का एक आसान तरीका प्रदान करते हैं जो कि Iterator इंटरफ़ेस को लागू करने वाले वर्ग को लागू करता है।

एक जनरेटर आपको कोड लिखने की अनुमति देता है जो मेमोरी में एक सरणी बनाने की आवश्यकता के बिना डेटा के एक सेट पर पुनरावृत्ति करने के लिए फ़ॉरेस्ट का उपयोग करता है, जिससे आपको मेमोरी सीमा से अधिक हो सकती है, या उत्पन्न करने के लिए काफी मात्रा में प्रसंस्करण समय की आवश्यकता होती है। इसके बजाय, आप एक जनरेटर फ़ंक्शन लिख सकते हैं, जो एक सामान्य फ़ंक्शन के समान है, सिवाय इसके कि एक बार लौटने के बजाय, एक जनरेटर कई बार उपज सकता है क्योंकि इसे पुनरावृत्त होने के लिए मूल्यों को प्रदान करने की आवश्यकता होती है।

इस जगह से: जनरेटर = जनरेटर, अन्य कार्य (बस एक साधारण कार्य) = कार्य।

तो, वे उपयोगी हैं जब:

  • आपको चीजों को सरल (या सरल चीजें) करने की आवश्यकता है;

    जनरेटर वास्तव में बहुत सरल है तो Iterator इंटरफ़ेस को लागू करना। दूसरी ओर, स्रोत है, कि जनरेटर कम कार्यात्मक हैं। उनकी तुलना करें

  • आपको बड़ी मात्रा में डेटा की बचत करने की आवश्यकता है - मेमोरी की बचत;

    वास्तव में स्मृति को बचाने के लिए हम हर लूप पुनरावृत्ति के लिए कार्यों के माध्यम से आवश्यक डेटा उत्पन्न कर सकते हैं, और पुनरावृत्ति के बाद कचरे का उपयोग कर सकते हैं। इसलिए यहाँ मुख्य बिंदु हैं - स्पष्ट कोड और शायद प्रदर्शन। देखें कि आपकी जरूरतों के लिए क्या बेहतर है।

  • आपको अनुक्रम उत्पन्न करने की आवश्यकता है, जो मध्यवर्ती मूल्यों पर निर्भर करता है;

    यह पिछले विचार का विस्तार है। जनरेटर कार्यों की तुलना में चीजों को आसान बना सकते हैं। फाइबोनैचि उदाहरण की जाँच करें , और जनरेटर के बिना अनुक्रम बनाने का प्रयास करें। इसके अलावा जनरेटर तेजी से काम कर सकते हैं यह मामला है, कम से कम स्थानीय चर में मध्यवर्ती मूल्यों को संग्रहीत करने के कारण;

  • आपको प्रदर्शन में सुधार करने की आवश्यकता है।

    वे कुछ मामलों में तेजी से काम कर सकते हैं (पिछले लाभ देखें);


1
मुझे समझ नहीं आया कि जनरेटर कैसे काम करते हैं। यह वर्ग पुनरावृत्ति इंटरफ़ेस को लागू करता है। क्या मैं जानता हूँ कि पुनरावृत्तियाँ वर्ग मुझे कॉन्फ़िगर करने की अनुमति देती हैं कि मैं किसी वस्तु पर कैसे पुनरावृति करना चाहता हूँ। उदाहरण के लिए ArrayIterator को एक सरणी या वस्तु मिलती है ताकि मैं इसे पुनरावृत्त करते समय मूल्यों और कुंजियों को संशोधित कर सकूं। इसलिए यदि पुनरावृत्तियों को संपूर्ण ऑब्जेक्ट / सरणी मिलती है, तो जनरेटर को मेमोरी में पूरे सरणी का निर्माण कैसे नहीं करना है ???
user3021621

7

साथ yieldआप आसानी से किसी समारोह में कई कार्य के बीच breakpoints वर्णन कर सकते हैं। बस इतना ही, इसमें कुछ खास बात नहीं है।

$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 अत्यधिक संबंधित हैं, लेकिन आपको कुछ और करने के लिए उनके बीच एक ब्रेकप्वाइंट की आवश्यकता है:

  • डेटाबेस पंक्तियों के बीच मुक्त स्मृति
  • अन्य कार्यों को चलाएं जो अगले कार्य के लिए निर्भरता प्रदान करते हैं, लेकिन जो वर्तमान कोड को समझकर असंबंधित हैं
  • async कॉल कर रहा है और परिणामों की प्रतीक्षा कर रहा है
  • और इसी तरह ...

फिर जनरेटर सबसे अच्छा समाधान है, क्योंकि आपको अपने कोड को कई क्लोजर में विभाजित करने या अन्य कोड के साथ मिश्रण करने की आवश्यकता नहीं है, या कॉलबैक आदि का उपयोग करें ... आप बस 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

स्पष्ट रूप से स्मृति उपयोग की बचत काफी थी (memoryMemoryUsage -----> ~ पहले उदाहरण में 270.5 एमबी , दूसरे उदाहरण में ~ 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है संदर्भ द्वारा उत्पन्न होने वाले जनरेटर नाम से पहले एम्परसेंड की वजह से। इस कारण से, $valueभीतर चर foreachपाश लिए एक संदर्भ है $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 कोड उत्पन्न करने के लिए उपज का उपयोग करना संभव नहीं है? मुझे वास्तविक वातावरण में लाभ का पता नहीं है
Giuseppe Lodi Rizzini

@GiuseppeLodiRizzini आपको क्या लगता है?
ब्रैड केंट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.