পাইথনে জেনারেটরগুলি বোঝা


218

আমি এই মুহুর্তে পাইথন কুকবুকটি পড়ছি এবং বর্তমানে জেনারেটরগুলির দিকে তাকিয়ে আছি। আমার মাথাটা গোল করা শক্ত হয়ে পড়েছে।

আমি একটি জাভা ব্যাকগ্রাউন্ড থেকে আসা হিসাবে, একটি জাভা সমতুল্য আছে? বইটি 'প্রযোজক / গ্রাহক' সম্পর্কে কথা বলছিল, তবে যখন শুনলাম যে আমি থ্রেডিংয়ের কথা ভাবি।

জেনারেটর কী এবং আপনি কেন এটি ব্যবহার করবেন? কোনও বইয়ের উদ্ধৃতি না দিয়ে, স্পষ্টতই (আপনি যদি কোনও বই থেকে সরাসরি একটি শালীন, সরল উত্তর খুঁজে না পান)। উদাহরণস্বরূপ, যদি আপনি উদার বোধ করেন!

উত্তর:


402

দ্রষ্টব্য: এই পোস্টটি পাইথন 3.x সিনট্যাক্স ধরেছে।

একটি জেনারেটর হ'ল এমন একটি ফাংশন যা কোনও অবজেক্টকে কল করে যার উপর আপনি কল করতে পারেন next, যেমন প্রতিটি কলের জন্য এটি কিছু মান দেয়, যতক্ষণ না এটি StopIterationব্যতিক্রম উত্থাপন করে , যা সংকেত দেয় যে সমস্ত মান উত্পন্ন হয়েছে। এ জাতীয় অবজেক্টকে পুনরুক্তি বলা হয় ।

সাধারণ ফাংশনগুলি returnজাভা হিসাবে যেমন ব্যবহার করে একটি একক মান প্রদান করে । পাইথনে অবশ্য একটি বিকল্প রয়েছে, যাকে বলে yieldyieldকোনও ফাংশনে কোথাও ব্যবহার করা এটিকে জেনারেটর করে তোলে। এই কোডটি পর্যবেক্ষণ করুন:

>>> def myGen(n):
...     yield n
...     yield n + 1
... 
>>> g = myGen(6)
>>> next(g)
6
>>> next(g)
7
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

যেমন আপনি দেখতে পাচ্ছেন, myGen(n)এটি একটি ফাংশন যা ফল দেয় nএবং n + 1nextসমস্ত মান প্রাপ্ত না হওয়া পর্যন্ত প্রতিটি কল একক মান দেয় yield forলুপগুলি nextব্যাকগ্রাউন্ডে কল করে,

>>> for n in myGen(6):
...     print(n)
... 
6
7

তেমনিভাবে জেনারেটর এক্সপ্রেশন রয়েছে যা কিছু সাধারণ জেনারেটরের সংক্ষিপ্তভাবে বর্ণনা করার একটি উপায় সরবরাহ করে:

>>> g = (n for n in range(3, 5))
>>> next(g)
3
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

নোট করুন যে জেনারেটর এক্সপ্রেশনগুলি অনেকগুলি তালিকা বোঝার মতো :

>>> lc = [n for n in range(3, 5)]
>>> lc
[3, 4]

লক্ষ্য করুন যে কোনও জেনারেটর অবজেক্ট একবার তৈরি হয়েছিল তবে এর কোডটি একবারে চালানো হয় না । কেবলমাত্র nextকোডটি বাস্তবায়নের জন্য (অংশের) কল । কোনও জেনারেটরে কোড সম্পাদন বন্ধ হয়ে যায় যখন কোনও yieldবিবৃতি পৌঁছে যায়, যার উপর এটি একটি মান দেয়। nextতারপরে পরবর্তী কলের ফলে জেনারেটরটি শেষের পরে যে রাজ্যে রেখেছিল সেই স্থানেই মৃত্যুদন্ড কার্যকর করা যায় yield। এটি নিয়মিত ক্রিয়াকলাপগুলির সাথে একটি মৌলিক পার্থক্য: যারা সর্বদা "শীর্ষে" সম্পাদন শুরু করে এবং কোনও মান ফেরত দেওয়ার পরে তাদের রাষ্ট্রটি বাতিল করে দেয়।

এই বিষয় সম্পর্কে আরও কিছু বলা দরকার। এটি উদাহরণস্বরূপ sendকোনও জেনারেটরে ( রেফারেন্স ) ডেটা পাওয়া সম্ভব । তবে এটি এমন একটি বিষয় যা আমি আপনাকে জেনারেটরের প্রাথমিক ধারণাটি না বোঝা পর্যন্ত ততক্ষণ সন্ধান করার পরামর্শ দিই না।

এখন আপনি জিজ্ঞাসা করতে পারেন: কেন জেনারেটর ব্যবহার? বেশ কয়েকটি ভাল কারণ রয়েছে:

  • কয়েকটি ধারণা জেনারেটর ব্যবহার করে আরও বেশি সংবেদনশীলভাবে বর্ণনা করা যায়।
  • মানগুলির তালিকায় ফিরিয়ে দেয় এমন একটি ফাংশন তৈরির পরিবর্তে, একটি জেনারেটর লিখতে পারে যা উড়ে মানগুলি উত্পন্ন করে। এর অর্থ হ'ল কোনও তালিকা তৈরি করার দরকার নেই, যার ফলে ফলাফলটি আরও মেমরির দক্ষ। এইভাবে কেউ এমন কোনও ডেটা স্ট্রিমগুলি বর্ণনা করতে পারে যা মেমরির সাথে ফিট করার পক্ষে খুব বড় large
  • জেনারেটর অসীম স্ট্রিমগুলি বর্ণনা করার জন্য একটি প্রাকৃতিক উপায়ে অনুমতি দেয় । উদাহরণস্বরূপ ফিবোনাচি সংখ্যাগুলি বিবেচনা করুন :

    >>> def fib():
    ...     a, b = 0, 1
    ...     while True:
    ...         yield a
    ...         a, b = b, a + b
    ... 
    >>> import itertools
    >>> list(itertools.islice(fib(), 10))
    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    

    এই কোডটি itertools.isliceঅসীম স্ট্রিম থেকে সীমাবদ্ধ সংখ্যক উপাদান গ্রহণ করতে ব্যবহার করে। আপনাকে itertoolsমডিউলটিতে ফাংশনগুলি ভালভাবে দেখার পরামর্শ দেওয়া হচ্ছে , কারণ এগুলি খুব সহজেই উন্নত জেনারেটর লেখার জন্য প্রয়োজনীয় সরঞ্জাম।


  Py পাইথন সম্পর্কে <= 2.6: উপরের উদাহরণগুলিতে nextএকটি ফাংশন যা __next__প্রদত্ত বস্তুটিতে পদ্ধতিটিকে কল করে । পাইথনে <= 2.6 এ o.next()পরিবর্তে কিছুটা আলাদা কৌশল ব্যবহার করা হয়েছে next(o)। পাইথন ২.7 এর next()কল রয়েছে .nextযাতে আপনার নীচের ২.7 ব্যবহার করার দরকার নেই:

>>> g = (n for n in range(3, 5))
>>> g.next()
3

9
আপনি উল্লেখ করেছেন sendযে কোনও জেনারেটরে ডেটা পাওয়া সম্ভব । একবার আপনি এটি করেন যে আপনার একটি 'কর্টিন' রয়েছে। উল্লিখিত গ্রাহক / প্রযোজকের মতো কর্টিনগুলির সাথে নিদর্শনগুলি প্রয়োগ করা খুব সহজ কারণ তাদের কোনও প্রয়োজন নেই Lockএবং তাই অচলাবস্থা রাখতে পারে না। থ্রেড বাজানো ছাড়াই কর্টিনগুলি বর্ণনা করা শক্ত, তাই আমি কেবলই বলব যে কর্টাইনগুলি থ্রেডিংয়ের জন্য খুব মার্জিত বিকল্প alternative
জোচেন রিটজেল

পাইথন জেনারেটরগুলি কীভাবে কাজ করে তার বিচারে মূলত টুরিং মেশিনগুলি রয়েছে?
জ্বলন্ত ফিনিক্স

48

একটি জেনারেটর কার্যকরভাবে একটি ফাংশন যা এটি শেষ হওয়ার পূর্বে (ডেটা) ফেরত দেয় তবে এটি এই মুহুর্তে বিরতি দেয় এবং আপনি সেই মুহুর্তে ফাংশনটি আবার শুরু করতে পারেন।

>>> def myGenerator():
...     yield 'These'
...     yield 'words'
...     yield 'come'
...     yield 'one'
...     yield 'at'
...     yield 'a'
...     yield 'time'

>>> myGeneratorInstance = myGenerator()
>>> next(myGeneratorInstance)
These
>>> next(myGeneratorInstance)
words

ইত্যাদি। জেনারেটরগুলির (বা এক) উপকারটি হ'ল যেহেতু তারা একবারে ডেটার সাথে এক টুকরো লেনদেন করে, আপনি প্রচুর পরিমাণে ডেটা মোকাবেলা করতে পারেন; তালিকাগুলি সহ অতিরিক্ত মেমরির প্রয়োজনীয়তা একটি সমস্যা হয়ে উঠতে পারে। জেনারেটর, ঠিক তালিকার মতো, পুনরাবৃত্তিযোগ্য, তাই সেগুলি একই পদ্ধতিতে ব্যবহার করা যেতে পারে:

>>> for word in myGeneratorInstance:
...     print word
These
words
come
one
at 
a 
time

নোট করুন যে জেনারেটর উদাহরণস্বরূপ অসীম মোকাবিলার জন্য আরেকটি উপায় সরবরাহ করে

>>> from time import gmtime, strftime
>>> def myGen():
...     while True:
...         yield strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())    
>>> myGeneratorInstance = myGen()
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:17:15 +0000
>>> next(myGeneratorInstance)
Thu, 28 Jun 2001 14:18:02 +0000   

জেনারেটর একটি অসীম লুপকে আবদ্ধ করে, তবে এটি কোনও সমস্যা নয় কারণ আপনি প্রতিবার যখনই জিজ্ঞাসা করবেন কেবল তখনই প্রতিটি উত্তর পান।


30

প্রথমত, জেনারেটর শব্দটি মূলত পাইথনে কিছুটা অশুভ সংজ্ঞায়িত হয়েছিল, যার ফলে প্রচুর বিভ্রান্তি হয়েছিল। আপনি সম্ভবত গড় iterators এবং iterables (দেখুন এখানে )। তারপরে পাইথনে জেনারেটর ফাংশন (যা একটি জেনারেটরের বস্তু ফেরত দেয়), জেনারেটর অবজেক্টস (যা পুনরুক্তি করা হয়) এবং জেনারেটর এক্সপ্রেশন (যা জেনারেটরের সাথে মূল্যায়ন করা হয়) রয়েছে।

মতে জন্য শব্দকোষ এন্ট্রি জেনারেটরের মনে হচ্ছে যে সরকারী পরিভাষা এখন যে জেনারেটরের "উত্পাদক ফাংশন" জন্য ছোট। অতীতে ডকুমেন্টেশন শর্তাবলীকে বেমানানভাবে সংজ্ঞায়িত করেছিল, তবে ভাগ্যক্রমে এটি ঠিক করা হয়েছে।

যথাযথ হওয়া এবং আরও নির্দিষ্টকরণ ছাড়াই "জেনারেটর" শব্দটি এড়ানো এখনও ভাল ধারণা হতে পারে।


2
হুম আমি মনে করি আপনি ঠিক বলেছেন, কমপক্ষে পাইথন ২.6 এর কয়েকটি লাইনের পরীক্ষা অনুযায়ী। একটি জেনারেটর এক্সপ্রেশন একটি পুনরুক্তি (ওরফে 'জেনারেটর অবজেক্ট') দেয়, জেনারেটর নয়।
ক্রেগ ম্যাকউউইন

22

একটি জেনারেটর তৈরির জন্য জেনারেটরগুলি শর্টহ্যান্ড হিসাবে বিবেচনা করা যেতে পারে। তারা জাভা আইট্রেটারের মতো আচরণ করে। উদাহরণ:

>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x7fac1c1e6aa0>
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> list(g)   # force iterating the rest
[3, 4, 5, 6, 7, 8, 9]
>>> g.next()  # iterator is at the end; calling next again will throw
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

আশা করি এটি আপনার সন্ধান করছে / এটিকে সহায়তা করে।

হালনাগাদ:

অন্যান্য অনেক উত্তর প্রদর্শিত হচ্ছে, জেনারেটর তৈরির বিভিন্ন উপায় রয়েছে। উপরের উদাহরণ হিসাবে আপনি প্রথম বন্ধনী বাক্য গঠন ব্যবহার করতে পারেন, বা আপনি ফলন ব্যবহার করতে পারেন। আর একটি আকর্ষণীয় বৈশিষ্ট্য হ'ল জেনারেটরগুলি "অসীম" হতে পারে - পুনরাবৃত্তিকারীরা থামে না:

>>> def infinite_gen():
...     n = 0
...     while True:
...         yield n
...         n = n + 1
... 
>>> g = infinite_gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
...

1
এখন, জাভা এর রয়েছে Streamযা জেনারেটরের সাথে অনেক বেশি সাদৃশ্যযুক্ত, আপনি সম্ভবত দৃশ্যমান একটি বিস্ময়কর পরিমাণ ঝামেলা ছাড়াই পরবর্তী উপাদানটি পেতে পারেন না।
মনিকার লসুইট

12

জাভা সমতুল্য নেই।

এখানে কিছুটা নিবিড় উদাহরণ রয়েছে:

#! /usr/bin/python
def  mygen(n):
    x = 0
    while x < n:
        x = x + 1
        if x % 3 == 0:
            yield x

for a in mygen(100):
    print a

জেনারেটরে একটি লুপ থাকে যা 0 থেকে এন পর্যন্ত চলে এবং লুপ ভেরিয়েবলটি 3 এর একাধিক হয়, এটি চলকটি দেয়।

forলুপের প্রতিটি পুনরাবৃত্তির সময় জেনারেটর কার্যকর করা হয়। এটি যদি প্রথমবারের মতো উত্পাদক কার্যকর করে থাকে তবে এটি শুরুতে শুরু হয়, অন্যথায় এটি পূর্ববর্তী সময় থেকে ফলন করেছিল।


2
শেষ অনুচ্ছেদটি অত্যন্ত গুরুত্বপূর্ণ: জেনারেটরের ফাংশনটির স্থিতিটি হ'ল 'হিমায়িত' প্রতিবার যখন স্টেচ দেয়, এবং পরের বার যখন আহ্বান করা হয় ঠিক ঠিক একই অবস্থায় চালিয়ে যায়।
জোহানেস চারারা 14

জাভাতে কোনও "জেনারেটর এক্সপ্রেশন" এর সাথে কোনও সিনট্যাক্টিক সমতুল্য নেই, তবে জেনারেটরগুলি - একবার আপনি একবার পেয়েছেন - মূলত কেবল একটি পুনরাবৃত্তকারী (জাভা পুনরুদ্ধারের মতো একই মৌলিক বৈশিষ্ট্য)।
চিন্তা কোরো

@ ওভারথিংক: ঠিক আছে, জেনারেটরের জাভা ইটারেটরগুলির অন্যান্য পার্শ্ব প্রতিক্রিয়া থাকতে পারে। আমার উদাহরণটিতে যদি আমি রাখি print "hello"তবে x=x+1"হ্যালো" 100 বার মুদ্রিত হবে, যখন লুপটির বডি এখনও 33 বার কার্যকর করা হবে।
ওয়ার্ন্সি

@ আই ওয়ার্নার: খুব নিশ্চিত যে জাভাতেও একই প্রভাব থাকতে পারে। সমান জাভা ইটারেটরে পরবর্তী () প্রয়োগের জন্য এখনও 0 থেকে 99 (আপনার মাইগেন (100) উদাহরণ ব্যবহার করে) অনুসন্ধান করতে হবে, যাতে আপনি প্রতিটি সময় System.out.println () করতে পারেন could যদিও আপনি পরবর্তী () থেকে কেবলমাত্র 33 বার ফিরে আসবেন। জাভার যে অভাব রয়েছে তা হ'ল খুব সহজ ফলনের বাক্য গঠন যা পড়া (এবং লিখতে) উল্লেখযোগ্যভাবে সহজ।
চিন্তা কোরো

আমি এই এক লাইন ডিএফটি পড়তে এবং মনে রাখতে পছন্দ করি: জেনারেটর যদি প্রথমবার প্রয়োগ করে তবে এটি শুরুতে শুরু হয়, অন্যথায়, এটি পূর্ববর্তী সময় থেকে ফলন করেছিল continues
ইকরা।

8

আমি জেনারেটরগুলিকে স্ট্যাক ফ্রেমের ক্ষেত্রে প্রোগ্রামিং ভাষা এবং কম্পিউটিংয়ের একটি ভাল পটভূমির সাথে বর্ণনা করতে চাই।

অনেক ভাষায়, এর উপরে একটি স্ট্যাক রয়েছে যার বর্তমান স্ট্যাক "ফ্রেম"। স্ট্যাক ফ্রেমটিতে ফাংশনে স্থানান্তরিত আর্গুমেন্ট সহ স্থানীয় ভেরিয়েবলের জন্য বরাদ্দ করা স্থান অন্তর্ভুক্ত includes

আপনি যখন কোনও ফাংশন কল করেন, কার্যকর করার বর্তমান পয়েন্টটি ("প্রোগ্রামের কাউন্টার" বা সমতুল্য) স্ট্যাকের দিকে ঠেলা যায় এবং একটি নতুন স্ট্যাক ফ্রেম তৈরি হয়। এক্সিকিউশন তারপরে ডাকা ফাংশনটির শুরুতে স্থানান্তর করে।

নিয়মিত ফাংশন সহ, কোনও সময়ে ফাংশনটি একটি মান দেয় এবং স্ট্যাকটি "পপড" হয়। ফাংশনের স্ট্যাক ফ্রেমটি বাতিল করা হয়েছে এবং পূর্ববর্তী স্থানে এক্সিকিউশনটি পুনরায় শুরু হয়।

যখন কোনও ফাংশন জেনারেটর হয়, তখন ফলন বিবরণী ব্যবহার করে স্ট্যাক ফ্রেমটি বাতিল না করেই এটি কোনও মান ফিরিয়ে দিতে পারে । ফাংশনের মধ্যে স্থানীয় ভেরিয়েবলের মান এবং প্রোগ্রামের কাউন্টারটি সংরক্ষণ করা হয়। ফলন বিবরণী থেকে মৃত্যুদন্ড কার্যকর রেখে জেনারেটরটিকে পরবর্তী সময়ে পুনরায় চালু করার অনুমতি দেয় এবং এটি আরও কোড কার্যকর করতে এবং অন্য মানটি ফিরিয়ে দিতে পারে।

পাইথন 2.5 এর আগে এটি সমস্ত জেনারেটর ছিল। পাইথন 2.5 ফিরে মান পাস করার ক্ষমতা যোগ মধ্যে জেনারেটরের পাশাপাশি। এটি করার মাধ্যমে, উত্তীর্ণ হওয়া মানটি উত্পাদনপ্রদানের বিবৃতি থেকে প্রকাশিত অভিব্যক্তি হিসাবে উপলব্ধ যা জেনারেটরের কাছ থেকে সাময়িকভাবে নিয়ন্ত্রণ (এবং একটি মান) ফিরে আসে।

জেনারেটরগুলির মূল সুবিধাটি হ'ল ফাংশনটির "রাষ্ট্র" সংরক্ষণ করা হয়, নিয়মিত ফাংশনগুলির সাথে ভিন্ন নয় যেখানে প্রতিবার স্ট্যাক ফ্রেমটি ফেলে দেওয়া হয়, আপনি সমস্ত "রাষ্ট্র" হারাবেন। একটি গৌণ সুবিধা হ'ল ফাংশন কল ওভারহেডের কিছু (স্ট্যাক ফ্রেম তৈরি করা এবং মুছে ফেলা) এড়ানো হয়, যদিও এটি সাধারণত একটি ছোটখাটো সুবিধা।


6

স্টিফান202 এর উত্তরে আমি কেবল যুক্ত করতেই এই প্রস্তাবটি হ'ল আপনি ডেভিড বেজলির পাইকন '08 উপস্থাপনা "সিস্টেম প্রোগ্রামারদের জন্য জেনারেটর ট্রিকস" একবার দেখে নিন যা আমি কীভাবে এবং কেন জেনারেটর দেখেছি তার সেরা একক ব্যাখ্যা যে কোন জায়গায়। এটিই সেই জিনিস যা আমাকে পাইথন থেকে একরকম মজাদার দেখায় "" এটিই আমি খুঁজছিলাম "" এটি http://www.dabeaz.com/generators/ এ


6

এটি ফাংশন foo এবং জেনারেটর foo (n) এর মধ্যে একটি স্পষ্ট পার্থক্য তৈরি করতে সহায়তা করে:

def foo(n):
    yield n
    yield n+1

foo একটি ফাংশন। foo (6) একটি জেনারেটর অবজেক্ট।

জেনারেটর অবজেক্টটি ব্যবহারের সাধারণ উপায়টি একটি লুপে রয়েছে:

for n in foo(6):
    print(n)

লুপ প্রিন্ট

# 6
# 7

একটি জেনারেটরটিকে পুনরায় শুরুযোগ্য কার্য হিসাবে ভাবেন।

yieldreturnএই অর্থে এমন আচরণ করে যে প্রাপ্ত মানগুলি জেনারেটর দ্বারা "ফিরে" পায়। প্রত্যাবর্তনের বিপরীতে, পরের বার, জেনারেটরের কাছে কোনও মান জিজ্ঞাসা করা হলে, জেনারেটরের ফাংশন, ফু, এটি যেখানে ছেড়েছিল তা আবার শুরু করে - শেষ ফলন বিবরণের পরে - এবং এটি অন্য ফলন বিবৃতিতে আঘাত না করা অবধি চালিয়ে যেতে থাকবে।

পর্দার আড়ালে, আপনি bar=foo(6)জেনারেটর অবজেক্ট বারটি কল করার সময় একটি nextঅ্যাট্রিবিউট থাকার জন্য সংজ্ঞা দেওয়া হয় ।

Foo থেকে প্রাপ্ত মানগুলি পুনরুদ্ধার করতে আপনি নিজে এটি কল করতে পারেন:

next(bar)    # Works in Python 2.6 or Python 3.x
bar.next()   # Works in Python 2.5+, but is deprecated. Use next() if possible.

যখন next(bar)ফু শেষ হয় (এবং কোনও ফলিত মান নেই), কল করা একটি স্টপ ইন্টিগ্রেশন ত্রুটি ছুড়ে দেয়।


5

পাইথন জেনারেটরের কার্যকারিতা ব্যাখ্যা করার জন্য এই পোস্টটি ফিবোনাচি নম্বরগুলি একটি সরঞ্জাম হিসাবে ব্যবহার করবে ।

এই পোস্টে সি ++ এবং পাইথন কোড উভয়ই প্রদর্শিত হবে।

ফিবোনাচি সংখ্যাগুলি ক্রম হিসাবে সংজ্ঞায়িত করা হয়: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ....

বা সাধারণভাবে:

F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2

এটি অত্যন্ত সহজেই একটি সি ++ ফাংশনে স্থানান্তরিত হতে পারে:

size_t Fib(size_t n)
{
    //Fib(0) = 0
    if(n == 0)
        return 0;

    //Fib(1) = 1
    if(n == 1)
        return 1;

    //Fib(N) = Fib(N-2) + Fib(N-1)
    return Fib(n-2) + Fib(n-1);
}

তবে আপনি যদি প্রথম ছয়টি ফিবোনাচি নম্বর মুদ্রণ করতে চান তবে উপরের ফাংশনটি দিয়ে আপনি প্রচুর মান পুনরায় গণনা করবেন।

উদাহরণস্বরূপ: Fib(3) = Fib(2) + Fib(1)তবে পুনরায় Fib(2)গণনা করা হয় Fib(1)। আপনি যত বেশি মান গণনা করতে চান, ততই খারাপ আপনি হবেন।

সুতরাং কেউ রাষ্ট্রকে ট্র্যাক করে উপরের লেখার প্রলোভনে পড়তে পারে main

// Not supported for the first two elements of Fib
size_t GetNextFib(size_t &pp, size_t &p)
{
    int result = pp + p;
    pp = p;
    p = result;
    return result;
}

int main(int argc, char *argv[])
{
    size_t pp = 0;
    size_t p = 1;
    std::cout << "0 " << "1 ";
    for(size_t i = 0; i <= 4; ++i)
    {
        size_t fibI = GetNextFib(pp, p);
        std::cout << fibI << " ";
    }
    return 0;
}

তবে এটি খুব কুরুচিপূর্ণ এবং এটি আমাদের যুক্তিগুলিকে জটিল করে তোলে main। আমাদের mainফাংশনে রাষ্ট্র সম্পর্কে উদ্বিগ্ন না হওয়াই ভাল ।

আমরা vectorমানগুলির একটি ফিরিয়ে দিতে পারি এবং মানগুলির iteratorসেটের উপরে পুনরাবৃত্তি করতে একটি ব্যবহার করতে পারি, তবে এর জন্য রিটার্ন মানগুলির একটি বৃহত সংখ্যার জন্য একবারে অনেকগুলি স্মৃতি দরকার।

সুতরাং আমাদের পুরানো পদ্ধতির দিকে ফিরে, আমরা সংখ্যা মুদ্রণ ছাড়াও অন্য কিছু করতে চাইলে কি হবে? আমাদের কোডের পুরো ব্লকটি অনুলিপি করে পেস্ট করতে mainহবে এবং আউটপুট স্টেটমেন্টগুলিকে আমাদের অন্য যা করতে হবে তা পরিবর্তন করতে হবে। এবং যদি আপনি কোডটি অনুলিপি করে আটকান, তবে আপনাকে গুলি করা উচিত। আপনি গুলি করতে চান না, তাই না?

এই সমস্যাগুলি সমাধান করার জন্য, এবং গুলি চালানো এড়াতে, আমরা কলব্যাক ফাংশন ব্যবহার করে কোডের এই ব্লকটি আবার লিখতে পারি। প্রতিবার নতুন ফিবোনাচি নম্বর আসার পরে আমরা কলব্যাক ফাংশনটি কল করব call

void GetFibNumbers(size_t max, void(*FoundNewFibCallback)(size_t))
{
    if(max-- == 0) return;
    FoundNewFibCallback(0);
    if(max-- == 0) return;
    FoundNewFibCallback(1);

    size_t pp = 0;
    size_t p = 1;
    for(;;)
    {
        if(max-- == 0) return;
        int result = pp + p;
        pp = p;
        p = result;
        FoundNewFibCallback(result);
    }
}

void foundNewFib(size_t fibI)
{
    std::cout << fibI << " ";
}

int main(int argc, char *argv[])
{
    GetFibNumbers(6, foundNewFib);
    return 0;
}

এটি স্পষ্টতই একটি উন্নতি, আপনার যুক্তিটি mainবিশৃঙ্খলাযুক্ত নয় এবং আপনি ফিবোনাচি সংখ্যাগুলি দিয়ে যা কিছু করতে চান, কেবল নতুন কলব্যাকগুলি সংজ্ঞায়িত করতে পারেন।

তবে এটি এখনও নিখুঁত নয়। আপনি যদি কেবল প্রথম দুটি ফিবোনাচি নম্বর পেতে এবং তারপরে কিছু করতে চান, তবে আরও কিছু পেতে চান, তবে অন্য কিছু করুন?

ঠিক আছে, আমরা যেমন চলি তেমন চলতে পারি এবং আমরা আবার রাষ্ট্র যুক্ত করতে শুরু করতে পারলাম, এবং mainফেইসবুক নম্বরগুলি একটি স্বেচ্ছাসেবী বিন্দু থেকে শুরু করতে পারব। তবে এটি আমাদের কোডটিকে আরও ফাটিয়ে দেবে এবং ফিবোনাচি সংখ্যা মুদ্রণের মতো কোনও সাধারণ কাজের জন্য এটি ইতিমধ্যে বড় দেখায়।

আমরা কয়েক থ্রেডের মাধ্যমে একটি প্রযোজক এবং ভোক্তা মডেলটি প্রয়োগ করতে পারি। তবে এটি কোডটিকে আরও জটিল করে তোলে।

পরিবর্তে জেনারেটর সম্পর্কে কথা বলা যাক।

পাইথনের একটি খুব সুন্দর ভাষা বৈশিষ্ট্য রয়েছে যা এই নামক জেনারেটরের মতো সমস্যার সমাধান করে।

একটি জেনারেটর আপনাকে একটি ফাংশন সম্পাদন করতে, একটি স্বেচ্ছাসেবী বিন্দুতে থামতে দেয় এবং তারপরে আপনি যেখানে রেখেছিলেন সেখানে আবার চালিয়ে যাওয়ার অনুমতি দেয়। প্রতিটি সময় একটি মান ফেরত।

নিম্নলিখিত কোডটি বিবেচনা করুন যা একটি জেনারেটর ব্যবহার করে:

def fib():
    pp, p = 0, 1
    while 1:
        yield pp
        pp, p = p, pp+p

g = fib()
for i in range(6):
    g.next()

যা আমাদের ফলাফল দেয়:

0 1 1 2 3 5

yieldবিবৃতি পাইথন জেনারেটর দিয়ে conjuction ব্যবহার করা হয়। এটি ফাংশনের স্থিতি সংরক্ষণ করে এবং ইয়েল্ডযুক্ত মান প্রদান করে। পরের বার আপনি জেনারেটরে পরবর্তী () ফাংশনটি কল করবেন, ফলনটি যেখানে ছেড়ে গেছে সেখানেই এটি চালিয়ে যাবে।

এটি কলব্যাক ফাংশন কোডের চেয়ে অনেক বেশি পরিষ্কার। আমাদের ক্লিনার কোড রয়েছে, ছোট কোড রয়েছে এবং আরও বেশি কার্যকরী কোডের উল্লেখ না করা (পাইথন নির্বিচারে বড় সংখ্যার অনুমতি দেয়)।

উৎস


3

আমি বিশ্বাস করি যে প্রায় 20 বছর আগে পুনরাবৃত্তকারী এবং জেনারেটরের প্রথম উপস্থিতি আইকন প্রোগ্রামিং ভাষায় ছিল।

আপনি আইকন ওভারভিউ উপভোগ করতে পারেন , যা আপনাকে সিনট্যাক্সের উপর মনোনিবেশ না করে আপনার চারপাশে আপনার মাথাটি গুটিয়ে ফেলতে দেয় (যেহেতু আইকন এমন ভাষা যা আপনি সম্ভবত জানেন না, এবং গ্রিসওয়ল্ড অন্যান্য ভাষা থেকে আগত লোকদের কাছে তাঁর ভাষার সুবিধাগুলি ব্যাখ্যা করছিলেন)।

সেখানে মাত্র কয়েকটি অনুচ্ছেদ পড়ার পরে, জেনারেটর এবং পুনরুক্তিগুলির উপযোগিতা আরও স্পষ্ট হয়ে উঠতে পারে।


2

তালিকা বোঝার সাথে অভিজ্ঞতা পাইথন জুড়ে তাদের ব্যাপক ইউটিলিটি দেখিয়েছে। তবে ব্যবহারের ক্ষেত্রে বেশিরভাগেরই মেমরিতে সম্পূর্ণ তালিকা তৈরি করার প্রয়োজন হয় না। পরিবর্তে, তাদের কেবল একবারে উপাদানগুলির উপর পুনরাবৃত্তি করতে হবে।

উদাহরণস্বরূপ, নিম্নলিখিত সংমিশ্রণ কোডটি স্মৃতিতে স্কোয়ারগুলির একটি সম্পূর্ণ তালিকা তৈরি করবে, সেই মানগুলির উপর পুনরাবৃত্তি করবে এবং যখন রেফারেন্সের প্রয়োজন হবে না তখন তালিকাটি মুছুন:

sum([x*x for x in range(10)])

পরিবর্তে জেনারেটর এক্সপ্রেশন ব্যবহার করে মেমরিটি সংরক্ষণ করা হয়:

sum(x*x for x in range(10))

একই ধরণের সুবিধা কনটেইনার অবজেক্টগুলির জন্য কনস্ট্রাক্টরকে দেওয়া হয়:

s = Set(word  for line in page  for word in line.split())
d = dict( (k, func(k)) for k in keylist)

জেনারেটর এক্সপ্রেশন বিশেষত যোগ (), মিনিট (), এবং সর্বোচ্চ () এর মতো ফাংশনগুলির সাথে দরকারী যা একটি একক মানকে পুনরাবৃত্তিযোগ্য ইনপুট হ্রাস করে:

max(len(line)  for line in file  if line.strip())

অধিক


1

আমি কোডের এই অংশটি রেখেছি যা জেনারেটর সম্পর্কে 3 টি মূল ধারণাটি ব্যাখ্যা করে:

def numbers():
    for i in range(10):
            yield i

gen = numbers() #this line only returns a generator object, it does not run the code defined inside numbers

for i in gen: #we iterate over the generator and the values are printed
    print(i)

#the generator is now empty

for i in gen: #so this for block does not print anything
    print(i)
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.