Wes asked (a couple posts back):  is a generator a type of callable?

Those copy/deepcopy links were helpful, I hope to any Python students
trekking through here. edu-sig is a great climb for those into learning
curves.

Most definitely a generator is a "callable" as, in my Python for Kids (not
a trademark [nTM]), a callable is simply "something with a mouth" where "a
mouth" is an open-closed paren, with or without arguments.

In a course or presentation, one might go into "emoticons" here, as
distinct from emoji, coming before emoji in the evolution e.g. :-D and :-(
were part of "ascii email" early on (SMTP etc.).

In this older emoticon language, :-() shows "()" as a "mouth" i.e. parens
are lips, and a callable is "that which may have a mouth" in this sense
(albeit a sideways one).

The rule "no keyword is a callable" is the same as saying, no keyword has a
mouth.  There's no True() or if() and print() is not a keyword (used to be,
in older Pythons).  print is a function, and as such is a callable.

Generator function:

>>> new_gen = pi()  # returns digits of pi [1]
>>> next(new_gen)
3
>>> next(new_gen)
1
>>> next(new_gen)
4
>>> next(new_gen)
1
>>> next(new_gen)
9
...

One of my favorite decorators comes from a David Beazely Youtube I watched
where he goes something like:

def nudge(f):
    "nudger"
    def started():
        g = f()
        g.send(None) # or next(g) -- goes to first yield and waits
        return g
    return started

What this does is take a generator object at define time and replace it
with a proxy wherein it has already been "nudged" to the first yield, and
is now waiting to be fed to next() or to have send() fire, which will at
this point, after nudging, have a corresponding yield queued up to receive
whatever.

As ya'll recall, a generator on birth has its execution pointer still at
the top, no steps taken, and the first next or send(None) takes it to the
first yield, where it may output, and then "waits" (actually suspends
execution and returns control to the caller thread).

next(gen) always means: take steps to the next yield, or: raise a
StopIteration if hitting return or falling off the end.

In other words, at birth, a generator is not ready for any substantive
obj.send(arg) because it's not at a yield yet.  nudge (above) takes care of
that for us, returning a proxy generator with "its pump already primed" so
to speak, g.send(None) having taken g to the first yield already.

In action:

@nudge
def get_mail():
    "Get mail!"
    pickup = "nothing yet..."
    while True:
        pickup = (yield "Thank you for sending me {}".format(pickup))
        if pickup == "q":
            break

gm = get_mail() # a generator function
try:
    while True:
        email = input("What to send? (q to quit): ")
        output = gm.send(email)
        print(output)
except StopIteration:
    print("We appreciate your business")

RUNTIME:

What to send? (q to quit): birthday card
Thank you for sending me birthday card

What to send? (q to quit): postcard
Thank you for sending me postcard

What to send? (q to quit): q
We appreciate your business

Note: David Beazely is not in favor of having having a single yield both
output and take input, as above, regarding such a double-duty yield as too
confusing.  My code might count as demented in his book.

One of my favorite use of the generator-with-send ability patterns is what
I call Tractor in a Farm [nTM].

Farm(ville) is an n x m array of dots or other character, could be numbers,
and a Tractor is an iterator following some raster pattern, of row: each
column, next row: each column and so on.

When it finishes row 0 it jumps to the start of row 1, so not really a
tractor -- more like how people read these left-to-right languages (like
you're reading now).  If the field were a cylinder, with East and West
connecting, the tractor would go in a North to South spiral.

At the bottom right, a Tractor starts over at the top left (so a donut?).

Change in [row][column] (position) is what next() has the tractor do
(there's a __next__ method).

But then "fuel level" is dropping with each advance in some models, and
without the occasional "send" of more fuel, the tractor will come to a
grinding halt in the middle of a field somewhere (demented, but good for
learning purposes).

What's useful about this pattern is it's all "ASCII art" or "Unicode art"
at first, but yet gives the idea of "pixels" (row, column) and therefore
"pixel depth" (the bits needed to define a character), so the transition to
a discussion of binary files, say with R, G, B layers, is pretty easy.

The iterator : iterable relationship, between Tractor(s) : Field is pretty
intuitive also, owing to the extended metaphor.  The tractor is even
simpler than the turtle concept deriving from Logo, as the tractor has a
fixed and closed path.

Finally, said tractors have a "read" and "write" verbs, or "sense" and
"plant", meaning they're able to "write" to whatever cell (patch of Field)
they're in (over), replacing the current character with another if need be,
according to whatever rule.

In Pythonic Andragogy slides, a TextWriterTractor (subclass of Tractor)
starts writing a user-provided phrase at whatever initially passed-in (x,y)
position in the field.  Example:  Just Use It.

A CropCircleTractor (another subclass) reads the Field as complex numbers
and plants "@" where z = z * z + current(row, column) doesn't spiral out
after n iterations.  Result:  A Mandelbrot Set.

https://flic.kr/p/9AWnC2  (results of plowing)

Pythonic Andragogy slides:
http://4dsolutions.net/presentations/pycon2013.pdf
(not saying "not for kids")
An ongoing theme:
http://opensourcebridge.org/proposals/1102

Kirby

[1]  https://github.com/4dsolutions/Python5/blob/master/Pi%20Day%20Fun.ipynb
(see last code cell for mysterious Pi generator discussed here on edu-sig
in chapters past)
_______________________________________________
Edu-sig mailing list
Edu-sig@python.org
https://mail.python.org/mailman/listinfo/edu-sig

Reply via email to