#!/usr/bin/env python3
"""Run independent built-in examples. No package installs needed."""

def example_00():
    xs = [-5, 2, -1]
    abs(-5)            # 5
    abs(-2.5)          # 2.5
    abs(3 + 4j)        # 5.0   the magnitude, not a part
    sorted(xs, key=abs)  # sort by size, ignoring sign

def example_01():
    divmod(7, 2)             # (3, 1)      same as (7 // 2, 7 % 2)
    divmod(-7, 2)            # (-4, 1)     floor, so it rounds down
    hours, mins = divmod(135, 60)   # (2, 15)
    divmod(7.5, 2)           # (3.0, 1.5)  floats work too

def example_02():
    from types import SimpleNamespace
    words = ['bee', 'honey', 'a']
    people = [SimpleNamespace(age=20), SimpleNamespace(age=30)]
    counts = {'bee': 3, 'honey': 5}
    max([3, 9, 1])                       # 9
    max(3, 9, 1)                         # 9      separate args
    max(words, key=len)                  # longest word
    max(people, key=lambda p: p.age)     # oldest person object
    max([], default=0)                   # 0      no exception
    max(counts, key=counts.get)          # key with the biggest value

def example_03():
    a, b = 7, 4
    points = [(3, 4), (1, 1)]
    intervals = [(1, 5), (2, 3)]
    min([3, 9, 1])                       # 1
    min(a, b)                            # 4, the smaller value
    min(points, key=lambda p: p[0]**2 + p[1]**2)   # closest to origin
    min(intervals, key=lambda iv: iv[1]) # earliest end time

def example_04():
    pow(2, 10)             # 1024        same as 2 ** 10
    pow(2, -1)             # 0.5         negative exponent gives a float
    pow(2, 100, 1000)      # 376         fast, never builds the huge number
    pow(3, -1, 7)          # 5           modular inverse, Python 3.8+

def example_05():
    round(3.567)           # 4      an int, not a float
    round(3.567, 2)        # 3.57
    round(1234, -2)        # 1200   round to the nearest hundred
    round(2.5)             # 2      banker's rounding, see below
    round(0.5)             # 0

def example_06():
    nums = [-2, 0, 3]
    sum([1, 2, 3])              # 6
    sum([1, 2, 3], 100)         # 106
    sum(x*x for x in nums)      # generator, no temp list
    sum(1 for x in nums if x > 0)   # count matches
    sum([[1], [2]], [])         # [1, 2]  works, but O(n^2). Use itertools.chain

def example_07():
    int('42')            # 42
    int(3.99)            # 3       truncates, never rounds
    int(-3.99)           # -3      toward zero, not down
    int('ff', 16)        # 255
    int('1010', 2)       # 10
    int('0x1f', 0)       # 31      base guessed from the prefix
    int('  7 ')          # 7       surrounding spaces are fine

def example_08():
    float('3.5')         # 3.5
    float(7)             # 7.0
    float('1e-3')        # 0.001
    float('inf')         # inf     handy as a starting "worst" value
    float('nan') == float('nan')   # False. Use math.isnan

def example_09():
    complex(1, 2)        # (1+2j)
    complex('1+2j')      # (1+2j)   note: no spaces allowed inside
    z = 3 + 4j
    z.real, z.imag       # (3.0, 4.0)
    abs(z)               # 5.0

def example_10():
    flags = [True, False, True]
    bool([])             # False
    bool([0])            # True    non-empty list, contents ignored
    bool('False')        # True    any non-empty string is true
    bool(0.0)            # False
    True + True          # 2       bool is a subclass of int
    sum(flags)           # counts the True values

def example_11():
    x = 13
    bin(10)                    # '0b1010'
    bin(10)[2:]                # '1010'    drop the prefix
    format(10, '08b')          # '00001010' padded, no prefix
    bin(-5)                    # '-0b101'  sign, not two's complement
    bin(x).count('1')          # popcount. Or x.bit_count() in 3.10+

def example_12():
    hex(255)                   # '0xff'
    hex(255)[2:]               # 'ff'
    format(255, '04X')         # '00FF'   padded and upper case
    int(hex(255), 16)          # 255      round trip
    bytes([255, 0]).hex()      # 'ff00'   for byte strings

def example_13():
    oct(8)                 # '0o10'
    oct(0o755)             # '0o755'
    int('755', 8)          # 493
    mode = 0o644           # Example Unix permission bits. No file changes.

def example_14():
    value = 42
    str(42)                          # '42'
    str([1, 2])                      # '[1, 2]'
    str(b'hi', 'utf-8')              # 'hi'   same as b'hi'.decode()
    str(b'\xff', 'utf-8', 'replace') # a placeholder char, no crash
    f'{value}'                       # calls str() under the hood

def example_15():
    name = 'Balbir'
    repr('a')            # "'a'"    quotes are visible
    str('a')             # 'a'      quotes are not
    repr('a\nb')         # "'a\\nb'"  escapes show up
    print(f'{name!r}')   # !r means "use repr here"

def example_16():
    ascii('café')        # "'caf\\xe9'"
    repr('café')         # "'café'"
    ascii('日本')         # "'\\u65e5\\u672c'"

def example_17():
    format(3.14159, '.2f')     # '3.14'
    format(1234567, ',')       # '1,234,567'
    format(0.256, '.1%')       # '25.6%'
    format('hi', '>10')        # '        hi'  right aligned
    format(255, '#06x')        # '0x00ff'
    f'{3.14159:.2f}'           # same thing, preferred in normal code

def example_18():
    chr(65)                          # 'A'
    chr(97)                          # 'a'
    chr(ord('a') + 1)                # 'b'   next letter
    [chr(ord('a') + i) for i in range(26)]   # the alphabet

def example_19():
    counts = [0] * 26
    ch = 'b'
    ord('A')                   # 65
    ord('a') - ord('A')        # 32    case gap
    counts[ord(ch) - ord('a')] += 1    # 26-slot array instead of a dict

def example_20():
    import sys
    my_list = ['bee', 'honey']
    pct = 50
    print('a', 'b')                        # a b
    print('a', 'b', sep='-')               # a-b
    print('loading', end='')               # no newline
    print(*my_list, sep='\n')              # one item per line
    print('error', file=sys.stderr)        # to the error stream
    print(f'{pct}%', end='\r', flush=True) # a live-updating line

def example_21():
    def read_person(ask=input):
        name = ask('Name: ')
        age = int(ask('Age: '))
        return name, age
    
    answers = iter(['Balbir', '30'])
    print(read_person(lambda prompt: next(answers)))  # ('Balbir', 30)
    # Use read_person() in a terminal to enter real answers.
    n, m = map(int, '3 5'.split())

def example_22():
    xs, ys = [1, 2], ['a', 'b']
    original = [[1], [2]]
    list()                     # []
    list('abc')                # ['a', 'b', 'c']
    list(range(3))             # [0, 1, 2]
    list({'a': 1})             # ['a']   dicts iterate over keys
    list(zip(xs, ys))          # drain an iterator into memory
    copy = list(original)      # a shallow copy

def example_23():
    seen = set()
    row = [1, 2]
    cache = {}
    x, y, value = 1, 2, 'bee'
    tuple([1, 2])              # (1, 2)
    tuple('ab')                # ('a', 'b')
    (1,)                       # a 1-tuple. The comma makes it, not the brackets
    seen.add(tuple(row))       # lists cannot go in a set. Tuples can
    cache[(x, y)] = value      # tuples make fine dict keys

def example_24():
    keys, values = ['a', 'b'], [1, 2]
    base, overrides = {'a': 1}, {'a': 9}
    dict(a=1, b=2)                   # {'a': 1, 'b': 2}
    dict([('a', 1), ('b', 2)])       # same
    dict(zip(keys, values))          # the pairing idiom
    dict.fromkeys('abc', 0)          # {'a': 0, 'b': 0, 'c': 0}
    {**base, **overrides}            # merge, right side wins

def example_25():
    a, b = {1, 2}, {2, 3}
    xs = [1, 1, 2]
    set([1, 1, 2])             # {1, 2}
    set()                      # the empty set. {} is an empty DICT
    set('hello')               # {'h', 'e', 'l', 'o'}
    a | b, a & b, a - b        # union, intersection, difference
    len(set(xs)) != len(xs)    # "has duplicates" in one line

def example_26():
    seen = set()
    members = ['a', 'b']
    frozenset([1, 1, 2])                 # frozenset({1, 2})
    groups = {frozenset({'a','b'}): 1}   # a set used as a dict key
    seen.add(frozenset(members))         # dedupe unordered groups

def example_27():
    bytes([65, 66])            # b'AB'
    bytes(3)                   # b'\x00\x00\x00'
    bytes('hi', 'utf-8')       # b'hi'   same as 'hi'.encode()
    b'AB'[0]                   # 65      indexing gives an INT
    b'AB'[0:1]                 # b'A'    slicing gives bytes

def example_28():
    buf = bytearray(b'hi')
    buf[0] = 72                # bytearray(b'Hi')
    buf.extend(b' there')      # grows in place, no copy
    buf += b'!'
    bytes(buf)                 # freeze it back

def example_29():
    mv = memoryview(b'abcdef')
    mv[0]                      # 97
    bytes(mv[1:3])             # b'bc'   the slice itself was free
    data = bytearray(1_000_000)
    chunk = memoryview(data)[500:600]   # no 1 MB copy
    chunk[0] = 65              # writes straight into `data`

def example_30():
    range(3)                   # 0, 1, 2
    range(2, 5)                # 2, 3, 4
    range(0, 10, 2)            # 0, 2, 4, 6, 8
    range(5, 0, -1)            # 5, 4, 3, 2, 1
    len(range(10**18))         # instant. Nothing is built
    10**17 in range(10**18)    # also instant. It does the maths

def example_31():
    record = 'Balbir    Singh'
    s = slice(None, None, 2)
    seq = [1, 2, 3, 4]
    FIRST_NAME = slice(0, 10)        # name the columns once
    record[FIRST_NAME]               # readable at every use site
    [1,2,3,4][slice(None, None, 2)]  # [1, 3]   same as [::2]
    s.indices(len(seq))              # (start, stop, step) clamped to the length

def example_32():
    len('hello')       # 5
    len({'a': 1})      # 1
    len(range(10**9))  # 1000000000, no list is built
    try:
        len(x for x in [1, 2])
    except TypeError:
        print('Generators have no length')

def example_33():
    from io import BytesIO
    it = iter([1, 2, 3])
    print(next(it))                       # 1
    f = BytesIO(b'bee honey')
    chunks = []
    for chunk in iter(lambda: f.read(4), b''):
        chunks.append(chunk)
    print(chunks)                         # [b'bee ', b'hone', b'y']
    answers = iter(['bee', 'honey', 'quit'])
    for line in iter(lambda: next(answers), 'quit'):
        print(line)

def example_34():
    it = iter([])
    xs = [1, 3, 4]
    a_set = {7}
    next(iter([7]))                          # 7
    next(it, None)                           # safe: None when empty
    first_even = next((x for x in xs if x % 2 == 0), None)   # find-first, lazy
    next(iter(a_set))                        # peek at any one set item

def example_35():
    handle = print
    async def consume(stream):
        ait = aiter(stream)        # what `async for` calls for you
        async for item in stream:  # the normal way to consume one
            handle(item)

def example_36():
    async def peek(ait):
        first = await anext(ait)         # raises StopAsyncIteration when empty
        safe = await anext(ait, None)    # None instead of an error
        return first, safe

def example_37():
    s = 'ab'
    pairs = [(1, 2), (3, 4)]
    list(enumerate('ab'))            # [(0, 'a'), (1, 'b')]
    list(enumerate('ab', 1))         # [(1, 'a'), (2, 'b')]
    for i, ch in enumerate(s):
        print(i, ch)
    for i, (a, b) in enumerate(pairs):   # nested unpacking works
        print(i, a, b)

def example_38():
    xs, ys = [1, 2], ['a', 'b']
    keys, values = ['a', 'b'], [1, 2]
    matrix = [[1, 2], [3, 4]]
    list(zip([1,2], 'ab'))               # [(1,'a'), (2,'b')]
    list(zip([1,2,3], 'ab'))             # [(1,'a'), (2,'b')]  silently short
    list(zip(xs, ys, strict=True))       # raises on a length mismatch
    dict(zip(keys, values))              # build a dict from two lists
    list(zip(*matrix))                   # transpose rows and columns
    zip(xs, xs[1:])                      # every neighbouring pair

def example_39():
    list(map(str, [1, 2]))              # ['1', '2']
    n, m = map(int, '3 5'.split())       # 3, 5
    list(map(pow, [2, 3], [3, 2]))       # [8, 9]
    words = ['bee', 'honey']
    max(map(len, words))                # 5

def example_40():
    chars = ['1', 'a', '2']
    nums = [-1, 0, 2]
    pred = lambda x: x > 0
    xs = [-1, 2]
    list(filter(None, [0, 1, '', 2]))        # [1, 2]  drops falsy values
    list(filter(str.isdigit, chars))         # only digits
    list(filter(lambda x: x > 0, nums))      # prefer [x for x in nums if x > 0]
    next(filter(pred, xs), None)             # find first match, lazily

def example_41():
    from types import SimpleNamespace
    words = ['Bee', 'honey', 'a']
    people = [SimpleNamespace(age=20, name='A'), SimpleNamespace(age=30, name='B')]
    counts = {'bee': 3, 'honey': 5}
    d = counts
    sorted([3, 1])                             # [1, 3]
    sorted(words, key=len)                     # by length
    sorted(words, key=str.lower)               # case insensitive
    sorted(people, key=lambda p: (-p.age, p.name))   # age desc, then name asc
    sorted(counts.items(), key=lambda kv: kv[1], reverse=True)
    sorted(d, key=d.get)                       # keys, ordered by value

def example_42():
    n = 3
    s = 'bee'
    my_dict = {'a': 1, 'b': 2}
    list(reversed([1, 2]))           # [2, 1]
    for i in reversed(range(n)):     # count down, no list built
        print(i)
    ''.join(reversed(s))             # same as s[::-1], but lazy
    reversed(my_dict)                # keys, newest first. Python 3.8+

def example_43():
    nums = [1, 2, 3]
    xs = [1, 'a']
    all([1, 2, 0])                   # False
    all([])                          # True   "no counter-example found"
    all(x > 0 for x in nums)         # short-circuits on the first failure
    all(isinstance(x, int) for x in xs)

def example_44():
    banned = ['spam', 'junk']
    text = 'bee honey'
    errors = [False, False]
    ship = lambda: print('No errors')
    any([0, 0, 3])                   # True
    any([])                          # False
    any(word in text for word in banned)     # exits early on a hit
    if not any(errors): ship()       # "none of them" reads as `not any`

def example_45():
    x = 3
    type(1)                          # <class 'int'>
    type(x).__name__                 # 'int'   just the name
    type(x) is list                  # exact type, no subclasses
    Point = type('Point', (), {'x': 0})   # a class made at runtime

def example_46():
    import collections.abc
    x = [1, 2]
    isinstance(1, int)                     # True
    isinstance(True, int)                  # True. bool subclasses int
    isinstance(x, (int, float))            # either one
    isinstance(x, int | str)               # 3.10+ syntax
    isinstance(x, collections.abc.Iterable)   # "does it behave like one"

def example_47():
    issubclass(bool, int)    # True
    issubclass(int, int)     # True
    issubclass(int, object)  # True
    try:
        issubclass(1, int)
    except TypeError:
        print('The first argument must be a class')

def example_48():
    callable(len)                    # True
    callable(int)                    # True. Classes are callable
    callable(42)                     # False
    callable(lambda: 1)              # True

def example_49():
    from types import SimpleNamespace
    obj = SimpleNamespace(x=3)
    field_name = 'x'
    self = SimpleNamespace(do_save=lambda: 'saved', unknown=lambda: 'unknown')
    cmd = 'save'
    getattr(obj, 'x')                # same as obj.x
    getattr(obj, 'x', 0)             # 0 when missing
    getattr(obj, field_name)         # the whole point: a dynamic name
    handler = getattr(self, f'do_{cmd}', self.unknown)   # dispatch table

def example_50():
    from types import SimpleNamespace
    obj = SimpleNamespace(x=3)
    self = obj
    config = {'x': 5, 'y': 2}
    setattr(obj, 'x', 5)             # same as obj.x = 5
    for k, v in config.items():
        setattr(self, k, v)          # bulk-assign from a dict

def example_51():
    from types import SimpleNamespace
    obj = SimpleNamespace(cache={}, tmp=1)
    delattr(obj, 'cache')            # same as del obj.cache
    if hasattr(obj, 'tmp'):
        delattr(obj, 'tmp')          # guard first, or catch AttributeError

def example_52():
    from io import StringIO
    obj = StringIO('bee')
    hasattr([], 'append')            # True
    hasattr(obj, 'read')             # duck typing: "is it file-like"

def example_53():
    x = 3
    obj = []
    dir()                                   # names defined right here
    dir(str)                                # every string method
    [m for m in dir(x) if not m.startswith('_')]   # the public surface
    'append' in dir(obj)

def example_54():
    import json
    from types import SimpleNamespace
    obj = SimpleNamespace(x=5)
    vars(obj)                        # {'x': 5}
    vars()                           # same as locals()
    json.dumps(vars(obj))            # quick serialise of a simple object
    vars(obj)['x'] = 9               # writes through. It is the real dict

def example_55():
    x = []
    y = x
    id(x) == id(y)                   # same object. Prefer `x is y`
    a = [1]; b = a
    id(a) == id(b)                   # True. Two names, one list

def example_56():
    hash('a')               # Process-specific integer, not a durable ID.
    hash(1) == hash(1.0)     # True
    try:
        hash([1])
    except TypeError:
        print('Lists are unhashable')

def example_57():
    MISSING = object()               # a unique sentinel value
    def f(x=MISSING):
        if x is MISSING: ...         # distinguishes "not passed" from None
    class C(object): ...             # Python 2 habit. Just write class C:

def example_58():
    class Point:
        def __init__(self, x, y):
            self.x, self.y = x, y
    
        @classmethod
        def from_string(cls, text):        # an alternative constructor
            x, y = map(int, text.split(','))
            return cls(x, y)               # cls, so subclasses work too
    
    Point.from_string('1,2')

def example_59():
    class Temp:
        @staticmethod
        def c_to_f(c):                     # no self needed
            return c * 9 / 5 + 32
    
    Temp.c_to_f(100)                       # 212
    Temp().c_to_f(100)                     # works on an instance too

def example_60():
    class Circle:
        def __init__(self, r):
            self.r = r
    
        @property
        def area(self):                    # read as circle.area, no brackets
            return 3.14159 * self._r ** 2
    
        @property
        def r(self):
            return self._r
    
        @r.setter
        def r(self, value):                # validation on write
            if value < 0:
                raise ValueError('radius must be non-negative')
            self._r = value

def example_61():
    class Base:
        def greet(self): return 'hi'
    
    class Child(Base):
        def __init__(self, extra):
            super().__init__()             # run the parent's setup
            self.extra = extra
    
        def greet(self):
            return super().greet() + '!'   # extend, do not replace
    
    Child.__mro__                          # the exact search order

def example_62():
    name = 'save'
    def handler_save(): return 'saved'
    globals()['handler_save'] = handler_save
    globals()['__name__']            # '__main__'
    globals()['dynamic'] = 5         # really does create a global
    fn = globals()['handler_' + name]    # look up a function by name

def example_63():
    name = 'Balbir'
    def f(a, b):
        c = a + b
        print(locals())              # {'a': 1, 'b': 2, 'c': 3}
    'Hello {name}'.format(**locals())    # quick templating

def example_64():
    import ast
    eval('2 + 3')                    # 5
    eval('x * 2', {'x': 4})          # 8
    try:
        eval('x = 1')
    except SyntaxError:
        print('eval needs an expression')
    ast.literal_eval('[1, 2]')       # the safe choice for data

def example_65():
    ns = {}
    exec('def double(x): return x * 2', ns)
    ns['double'](5)                  # 10

def example_66():
    import ast
    src = 'x = 1'
    code = compile('x + 1', '<expr>', 'eval')
    eval(code, {'x': 1})             # 2. Reuse the code, skip re-parsing
    tree = compile(src, 'f.py', 'exec', ast.PyCF_ONLY_AST)   # get the AST

def example_67():
    import importlib
    __import__('math').pi                    # 3.14159...
    __import__('os.path')                    # returns `os`, not `os.path`
    __import__('os.path', fromlist=['path']) # returns os.path
    importlib.import_module('os.path')       # do this instead

def example_68():
    from pathlib import Path
    from tempfile import TemporaryDirectory
    
    # This example owns its files. It never overwrites your notes.
    with TemporaryDirectory(prefix='builtin-demo-') as folder:
        root = Path(folder)
        with open(root / 'note.txt', 'w', encoding='utf-8') as f:
            f.write('bee\nhoney\n')
        with open(root / 'note.txt', encoding='utf-8') as f:
            for line in f:
                print(line.rstrip())
        with open(root / 'note.txt', 'a', encoding='utf-8') as f:
            f.write('done\n')
        with open(root / 'data.bin', 'wb') as f:
            f.write(b'AB')
        with open(root / 'data.bin', 'rb') as f:
            print(f.read())               # b'AB'

def example_69():
    def double(x):
        '''Return twice the value.'''
        return x * 2
    
    help(len)
    help(double)
    # Run help('modules') yourself to list local installed modules.

def example_70():
    def f(x):
        breakpoint()                 # pdb opens here
        return x * 2

if __name__ == '__main__':
    for index in range(71):
        print(f'Example {index:02d}')
        globals()[f'example_{index:02d}']()
    print('71 examples ran without errors')
