yield ใน Python

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.
  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.

คุณ ppthera อธิบาย yield ของ Ruby ไว้น่าสนใจที่ http://www.codenone.com/node/21 เลยถือโอกาสนี้อธิบายคำเดียวกันใน Python บ้าง Python ก็มี yield แต่แทบจะต่างกันโดยสิ้นเชิง yield ใน Python มีไว้สำหรับสร้าง generator และมันเป็นคีย์เวิร์ด

from UserList import UserList
 
class MyList(UserList):
    def mycollect(self,func):
        for i in self.data:
            yield func(i)
 
list(MyList([1,2,3]).mycollect(lambda i: i+1)) # => [2,3,4]

น่าเกลียดไปนิด เขียนแบบ Python ดีกว่า

def mycollect(l,func):
    for i in l:
        yield func(i)
 
list(mycollect([1,2,3],lambda i: i+1)) # => [2,3,4]

สิ่งที่ได้จาก mycollect คือ generator ที่มี next() ให้เรียกไปเรื่อยๆ จนเจอ None ใน BitTorrent เอา yield มาใช้สำหรับเก็บ state ของโปรโตคอล ว่าแต่ ความสวยงามสู้ไม่ได้เลยแฮะ

ผมว่า yield ใน Python มันไม่ได้มีเป้าหมายเดียวกันกับใน Ruby เลยเวลาเอามาเขียนงานเดียวกัน (เช่นแบบ ใน mycollect) แล้วมันเลยดูสวยไม่เท่ากัน

เช่น ใน Python อยากจะทำแบบ mycollect ก็อาจเขียนง่าย ๆ แค่นี้ก็พอ (ไม่ต้องใช้ yield)

def mycollect2(l,f):
    r = []
        for i in l:
            r.append(f(i))
    return r

>>> mycollect2([1,2,3], lambda i:i+1)  # => [2, 3, 4]

แต่ ถ้าเขียนโดยใช้ yield มันจะเหมือน เขียนฟังก์ชันที่ return ของที่เรียกเอามาต่อได้เรื่อย ๆ ฟังก์ชันจะทำงาน ๆ หยุด ๆ ได้ เอาไปใช้อะไรได้อีกหลายอย่าง เช่น เราสามารถเอา mycollect ของคุณ sugree มาเขียนประมาณนี้ได้ (ซึ่งผมเห็นครั้งแรก นี่ผมทึ่งมาก)

>>> a = []
>>> for i in mycollect([1,2,3,4],lambda i:i*i):
       if i>5:
          a.append(i)
   
>>> a  # => [9,16]

ทีนี้เวลาในการทำงานจริง ๆ ฟังก์ชัน mycollect เราจะเหมือนโดนเรียกแล้วจะทำงานแล้วก็หยุด ทำงานแล้วก็หยุด ไปเรื่อย ๆ (คือหยุดตอน yield) แล้วพอโดนเรียก ด้วย next() ซึ่ง for จะเป็นคนเรียก function เราก็จะทำงานต่อ

(ถ้าเข้าใจไม่ผิด... นี่น่าจะเป็นรูปแบบหนึ่งของการใช้ continuation)

ใน rubygarden ก็มีคนเขียนอธิบายไว้เหมือนกันครับ
ว่าถ้าถ้าอยากใช้ generator แบบ python
ก็ให้ใช้วิธีนี้

def fib():
    a, b = 0, 1
    while 1:
        yield b
        a, b = b, a + b

เขียนเป็น ruby

class FibGenerator < Generator
  def generating_loop
    generate(1)
    a, b = 1, 1
    loop do
 
      generate(b)
      a, b = b, a + b
    end
  end
end
 
fibber = FibGenerator.new
10.times do
  puts fibber.next    # prints 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
end

โดย Generate class ใช้ Contination ในการ implement

class Generator
  def initialize
    do_generation
  end
 
  def next
    callcc do |here|
      @main_context = here
      @generator_context.call
    end
  end
 
  private
 
  def do_generation
    callcc do |context|
      @generator_context = context
      return
    end
    generating_loop    # subclass must implement this method!
  end
 
  def generate(value)
    callcc do |context|
      @generator_context = context
      @main_context.call(value)    
    end
  end
end

พอทำแบบนี้เหมือนมันจะ "ไม่ชัด" ว่าจะหยุดการทำงานแล้วนะ...
แต่ที่มันทำได้โดยไม่ต้องเป็น "ส่วนหนึ่ง" ของภาษา นี่น่าสนใจมากอ่ะครับ

sugree's picture

ครับใน Python เรียกว่า Simple Generator รู้สึก Java ก็มีนี่

ผมว่าเท่มาก ๆ
เท่กว่า yield ของ ruby อีก (อันนั้นมันเหมือน แบบ ส่ง block มาให้ โดยไม่ต้องประกาศ argument อีกต่างหาก...)
จริง ๆ น่าจะมีความหมายบางอย่างเหมือนกัน... สงสัยต้องถามคุณ ppthera

sugree's picture

lambda ของ Python มีได้แค่บรรทัดเดียว และจะไม่มีวันมีเกินบรรทัดเดียวด้วย บางคนชอบ lambda ถึงกับเถียงกันใหญ่โต อยากให้มีหลายบรรทัด สุดท้ายประเด็นก็ตกไป จน lambda เกือบโดนลบทิ้งเพราะมันไม่งาม แต่สุดท้ายก็ยังมีอยู่เหมือนเดิม

ของ Ruby ดูสะดวกดี โค้ดสั้นๆ ไม่ต้องไปเขียนฟังก์ชั่นเป็นตัวเป็นตน อืมแต่มันเป็นคำเดียวกันต่างจุดประสงค์จริงๆ

เหมือนกับคำว่า *yield* จะมีตำนาน ใครพอทราบบ้างครับ

ผมก็เคยสงสัยเหมือนกันว่า wording "yield" ที่ Mat (คนออกแบบ ruby) เลือกใช้
มีที่มาจากไหน
(เพราะตอนนั้นก็เริ่มเห็นแล้วว่า python มี yield ทีี่คนละความหมายกัน)
ไว้จะลองค้นดูบ้างครับ

ไปกด ๆ หามาครับ (จริง ๆ น่าเอาไปไว้ที่ forum ของ ruby)

Programming-language buffs will be pleased to know that the keyword yield was chosen to echo the yield function in Liskov's language CLU, a language that is over 20 years old and yet contains features that still haven't been widely exploited by the CLU-less.

จาก http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_containers.html

อืมม์ ใน python mailing list เขียนไว้แบบนี้

[LINK]
> Now that Python 2.2 has a 'yield' operator (like Ruby had from the start)

The word, but not the semantics. Python's yield is named after CLU's yield,
and acts very much like it; although a more direct influence was Icon's
generators
, where the Python yield acts much like Icon's suspend. The Ruby
yield would baffle anyone coming from CLU or Icon -- it's more Smalltalkish,
implicitly executing a parameterized closure passed to the procedure
containing the yield.

อ่านไปอ่านมา พบว่า
Yield ใน Python เนี่ยะ จะเป็นลักษณะเดียวกันกับในภาษา CLU
ส่วนของ ruby จะเป็นคนละแบบ (คือกลับหัวกลับหางกัน)

อ่านรายละเอียด จาก http://mjtsai.com/blog/2003/03/30/iterators/

sugree's picture

เป็นคำที่มีประวัติยาวนานจริงๆ รากเหง้าเดียวกัน แต่ดันตรงข้ามกัน เอาไว้สำหรับคนที่เขียน 2 ภาษานี้พร้อมกันให้ธาตุไฟเข้าแทรกเล่นๆ

java ไม่มี concept generator แบบ python ครับ

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น