diff --git a/README.md b/README.md index 950b105..62ad69e 100644 --- a/README.md +++ b/README.md @@ -668,9 +668,9 @@ Arguments --------- ### Inside Function Call ```python -func() # func(1, 2) -func() # func(x=1, y=2) -func(, ) # func(1, y=2) +func() # func(0, 0) +func() # func(x=0, y=0) +func(, ) # func(0, y=0) ``` ### Inside Function Definition @@ -744,7 +744,7 @@ def f(*args, y, **kwargs): ... # f(x=1, y=2, z=3) | f(1, y=2, z=3) ``` ```python -head, *body, tail = # Also `head, *body = ` and `*body, tail = `. +head, *body, tail = # Head or tail can be omitted. ``` @@ -1398,10 +1398,10 @@ finally: ### Catching Exceptions ```python -except : -except as : -except (, [...]): -except (, [...]) as : +except : ... +except as : ... +except (, [...]): ... +except (, [...]) as : ... ``` * **Also catches subclasses of the exception.** * **Use `'traceback.print_exc()'` to print the error message to stderr.** @@ -2615,8 +2615,7 @@ Line # Mem usage Increment Line Contents ### Call Graph #### Generates a PNG image of the call graph with highlighted bottlenecks: ```python -# $ pip3 install pycallgraph2 -# $ apt install graphviz +# $ pip3 install pycallgraph2; brew/apt install graphviz import pycallgraph2 as cg, datetime filename = f'profile-{datetime.datetime.now():%Y%m%d%H%M%S}.png' drawer = cg.output.GraphvizOutput(output_file=filename) diff --git a/index.html b/index.html index 3f3bfb1..fea01b9 100644 --- a/index.html +++ b/index.html @@ -54,7 +54,7 @@
- +
@@ -589,9 +589,9 @@ to_exclusive = <range>.stop <float> = <TD> / <TD> # How many weeks/years there are in TD. Also //. -

#Arguments

Inside Function Call

func(<positional_args>)                           # func(1, 2)
-func(<keyword_args>)                              # func(x=1, y=2)
-func(<positional_args>, <keyword_args>)           # func(1, y=2)
+

#Arguments

Inside Function Call

func(<positional_args>)                           # func(0, 0)
+func(<keyword_args>)                              # func(x=0, y=0)
+func(<positional_args>, <keyword_args>)           # func(0, y=0)
 
@@ -645,7 +645,7 @@ func(*args, **kwargs) <dict> = {**<dict> [, ...]} # Or: dict(**<dict> [, ...])
-
head, *body, tail = <coll.>     # Also `head, *body = <coll.>` and `*body, tail = <coll.>`.
+
head, *body, tail = <coll.>     # Head or tail can be omitted.
 

#Inline

Lambda

<func> = lambda: <return_value>                           # A single statement function.
 <func> = lambda <arg_1>, <arg_2>: <return_value>          # Also accepts default arguments.
@@ -790,7 +790,7 @@ creature = Creature(point, direction)
 
 

Parametrized Decorator

A decorator that accepts arguments and returns a normal decorator that accepts a function.

from functools import wraps
 
-def debug(print_result=False):
+def debug(print_result=False):
     def decorator(func):
         @wraps(func)
         def out(*args, **kwargs):
@@ -1200,10 +1200,10 @@ LogicOp = Enum('LogicOp', {'else' block will only be executed if 'try' block had no exceptions.
 
  • Code inside the 'finally' block will always be executed (unless a signal is received).
  • -

    Catching Exceptions

    except <exception>:
    -except <exception> as <name>:
    -except (<exception>, [...]):
    -except (<exception>, [...]) as <name>:
    +

    Catching Exceptions

    except <exception>: ...
    +except <exception> as <name>: ...
    +except (<exception>, [...]): ...
    +except (<exception>, [...]) as <name>: ...
     
      @@ -1936,7 +1936,7 @@ W, H = 15, 7 offset = P(curses.COLS//2 - W//2, curses.LINES//2 - H//2) while True: screen.erase() - curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W) + curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W) for id_, p in state.items(): screen.addstr(offset.y + (p.y - state['*'].y + H//2) % H, offset.x + (p.x - state['*'].x + W//2) % W, str(id_)) @@ -2140,8 +2140,7 @@ Line # Mem usage Increment Line Contents 3 38.012 MiB 0.344 MiB a = [*range(10000)] 4 38.477 MiB 0.465 MiB b = {*range(10000)}
    -

    Call Graph

    Generates a PNG image of the call graph with highlighted bottlenecks:

    # $ pip3 install pycallgraph2
    -# $ apt install graphviz
    +

    Call Graph

    Generates a PNG image of the call graph with highlighted bottlenecks:

    # $ pip3 install pycallgraph2; brew/apt install graphviz
     import pycallgraph2 as cg, datetime
     filename = f'profile-{datetime.datetime.now():%Y%m%d%H%M%S}.png'
     drawer = cg.output.GraphvizOutput(output_file=filename)
    @@ -2479,8 +2478,8 @@ W, H, MAX_S = 50, 50<
             Mario = dataclasses.make_dataclass('Mario', 'rect spd facing_left frame_cycle'.split())
             return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3)))
         def get_tiles():
    -        border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]]
    -        platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)]
    +        border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]]
    +        platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)]
             return [get_rect(x, y) for x, y in border + platforms]
         def get_rect(x, y):
             return pg.Rect(x*16, y*16, 16, 16)
    @@ -2528,7 +2527,7 @@ W, H, MAX_S = 50, 50<
         mario.facing_left = (D.w in pressed) if {D.w, D.e} & pressed else mario.facing_left
         screen.blit(images[get_marios_image_index() + mario.facing_left * 9], mario.rect)
         for t in tiles:
    -        screen.blit(images[18 if t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16] else 19], t)
    +        screen.blit(images[18 if t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16] else 19], t)
         pg.display.flip()
     
     if __name__ == '__main__':
    @@ -2902,7 +2901,7 @@ $ pyinstaller script.py --add-data '<path>:.'  
      
     
       
     
    diff --git a/parse.js b/parse.js
    index 6a35c5b..0eb1090 100755
    --- a/parse.js
    +++ b/parse.js
    @@ -51,6 +51,23 @@ const LRU_CACHE =
       'def fib(n):\n' +
       '    return n if n < 2 else fib(n-2) + fib(n-1)\n';
     
    +const PARAMETRIZED_DECORATOR =
    +  'from functools import wraps\n' +
    +  '\n' +
    +  'def debug(print_result=False):\n' +
    +  '    def decorator(func):\n' +
    +  '        @wraps(func)\n' +
    +  '        def out(*args, **kwargs):\n' +
    +  '            result = func(*args, **kwargs)\n' +
    +  '            print(func.__name__, result if print_result else \'\')\n' +
    +  '            return result\n' +
    +  '        return out\n' +
    +  '    return decorator\n' +
    +  '\n' +
    +  '@debug(print_result=True)\n' +
    +  'def add(x, y):\n' +
    +  '    return x + y\n';
    +
     const REPR_USE_CASES =
       'print/str/repr([<el>])\n' +
       'f\'{<el>!r}\'\n' +
    @@ -89,6 +106,59 @@ const EVAL =
       '>>> literal_eval(\'1 + 2\')\n' +
       'ValueError: malformed node or string\n';
     
    +const COROUTINES =
    +  'import asyncio, collections, curses, curses.textpad, enum, random\n' +
    +  '\n' +
    +  'P = collections.namedtuple(\'P\', \'x y\')         # Position\n' +
    +  'D = enum.Enum(\'D\', \'n e s w\')                  # Direction\n' +
    +  'W, H = 15, 7                                   # Width, Height\n' +
    +  '\n' +
    +  'def main(screen):\n' +
    +  '    curses.curs_set(0)                         # Makes cursor invisible.\n' +
    +  '    screen.nodelay(True)                       # Makes getch() non-blocking.\n' +
    +  '    asyncio.run(main_coroutine(screen))        # Starts running asyncio code.\n' +
    +  '\n' +
    +  'async def main_coroutine(screen):\n' +
    +  '    state = {\'*\': P(0, 0), **{id_: P(W//2, H//2) for id_ in range(10)}}\n' +
    +  '    moves = asyncio.Queue()\n' +
    +  '    coros = (*(random_controller(id_, moves) for id_ in range(10)),\n' +
    +  '             human_controller(screen, moves), model(moves, state), view(state, screen))\n' +
    +  '    await asyncio.wait(coros, return_when=asyncio.FIRST_COMPLETED)\n' +
    +  '\n' +
    +  'async def random_controller(id_, moves):\n' +
    +  '    while True:\n' +
    +  '        d = random.choice(list(D))\n' +
    +  '        moves.put_nowait((id_, d))\n' +
    +  '        await asyncio.sleep(random.triangular(0.01, 0.65))\n' +
    +  '\n' +
    +  'async def human_controller(screen, moves):\n' +
    +  '    while True:\n' +
    +  '        ch = screen.getch()\n' +
    +  '        key_mappings = {258: D.s, 259: D.n, 260: D.w, 261: D.e}\n' +
    +  '        if ch in key_mappings:\n' +
    +  '            moves.put_nowait((\'*\', key_mappings[ch]))\n' +
    +  '        await asyncio.sleep(0.005)\n' +
    +  '\n' +
    +  'async def model(moves, state):\n' +
    +  '    while state[\'*\'] not in (state[id_] for id_ in range(10)):\n' +
    +  '        id_, d = await moves.get()\n' +
    +  '        x, y   = state[id_]\n' +
    +  '        deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}\n' +
    +  '        state[id_] = P((x + deltas[d].x) % W, (y + deltas[d].y) % H)\n' +
    +  '\n' +
    +  'async def view(state, screen):\n' +
    +  '    offset = P(curses.COLS//2 - W//2, curses.LINES//2 - H//2)\n' +
    +  '    while True:\n' +
    +  '        screen.erase()\n' +
    +  '        curses.textpad.rectangle(screen, offset.y-1, offset.x-1, offset.y+H, offset.x+W)\n' +
    +  '        for id_, p in state.items():\n' +
    +  '            screen.addstr(offset.y + (p.y - state[\'*\'].y + H//2) % H,\n' +
    +  '                          offset.x + (p.x - state[\'*\'].x + W//2) % W, str(id_))\n' +
    +  '        await asyncio.sleep(0.005)\n' +
    +  '\n' +
    +  'if __name__ == \'__main__\':\n' +
    +  '    curses.wrapper(main)\n';
    +
     const PROGRESS_BAR =
       '# $ pip3 install tqdm\n' +
       '>>> from tqdm import tqdm\n' +
    @@ -97,6 +167,81 @@ const PROGRESS_BAR =
       '...     sleep(1)\n' +
       'Processing: 100%|████████████████████| 3/3 [00:03<00:00,  1.00s/it]\n';
     
    +const MARIO =
    +  'import collections, dataclasses, enum, io, itertools as it, pygame as pg, urllib.request\n' +
    +  'from random import randint\n' +
    +  '\n' +
    +  'P = collections.namedtuple(\'P\', \'x y\')          # Position\n' +
    +  'D = enum.Enum(\'D\', \'n e s w\')                   # Direction\n' +
    +  'W, H, MAX_S = 50, 50, P(5, 10)                  # Width, Height, Max speed\n' +
    +  '\n' +
    +  'def main():\n' +
    +  '    def get_screen():\n' +
    +  '        pg.init()\n' +
    +  '        return pg.display.set_mode((W*16, H*16))\n' +
    +  '    def get_images():\n' +
    +  '        url = \'https://gto76.github.io/python-cheatsheet/web/mario_bros.png\'\n' +
    +  '        img = pg.image.load(io.BytesIO(urllib.request.urlopen(url).read()))\n' +
    +  '        return [img.subsurface(get_rect(x, 0)) for x in range(img.get_width() // 16)]\n' +
    +  '    def get_mario():\n' +
    +  '        Mario = dataclasses.make_dataclass(\'Mario\', \'rect spd facing_left frame_cycle\'.split())\n' +
    +  '        return Mario(get_rect(1, 1), P(0, 0), False, it.cycle(range(3)))\n' +
    +  '    def get_tiles():\n' +
    +  '        border = [(x, y) for x in range(W) for y in range(H) if x in [0, W-1] or y in [0, H-1]]\n' +
    +  '        platforms = [(randint(1, W-2), randint(2, H-2)) for _ in range(W*H // 10)]\n' +
    +  '        return [get_rect(x, y) for x, y in border + platforms]\n' +
    +  '    def get_rect(x, y):\n' +
    +  '        return pg.Rect(x*16, y*16, 16, 16)\n' +
    +  '    run(get_screen(), get_images(), get_mario(), get_tiles())\n' +
    +  '\n' +
    +  'def run(screen, images, mario, tiles):\n' +
    +  '    clock = pg.time.Clock()\n' +
    +  '    while all(event.type != pg.QUIT for event in pg.event.get()):\n' +
    +  '        keys = {pg.K_UP: D.n, pg.K_RIGHT: D.e, pg.K_DOWN: D.s, pg.K_LEFT: D.w}\n' +
    +  '        pressed = {keys.get(ch) for ch, is_prsd in enumerate(pg.key.get_pressed()) if is_prsd}\n' +
    +  '        update_speed(mario, tiles, pressed)\n' +
    +  '        update_position(mario, tiles)\n' +
    +  '        draw(screen, images, mario, tiles, pressed)\n' +
    +  '        clock.tick(28)\n' +
    +  '\n' +
    +  'def update_speed(mario, tiles, pressed):\n' +
    +  '    x, y = mario.spd\n' +
    +  '    x += 2 * ((D.e in pressed) - (D.w in pressed))\n' +
    +  '    x -= (x > 0) - (x < 0)\n' +
    +  '    y += 1 if D.s not in get_boundaries(mario.rect, tiles) else (D.n in pressed) * -10\n' +
    +  '    mario.spd = P(x=max(-MAX_S.x, min(MAX_S.x, x)), y=max(-MAX_S.y, min(MAX_S.y, y)))\n' +
    +  '\n' +
    +  'def update_position(mario, tiles):\n' +
    +  '    x, y = mario.rect.topleft\n' +
    +  '    n_steps = max(abs(s) for s in mario.spd)\n' +
    +  '    for _ in range(n_steps):\n' +
    +  '        mario.spd = stop_on_collision(mario.spd, get_boundaries(mario.rect, tiles))\n' +
    +  '        x, y = x + mario.spd.x / n_steps, y + mario.spd.y / n_steps\n' +
    +  '        mario.rect.topleft = x, y\n' +
    +  '\n' +
    +  'def get_boundaries(rect, tiles):\n' +
    +  '    deltas = {D.n: P(0, -1), D.e: P(1, 0), D.s: P(0, 1), D.w: P(-1, 0)}\n' +
    +  '    return {d for d, delta in deltas.items() if rect.move(delta).collidelist(tiles) != -1}\n' +
    +  '\n' +
    +  'def stop_on_collision(spd, bounds):\n' +
    +  '    return P(x=0 if (D.w in bounds and spd.x < 0) or (D.e in bounds and spd.x > 0) else spd.x,\n' +
    +  '             y=0 if (D.n in bounds and spd.y < 0) or (D.s in bounds and spd.y > 0) else spd.y)\n' +
    +  '\n' +
    +  'def draw(screen, images, mario, tiles, pressed):\n' +
    +  '    def get_marios_image_index():\n' +
    +  '        if D.s not in get_boundaries(mario.rect, tiles):\n' +
    +  '            return 4\n' +
    +  '        return next(mario.frame_cycle) if {D.w, D.e} & pressed else 6\n' +
    +  '    screen.fill((85, 168, 255))\n' +
    +  '    mario.facing_left = (D.w in pressed) if {D.w, D.e} & pressed else mario.facing_left\n' +
    +  '    screen.blit(images[get_marios_image_index() + mario.facing_left * 9], mario.rect)\n' +
    +  '    for t in tiles:\n' +
    +  '        screen.blit(images[18 if t.x in [0, (W-1)*16] or t.y in [0, (H-1)*16] else 19], t)\n' +
    +  '    pg.display.flip()\n' +
    +  '\n' +
    +  'if __name__ == \'__main__\':\n' +
    +  '    main()\n';
    +
     const PYINSTALLER =
       '$ pip3 install pyinstaller\n' +
       '$ pyinstaller script.py                        # Compiles into \'./dist/script\' directory.\n' +
    @@ -570,6 +715,7 @@ function fixClasses() {
     
     function fixHighlights() {
       $(`code:contains(@lru_cache(maxsize=None))`).html(LRU_CACHE);
    +  $(`code:contains(@debug(print_result=True))`).html(PARAMETRIZED_DECORATOR);
       $(`code:contains((self, a=None):)`).html(CONSTRUCTOR_OVERLOADING);
       $(`code:contains(print/str/repr([]))`).html(REPR_USE_CASES);
       $(`code:contains(make_dataclass(\'\')`).html(DATACLASS);
    @@ -578,7 +724,9 @@ function fixHighlights() {
       $(`code:contains(\'s\')`).html(STRUCT_FORMAT);
       $(`code:contains(\'\', , )`).html(TYPE);
       $(`code:contains(ValueError: malformed node)`).html(EVAL);
    +  $(`code:contains(import asyncio, collections, curses, curses.textpad, enum, random)`).html(COROUTINES);
       $(`code:contains(pip3 install tqdm)`).html(PROGRESS_BAR);
    +  $(`code:contains(collections, dataclasses, enum, io, itertools)`).html(MARIO);
       $(`code:contains(pip3 install pyinstaller)`).html(PYINSTALLER);
       $(`ul:contains(Only available in)`).html(INDEX);
     }