Pythonジェネレーターの「送信」関数の目的は?


164

誰かが、Pythonジェネレーター関数に関連付けられた「送信」関数が存在する理由の例を教えてもらえますか?利回り関数を完全に理解しています。しかし、送信機能は私を混乱させます。この方法に関するドキュメントは複雑です。

generator.send(value)

実行を再開し、ジェネレータ関数に値を「送信」します。value引数は、現在のyield式の結果になります。send()メソッドは、ジェネレーターによって生成された次の値を返すか、ジェネレーターが別の値を生成せずに終了した場合にStopIterationを発生させます。

どういう意味ですか?関数への入力は値だと思いましたか?「send()メソッドがジェネレーターによって生成された次の値を返す」という句も、yield関数の正確な目的のようです。yieldは、ジェネレーターによって生成された次の値を返します...

誰かが私にセンドを利用したジェネレーターの例を教えてくれませんか?




2
send()ジェネレーターを起動するために呼び出されるときNone、値を受け取ることができるyield式がないため、それを引数として呼び出さなければなりません。」と公式ドキュメントから引用され、質問の引用は行方不明。
リック

回答:


147

生成されたばかりのジェネレータに値を送信するために使用されます。これは、人為的な(役に立たない)説明の例です。

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

これだけではできませんyield

なぜそれが便利なのかについては、私が見た中で最高のユースケースの1つはTwisted @defer.inlineCallbacksです。基本的に、次のような関数を記述できます。

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

何が起こるかは、値が後で計算されることを約束する値であるをtakesTwoSeconds()返すことですDeferred。Twistedは別のスレッドで計算を実行できます。計算が完了すると、それが遅延オブジェクトに渡され、値がdoStuff()関数に返されます。したがって、doStuff()は、あらゆる種類の計算やコールバックなどを実行できることを除いて、通常の手続き型関数のように見える可能性があります。この機能の前の代替方法は、次のようなものです。

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

それはずっと複雑で扱いにくいです。


2
これの目的は何ですか?なぜこれをdouble_inputs(startingnumber)とyieldで再作成できないのですか?
Tommy

@Tommy:ああ、あなたが得た値は前のものとは何の関係もないので。例を変えましょう
Claudiu 2013年

なぜこれを入力を2倍にする単純な関数に対して使用するのですか?
Tommy

4
@トミー:あなたはそうしません。最初の例は、それが何をするかを説明するだけです。2番目の例は、実際に役立つ使用例です。
Claudiu 2013年

1
@Tommy:このプレゼンテーションをチェックして、すべてのことを実際に確認したい場合は、簡単な答えでは不十分です。「このようにすることはできないのですか?」など
Claudiu 2013年

96

この関数はコルーチンを書くことです

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

プリント

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

コントロールがどのようにやり取りされるかを確認しますか?それらはコルーチンです。これらは、非同期IOなどのあらゆる種類のクールなものに使用できます。

このように考えてください。ジェネレーターがあり、センドがありません。片道です。

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

しかし、センドを使用すると、双方向のストリートになります

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

これは、ユーザーがジェネレータの動作をその場でカスタマイズし、ジェネレータがユーザーに応答するようにするドアを開きます。


3
しかし、ジェネレーター関数はパラメーターを取ることができます。「送信」は、ジェネレーターにパラメーターを送信するだけではありませんか?
Tommy

13
@Tommy実行時にパラメーターをジェネレーターに変更できないため。あなたはそれにパラメータを与え、実行し、完了します。送信では、パラメーターを指定し、少し実行して、値を送信すると、別のことを行います。繰り返します
Daniel Gratzer

2
@Tommyこれによりジェネレーターが再起動し、多くの作業をやり直すことになります
Daniel Gratzer

5
すべての前にNoneを送信する目的を説明していただけますか?
Shubham Aggarwal

2
@ShubhamAggarwalジェネレータを「開始」するために行われます。それは、やらなければならないことです。send()ジェネレータを初めて呼び出すのはyieldまだキーワードに達していないので、考えてみると意味があります。
Michael

50

これは誰かを助けるかもしれません。これは、送信機能の影響を受けないジェネレータです。これは、インスタンス化時に数値パラメーターを受け取り、送信の影響を受けません。

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

次に、sendを使用して同じタイプの関数を実行する方法を示します。そのため、反復ごとにnumberの値を変更できます。

def double_number(number):
    while True:
        number *= 2
        number = yield number

numberに新しい値を送信すると結果が変わることがわかるので、次のようになります。

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

これをforループに入れることもできます:

for x in range(10):
    n = c.send(n)
    print n

詳細については、この素晴らしいチュートリアルをご覧ください


12
send()の影響を受けない関数と影響を受ける関数のこの比較は、本当に役立ちました。ありがとう!
Manas Bajaj 2018

これはどのように目的の例示的な例になることができsendますか?シンプルlambda x: x * 2は、それほど複雑ではない方法で同じことを行います。
user209974

sendを使用しますか?行って答えを追加してください。
radtek

17

ジェネレーターを使用するいくつかのユースケースと send()

send()許可付きのジェネレータ:

  • 実行の内部状態を記憶する
    • 今のステップ
    • データの現在のステータスは何ですか
  • 値のシーケンスを返す
  • 入力シーケンスの受信

以下にいくつかの使用例を示します。

レシピをフォローする試みを見た

事前定義された一連の入力を何らかの順序で期待するレシピを見てみましょう。

私たちは次のことを行います:

  • watched_attemptレシピからインスタンスを作成する
  • 入力を取得させます
  • 入力ごとに、現在ポットにあるものに関する情報を返します
  • 各入力チェックで、入力が期待されるものであること(そうでない場合は失敗)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot

これを使用するには、最初にwatched_attemptインスタンスを作成します。

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

への呼び出し.next()は、ジェネレータの実行を開始するために必要です。

戻り値が示すように、ポットは現在空です。

次に、レシピが期待することに従っていくつかのアクションを実行します。

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

ご覧のように、やっと鍋が空になりました。

レシピに従っていない場合、失敗します(調理を監視する試みの望ましい結果-指示が与えられたときに十分な注意を払っていなかったことを学んだだけの場合。

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

次のことに注意してください。

  • 予想されるステップの線形シーケンスがあります
  • 手順は異なる場合があります(一部は削除され、一部はポットに追加されます)
  • これらはすべて関数/ジェネレーターで管理できます。複雑なクラスや同様の構造を使用する必要はありません。

累計

ジェネレーターを使用して、送信された値の現在までの合計を追跡できます。

数値を追加するたびに、入力の数と合計が返されます(前の入力が送信された時点で有効です)。

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

出力は次のようになります。

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)

3
私はあなたの例を実行し、Python 3ではwatched_attempt.next()をnext(watched_attempt)に置き換える必要があるようです。
thanos.a

15

このsend()メソッドは、yield式の左側の値を制御します。

収量の違いとそれが保持する値を理解するために、最初にPythonコードが評価される順序ですばやく更新してみましょう。

セクション6.15評価順序

Pythonは式を左から右に評価します。割り当てを評価するとき、右側が左側より先に評価されることに注意してください。

したがって、a = b右側の式が最初に評価されます。

以下はa[p('left')] = p('right')、右側が最初に評価されることを示しています。

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

yieldは何をしますか?、yield、関数の実行を中断して呼び出し元に戻り、中断する前に中断した場所から実行を再開します。

実行はどこで一時停止されますか?すでにご想像のとおり、yield式の右側と左側の間で実行が中断されます。したがってnew_val = yield old_val、実行は=記号で停止し、右側の値(中断する前であり、呼び出し元に返される値でもある)は左側の値(再開後に割り当てられる値)とは異なる場合があります実行)。

yield 1つは右に、もう1つは左に2つの値を生成します。

利回り式の左側の値をどのように制御しますか?.send()メソッドを介して。

6.2.9。利回り式

再開後のyield式の値は、実行を再開したメソッドによって異なります。__next__()が使用されている場合(通常、forまたはnext()組み込みを介して)、結果はNoneになります。それ以外の場合、send()を使用すると、結果はそのメソッドに渡される値になります。



6

「降伏」という言葉には2つの意味があります。何かを生産すること(たとえば、トウモロコシを生産すること)と、誰か/何かを続けることをやめること(たとえば、歩行者に譲歩する車)。どちらの定義もPythonのyieldキーワードに適用されます。ジェネレーター関数を特別なものにするのは、通常の関数とは異なり、ジェネレーター関数を終了するのではなく一時停止するだけで、呼び出し元に値を「返す」ことができるということです。

ジェネレータを、「左」端と「右」端のある双方向パイプの一端として想像するのが最も簡単です。このパイプは、ジェネレータ自体とジェネレータ関数の本体の間で値が送信される媒体です。パイプの両端には2つの操作がありますpush。これは、値を送信し、パイプのもう一方の端が値をプルするまでブロックし、何も返しません。そしてpull、パイプの他端が値をプッシュするまでブロックし、プッシュされた値を返します。実行時、実行はパイプのいずれかの側のコンテキスト間で往復します。それぞれの側は、反対側に値を送​​信するまで実行され、その時点で停止し、反対側を実行させ、値を待ちます。戻り、その時点で反対側は停止し、再開します。つまり、パイプの両端は、値を受け取った瞬間から値を送信する瞬間まで続きます。

パイプは機能的に対称ですが、慣例により、この回答で定義します-左端はジェネレーター関数の本体内でのみ使用でき、yieldキーワードを介してアクセスできますが、右端ジェネレーターであり、ジェネレーターのsend機能。パイプのそれぞれの端部に特異インターフェースとして、yield及びsend二重の義務を行う:パイプのその端部から/へ、彼らはそれぞれプッシュプル値の両方、yield右方向に押すと左方向一方を引っ張るとsendは反対を行います。この二重の義務は、のようなステートメントのセマンティクスを取り巻く混乱の核心ですx = yield y。壊すyieldsend2明示的なプッシュ/プル段階へのダウンは、その意味は、はるかに明確になります。

  1. 仮にgジェネレータであるとします。g.send値をパイプの右端から左方向に押します。
  2. g一時停止のコンテキスト内で実行し、ジェネレーター関数の本体を実行できるようにします。
  3. 押し込まれた値は、パイプの左端にg.send引き寄せyieldられ、左端で受け取られます。ではx = yield yxプルされた値に割り当てられます。
  4. を含む次の行にyield到達するまで、実行はジェネレーター関数の本体内で続行されます。
  5. yield値をパイプの左端から右方向に押し、に戻しg.sendます。ではx = yield yyパイプを通して右方向に押されます。
  6. ジェネレーター関数の本体内での実行が一時停止し、外側のスコープが中断したところから続行できるようにします。
  7. g.send 値を再開してプルし、ユーザーに返します。
  8. g.send次にが呼び出されたら、ステップ1に戻ります。

周期的ではありますが、この手順には始まりがあります。g.send(None)それは、next(g)略して-が最初に呼び出されたときです(None最初のsend呼び出し以外のものを渡すことは違法です)。そして、それは終わりがあるかもしれません:yieldジェネレータ関数の本体で到達する必要のあるステートメントがなくなったとき。

あなたは作るものを見てくださいyield、特別なので、(またはより正確に、発電機)の文を?measly returnキーワードとyieldは異なり、は、呼び出し元に値を渡したり、呼び出し元から値を受け取ったりすることができます。(もちろん、関数またはジェネレーターを終了したい場合は、returnキーワードも持つと便利です。)yieldステートメントが検出された場合、ジェネレーター関数は単に一時停止し、左の場所に戻ります。別の値が送信されるとオフになります。そして、それsendは外部からジェネレータ関数の内部と通信するためのインターフェースです。

私たちは本当にダウン限り我々はできるように、このプッシュ/プル/パイプアナロジーを破るしたい場合は、私たちは本当に家を駆動することを次の擬似コードで終わり、それはさておき、ステップ1-5からyieldsend同じの両側あるコインパイプ:

  1. right_end.push(None) # the first half of g.send; sending None is what starts a generator
  2. right_end.pause()
  3. left_end.start()
  4. initial_value = left_end.pull()
  5. if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
  6. left_end.do_stuff()
  7. left_end.push(y) # the first half of yield
  8. left_end.pause()
  9. right_end.resume()
  10. value1 = right_end.pull() # the second half of g.send
  11. right_end.do_stuff()
  12. right_end.push(value2) # the first half of g.send (again, but with a different value)
  13. right_end.pause()
  14. left_end.resume()
  15. x = left_end.pull() # the second half of yield
  16. goto 6

キー変換は、我々が分割されていることであるx = yield yvalue1 = g.send(value2)の二つの文に、それぞれを:left_end.push(y)x = left_end.pull(); とvalue1 = right_end.pull()right_end.push(value2)yieldキーワードには、との2つの特殊なケースがx = yieldありyield yます。これらはのために、それぞれ、シンタックスシュガーですx = yield None_ = yield y # discarding value

値がパイプを介して送信される正確な順序に関する具体的な詳細については、以下を参照してください。


以下は、上記のかなり具体的なモデルです。まず、最初に、どのジェネレータgnext(g)も、はとまったく同じであることに注意してくださいg.send(None)。これを念頭に置いて、どのようにsend機能するかにのみ焦点を当て、でジェネレーターを進めることについてのみ話すことができますsend

私たちが持っていると仮定します

def f(y):  # This is the "generator function" referenced above
    while True:
        x = yield y
        y = x
g = f(1)
g.send(None)  # yields 1
g.send(2)     # yields 2

ここで、f次の通常の(非ジェネレーター)関数に対する大まかなデスガーの定義:

def f(y):
    bidirectional_pipe = BidirectionalPipe()
    left_end = bidirectional_pipe.left_end
    right_end = bidirectional_pipe.right_end

    def impl():
        initial_value = left_end.pull()
        if initial_value is not None:
            raise TypeError(
                "can't send non-None value to a just-started generator"
            )

        while True:
            left_end.push(y)
            x = left_end.pull()
            y = x

    def send(value):
        right_end.push(value)
        return right_end.pull()

    right_end.send = send

    # This isn't real Python; normally, returning exits the function. But
    # pretend that it's possible to return a value from a function and then
    # continue execution -- this is exactly the problem that generators were
    # designed to solve!
    return right_end
    impl()

のこの変容では、次のことが起こりましたf

  1. 実装を入れ子関数に移動しました。
  2. left_endネストされた関数right_endによってアクセスされ、外側のスコープによって返されてアクセスされる双方向パイプを作成しました-これright_endがジェネレーターオブジェクトとして知られているものです。
  3. ネストされた関数内で最初に行うことleft_end.pull()None、チェック、つまり、プロセスでプッシュされた値を消費することです。
  4. ネストされた関数内で、ステートメントx = yield yは2行に置き換えられました:left_end.push(y)およびx = left_end.pull()
  5. send関数を定義しましたright_end。これはx = yield y、前の手順でステートメントを置き換えた2行に相当します。

関数が戻った後に継続することができ、このファンタジーの世界では、g割り当てられright_endた後、impl()呼ばれています。したがって、上記の例では、実行を1行ずつ追跡すると、おおよそ次のようになります。

left_end = bidirectional_pipe.left_end
right_end = bidirectional_pipe.right_end

y = 1  # from g = f(1)

# None pushed by first half of g.send(None)
right_end.push(None)
# The above push blocks, so the outer scope halts and lets `f` run until
# *it* blocks

# Receive the pushed value, None
initial_value = left_end.pull()

if initial_value is not None:  # ok, `g` sent None
    raise TypeError(
        "can't send non-None value to a just-started generator"
    )

left_end.push(y)
# The above line blocks, so `f` pauses and g.send picks up where it left off

# y, aka 1, is pulled by right_end and returned by `g.send(None)`
right_end.pull()

# Rinse and repeat
# 2 pushed by first half of g.send(2)
right_end.push(2)
# Once again the above blocks, so g.send (the outer scope) halts and `f` resumes

# Receive the pushed value, 2
x = left_end.pull()
y = x  # y == x == 2

left_end.push(y)
# The above line blocks, so `f` pauses and g.send(2) picks up where it left off

# y, aka 2, is pulled by right_end and returned to the outer scope
right_end.pull()

x = left_end.pull()
# blocks until the next call to g.send

これは、上記の16ステップの疑似コードに正確にマッピングされます。

エラーがどのように伝播されるか、ジェネレーターの最後に到達したときに何が起こるか(パイプが閉じている)など、他にもいくつかの詳細がありますが、これを使用すると、基本的な制御フローがどのように機能するかが明確になりますsend

これらの同じ糖除去ルールを使用して、2つの特殊なケースを見てみましょう。

def f1(x):
    while True:
        x = yield x

def f2():  # No parameter
    while True:
        x = yield x

ほとんどの場合、それらはと同じ方法で砂糖を取り除きfますが、唯一の違いはyieldステートメントの変換方法です。

def f1(x):
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end


def f2():
    # ... set up pipe

    def impl():
        # ... check that initial sent value is None

        while True:
            left_end.push(x)
            x = left_end.pull()

    # ... set up right_end

最初に、渡された値が最初にf1プッシュ(降伏)され、次にプル(送信)されたすべての値がすぐにプッシュ(降伏)されます。2番目のx場合、最初にに達したときに(まだ)値がないpushため、UnboundLocalErrorが発生します。


「g = f(1)の引数1は正常にキャプチャされ、fの本体内のyに割り当てられていますが、while Trueはまだ始まっていません。」何故なの?なぜPythonは、このコードに遭遇するまでこのコードを実行しようとしないのyieldですか?
ジョシュ

@Joshへの最初の呼び出しまでカーソルは進みませんsend。それは次のいずれかのコールを取るsend(None)最初にカーソルを移動するためyieldのステートメント、だけにして、後続のないsend呼び出しは、実際に「本物」の値を送信しますyield
BallpointBen

おかげで-それは興味深いので、インタプリタは関数f いつかは、呼び出し元からをyield取得するまで待機することを知っていますsendか?通常の関数calでは、インタプリタはすぐに実行を開始しfますよね?結局のところ、Pythonにはいかなる種類のAOTコンパイルもありません。本当にそうですか?(あなたが何を言っているのかを問うのではなく、私はあなたがここに書いたものに本当に困惑しています)Pythonが残りの関数の実行を開始する前に待機する必要があることをどのようにして知ることができるかについて、どこで詳細を読むことができますか?
ジョシュ

@Josh私は、Pythonの内部を理解せずに、さまざまなおもちゃジェネレーターの動作を観察するだけで、このメンタルモデルを構築しました。ただし、初期send(None)値がジェネレータに送信せずに適切な値(たとえば、1生成するという事実はNone、最初の呼び出しsendが特殊なケースであることを示唆しています。これは設計が難しいインターフェースです。最初sendに任意の値を送信させると、現在の値と比較して、生成される値と送信される値の順序が1つずれます。
BallpointBen

BallpointBenに感謝します。非常に興味深いので、ここに質問を残して、その理由を確認しました。
ジョシュ

2

これらも私を混乱させました。これは、交互の順序で信号を生成および受け入れるジェネレーターをセットアップしようとしたときに作成した例です(生成、受け入れ、生成、受け入れ)...

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

出力は次のとおりです。

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.