Guide
Give’s syntax
There are multiple ways you can use give()
. give
returns None unless it is given a single positional argument, in which case it returns the value of that argument.
give(key=value)
This is the most straightforward way to use
give
: you write out both the key and the value associated.Returns: None
x = give(value)
When no key is given, but the result of
give
is assigned to a variable, the key is the name of that variable. In other words, the above is equivalent togive(x=value)
.Returns: The value
give(x)
When no key is given and the result is not assigned to a variable,
give(x)
is equivalent togive(x=x)
. If the argument is an expression likex * x
, the key will be the string"x * x"
.Returns: The value
give(x, y, z)
Multiple arguments can be given. The above is equivalent to
give(x=x, y=y, z=z)
.Returns: None
x = value; give()
If
give
has no arguments at all, it will look at the immediately previous statement and infer what you mean. The above is equivalent tox = value; give(x=value)
.Returns: None
Time and code location
give.line(...)
emits, in addition to the rest,{"$line": location_info}
wherelocation_info
is aLinePosition
object that corresponds to wheregive.line
was called.give.time(...)
emits, in addition to the rest,{"$time": time.time()}
Wrapping
Use the wrap()
method to create blocks in which other context managers can be plugged:
@contextmanager
def hello():
print("hello!")
yield
print("bye!")
with given() as gv:
gv.wrap("main", hello)
with give.wrap("main"):
print(":)")
# prints:
# hello!
# :)
# bye!
Customization
Custom versions of give
can be created with giver()
. For example, givex = giver("x", y=7); givex(2)
would emit {"x": 2, "y": 7}
. You can also create give/given pairs with make_give()
.
Important methods
The important methods listed here are those you should know in order to be immediately productive with Giving. They are available on the given()
object. Unless otherwise specified, assume we are inside a block defined by with given() as gv: ...
.
print()
anddisplay()
: Print the stuff to the terminal.display
looks nicer, butprint
has more flexible formatting.gv.print("x = {x}, y = {y:.2%}")
accum()
: This returns a list to which the stream’s dicts will be appended. This allows you to do anything you want that can be done with a list, like reductions or plotting. Giving provides a battery of operators that might do the job better, but if you have trouble with the paradigm or can’t be bothered, this is an easy escape hatch.results = gv.accum() ... print(sum(data["x"] for data in results if "x" in data))
values()
: Context manager which accumulates all values into a list (with given().values() as vals: ...
is essentially the same aswith given() as gv: vals = gv.accum() ...
).with given()["?x"].values() as results: ... print(sum(results))
subscribe()
andksubscribe()
: Do stuff with the data as it comes. The difference betweensubscribe
andksubscribe
is that the former is called with one argument, which is the next entry in the stream, whereas the latter assumes that all the elements are dicts, and the function is called with**kwargs
syntax.Note
ksubscribe
will wrap the function withlax_function()
so that it has an implicit**kwargs
argument at the end, to make your life easier. That way, any keys that are not useful to your function are simply ignored.# Compare @gv.subscribe def pr(data): print("x = {x}, y = {y}".format(data)) # to: @gv.ksubscribe def pr(x, y): print(f"x = {x}, y = {y}")
gv[key]
,gv["?key"]
: Extracts all the values for a key.Without a leading
?
, e.g.gv["x"]
, every entry must have the key.With a leading
?
, e.g.gv["?x"]
, entries that don’t have the key are ignored. This is often the more useful syntax.
gv["?x"].print()
where()
,where_any()
,keep()
: These operators filter entries depending on the keys they have and their values.where
returns entries where all keys are present, and conditions are metwhere_any
returns entries where any key is present (does not support conditions)keep
returns entries where any key is present and drops all other keys; no conditions, but it can remap key names
gv.where("x", "y", z=True).print()
Selected operators
Here is a classification of available operators.
Filtering
filter()
: filter with a functionkfilter()
: filter with a function (keyword arguments)where()
: filter based on keys and simple conditionswhere_any()
: filter based on keyskeep()
: filter based on keys (+drop the rest)distinct()
: only emit distinct elementsnorepeat()
: only emit distinct consecutive elementsfirst()
: only emit the first elementlast()
: only emit the last elementtake()
: only emit the first n elementstake_last()
: only emit the last n elementsskip()
: suppress the first n elementsskip_last()
: suppress the last n elements
Mapping
Reduction
Arithmetic reductions
Most of these reductions can be called with the scan
argument set to True
to use scan
instead of reduce
. scan
can also be set to an integer, in which case roll
is used.
Wrapping
give.wrap()
: give a special key at the beginning and end of a blockgive.wrap_inherit()
: give a special key at the beginning and end of a blockgive.inherit()
: add default key/values for every give() in the blockgiven.wrap()
: plug a context manager at the location of agive.wrap
given.kwrap()
: same as wrap, but pass kwargs
Timing
debounce()
: suppress events that are too close in timesample()
: sample an element every n secondsthrottle()
: emit at most once every n seconds
Debugging
breakpoint()
: set a breakpoint whenever data comes in. Use this with filters.tag()
: assigns a special word to every entry. Use withbreakword
.breakword()
: set a breakpoint on a specific word set bytag
, using theBREAKWORD
environment variable.print()
: print out the stream.display()
: print out the stream (pretty).accum()
: accumulate into a list.values()
: accumulate into a list (context manager).subscribe()
: run a task on every element.ksubscribe()
: run a task on every element (keyword arguments).