├── .github └── workflows │ └── jekyll-gh-pages.yml ├── .gitignore ├── 01_Introduction_to_Python ├── pyscript_example_1.html └── pyscript_example_2.html ├── 02_The_Python_Interpreter └── pyscript_hello_world.html ├── 03_The_Python_Language ├── argument_collector_parameters.py ├── assignment_expressions.py ├── assignment_statements.py ├── attributes_of_function_objects_docstrings.py ├── attributes_of_function_objects_other.py ├── beware_of_using_unicode_characters_that_are_homoglyphs.py ├── break_statement.py ├── calling_functions_keyword_only_parameters.py ├── calling_functions_positional_and_named_arguments.py ├── calling_functions_semantics_of_argument_passing.py ├── comparison_chaining.py ├── continue_statement.py ├── control_flow_statements.py ├── count_trues.py ├── dict_comprehensions.py ├── dictionaries.py ├── dictionary_operations.py ├── ellipsis.py ├── else_clause_on_loop_statements.py ├── for_statement.py ├── function_annotations.py ├── functions.py ├── generators.py ├── generators_yield_from.py ├── indexing_a_sequence.py ├── iterators.py ├── lambda_expressions.py ├── list_comprehensions.py ├── lists.py ├── literals.py ├── match_statement_as_patterns.py ├── match_statement_capture_patterns.py ├── match_statement_class_patterns.py ├── match_statement_configuring_classes_for_positional_matching.py ├── match_statement_guards.py ├── match_statement_literal_patterns.py ├── match_statement_mapping_patterns.py ├── match_statement_or_patterns.py ├── match_statement_sequence_patterns.py ├── match_statement_value_patterns.py ├── match_statement_value_patterns_global_access.py ├── match_statement_wildcard_pattern.py ├── mutable_default_parameter_values.py ├── namespaces_global_statement.py ├── namespaces_nested_functions_and_nested_scopes.py ├── numbers.py ├── pass_statement.py ├── recursion.py ├── sequences_bytearray.py ├── sequences_bytes.py ├── sequences_lists.py ├── sequences_strings.py ├── sequences_tuples.py ├── set_comprehensions.py ├── set_operations.py ├── sets.py ├── slicing_a_sequence.py ├── sorting_a_list.py ├── underscores_in_numeric_literals.py ├── unicode_normalization_can_create_unintended_overlap_between_variables.py └── while_statement.py ├── 04_Object_Oriented_Python ├── attribute_reference_basics.py ├── bound_and_unbound_methods.py ├── class_body.py ├── class_level_methods.py ├── decorators_functools_wraps.py ├── decorators_showdoc.py ├── descriptors.py ├── enum_simple_example.py ├── enum_stat_file_permissions.py ├── enum_using_ints.py ├── factory_function.py ├── getattribute_list_no_append.py ├── inheritance_cooperative_superclass_method_calling.py ├── inheritance_delegating_to_superclass_methods.py ├── inheritance_overriding_attributes.py ├── instances.py ├── metaclass_alternatives.py ├── per_instance_methods.py ├── point_using_dataclass.py ├── point_using_dataclass_with_post_init.py ├── point_using_metaclass.py ├── properties.py ├── properties_and_inheritance.py ├── simple_bunch.py ├── singleton.py └── slots_rectangle.py ├── 05_Type_Annotations ├── TYPE_CHECKING_usage.py ├── examples_cast.py ├── examples_overload.py ├── forward_referencing_types_from_future_import_annotations.py ├── forward_referencing_types_not_yet_defined.py ├── generics_and_type_vars_accumulator.py ├── generics_and_type_vars_color_lookup.py ├── generics_and_type_vars_type_var_defns.py ├── namedtuple.py ├── newtype.py ├── protocols.py ├── protocols_roman_numeral.py ├── type_annotations_and_the_typing_module.py ├── typealias.py ├── typeddict.py ├── typeddict_generic.py ├── typeddict_required.py ├── typing_syntax_changes_in_python_39_and_310.py └── using_type_annotations_at_runtime.py ├── 06_Exceptions ├── cross_product.py ├── custom_exception_class.py ├── enclosing_tag_context_manager_class.py ├── enclosing_tag_context_manager_using_contextlib_contextmanager.py ├── exception_add_note.py ├── exception_group.py ├── exception_propagation_at_work.py ├── exception_wrapping_other_exception.py ├── exception_wrapping_raise_from_none.py ├── logging_example.py ├── read_or_default.py ├── safe_divide.py ├── the_try_statement.py ├── try_calling.py └── try_finally.py ├── 07_Modules ├── attributes_of_module_objects.py ├── custom_importers_import_hooks.py ├── mymodule.py └── python_builtins.py ├── 08_Core_Builtins_and_Standard_Library_Modules ├── argparse_example.py ├── collections_chain_map_equivalent.py ├── collections_defaultdict_equivalent.py ├── decorate_sort_undecorate.py ├── exec_with_data.py ├── functools_reduce_equivalent.py ├── greet.py ├── iter_examples.py ├── itertools_count_equivalent.py ├── itertools_cycle_equivalent.py ├── itertools_dropwhile_equivalent.py ├── itertools_groupby_example.py ├── itertools_islice_equivalent.py ├── itertools_repeat_unbounded.py ├── itertools_starmap_equivalent.py ├── itertools_takewhile_equivalent.py ├── keydefaultdict.py ├── safer_eval.py └── sys_ps1_ps2.py ├── 09_Strings_and_Things ├── digit_grouping.py ├── field_width.py ├── format_type.py ├── formatted_string_literals.py ├── formatting_of_user_coded_classes.py ├── legacy_string_formatting_with_percent.py ├── nested_format_specifications.py ├── precision_specification.py ├── str_split.py ├── string_formatting.py ├── unicodedata_module.py └── values_by_argument_lookup.py ├── 10_Regular_Expressions ├── afile.txt ├── anchoring_at_string_start_and_end.py ├── match_vs_search.py ├── re_and_the_walrus_operator.py ├── re_findall_1.py ├── re_findall_2.py ├── re_match.py ├── re_optional_flags.py ├── re_search.py └── res_and_bytes_vs_str.py ├── 11_File_and_Text_Operations ├── console_io_msvcrt_module.py ├── errno_module.py ├── fileinput_module.py ├── fnmatch_translate.py ├── gettext_install.py ├── gettext_module.py ├── locale_format_string.py ├── locale_strxfrm.py ├── os_path_expand_vars.py ├── os_path_join.py ├── os_stat.py ├── pathlib_glob.py ├── pathlib_mkdir.py ├── pathlib_replace.py ├── pathlib_touch.py ├── pian.po ├── print_function.py ├── shutil_copytree.py ├── standard_input.py ├── tempfile_module.py └── zipfile_module.py ├── 12_Persistence_and_Databases ├── csv_colors.py ├── dbm_find.py ├── dbm_save.py ├── json_find.py ├── json_save.py ├── pickle_find.py ├── pickle_save.py ├── shelve_find.py ├── shelve_save.py ├── sql_find.py └── sql_save.py ├── 13_Time_Operations ├── sched_module.py └── zoneinfo_module.py ├── 14_Contolling_Execution ├── code_object_type.py ├── gc_module.py └── weakref_module.py ├── 15_Threads_and_Processes ├── barrier_objects.py ├── concurrent_futures_as_completed.py ├── concurrent_futures_map.py ├── condition_objects.py ├── event_objects.py ├── manager_list.py ├── mmap_ipc_reader.py ├── mmap_ipc_writer.py ├── multiprocessing_array.py ├── multiprocessing_manager.py ├── multiprocessing_pool.py ├── multiprocessing_threadpool.py ├── process_environment.py ├── queue_eafp.py ├── queue_lbyl.py ├── rlock_objects.py ├── thread_local_storage.py ├── threaded_program_architecture_complete.py ├── threaded_program_architecture_external_interfacing.py ├── threaded_program_architecture_serializer.py ├── threaded_program_architecture_worker.py └── timer_objects.py ├── 16_Numeric_Processing ├── decimal_module.py ├── dont_use_a_float_as_a_loop_control_variable.py ├── dont_use_eqeq_between_floats_or_complex_numbers.py ├── floating_point_numbers.py ├── fractions_module.py ├── math_cmath_atan2.py ├── numpy_array.py ├── numpy_matrix_operations.py └── numpy_shape_indexing_and_slicing.py ├── 17_Testing_Debugging_and_Optimizing ├── building_up_a_string_from_pieces.py ├── inspect_example.py ├── inspect_getargvalues.py ├── inspect_getmro.py ├── inspect_stack.py ├── large_scale_optimization.py ├── memoizing.py ├── mod.py ├── nose2_example.py ├── optimizing_loops.py ├── pdb_example.py ├── pre_computing_a_lookup_table.py ├── pytest_example.py ├── pytest_parametrize.py ├── short_circuiting_of_iterators.py ├── using_doctest.py ├── using_unittest.py └── warnings_warn_to_unicode.py ├── 18_Basic_Networking ├── tcpclient.py ├── tcpclient6.py ├── tcpserver.py ├── tcpserver6.py ├── test.html ├── udpclient.py ├── udpclient6.py ├── udpserver.py └── udpserver6.py ├── 19_client_side_network_protocol_modules ├── third_party_requests_package.py ├── urllib_parse_module.py └── urllib_request_urlopen_example.py ├── 20_serving_http ├── client1.py ├── clitest.py ├── debug_test.py ├── flask_example.py ├── index.html ├── models.py ├── server1.py └── server2.py ├── 21_Email_MIME_and_Other_Network_Encodings └── email_example.py ├── 22_Structured_Text_HTML ├── bs4_attribute_references_on_bs4_and_tag.py ├── bs4_building_html.py ├── bs4_css_selectors.py ├── bs4_editing_and_creating_html.py ├── bs4_getting_an_actual_string.py ├── bs4_html_parsing_example.py ├── bs4_indexing_instances_of_tag.py ├── bs4_search_methods.py ├── bs4_unicode_and_encoding.py ├── bs4_which_parser.py └── jinja2_building_html.py ├── 23_Structured_Text_XML ├── building_an_elementtree_from_scratch.py ├── menu.csv ├── parsing_xml_iteratively_1.py ├── parsing_xml_iteratively_2.py ├── parsing_xml_with_elementtree_parse.py └── simple.xml ├── 24_Distributing_Extensions_and_Programs └── flask_setup.py ├── 25_Extending_and_Embedding_Classic_Python ├── hello │ ├── hello.c │ ├── hello_demo.py │ └── setup.py ├── intpair │ ├── intpair.c │ ├── intpair_demo.py │ └── setup.py └── merge │ ├── merge.c │ ├── merge_demo.py │ └── setup.py ├── README.md ├── chapters ├── 24 Packaging Programs and Extensions.pdf └── 25 Extending and Embedding Classic Python.pdf ├── static └── Pian_cover2.jpg └── test └── check_snippets.py /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Build job 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | - name: Setup Pages 31 | uses: actions/configure-pages@v3 32 | - name: Build with Jekyll 33 | uses: actions/jekyll-build-pages@v1 34 | with: 35 | source: ./ 36 | destination: ./_site 37 | - name: Upload artifact 38 | uses: actions/upload-pages-artifact@v1 39 | 40 | # Deployment job 41 | deploy: 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | runs-on: ubuntu-latest 46 | needs: build 47 | steps: 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v1 51 | -------------------------------------------------------------------------------- /01_Introduction_to_Python/pyscript_example_1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Minimal PyScript Example 4 | 5 | 6 | 7 | 8 | 9 | 10 | import sys 11 | import time 12 | 13 | print('PyScript runs Python in your browser!') 14 | print(f'Running Python version {sys.version_info}') 15 | print(f'The current local time is {time.asctime()}') 16 | print(f'The current time UTC is {time.asctime(time.gmtime())}') 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /01_Introduction_to_Python/pyscript_example_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Minimal PyScript Example 2 4 | 5 | 6 | 7 | 8 | 9 | Minimal PyScript Example 2 10 | 11 | 12 | import sys 13 | import time 14 | 15 | # cannot embed literals '<' and '>' in code, so must define them as variables 16 | lt, gt = '\x3c\x3e' 17 | br_tag = f'{lt}br{gt}' 18 | 19 | def update_current_time(evt): 20 | pyscript.write( 21 | 'display-current-time', 22 | f'The current local time is {time.asctime()}{br_tag}' 23 | f'The current time UTC is {time.asctime(time.gmtime())}' 24 | ) 25 | 26 | print('PyScript runs Python in your browser!') 27 | print(f'Running Python version {sys.version_info}') 28 | update_current_time(None) 29 | 30 | 31 | 32 |
33 | 34 | 37 | 38 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /02_The_Python_Interpreter/pyscript_hello_world.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | import time 10 | print('Hello, World!') 11 | print(f'The current local time is {time.asctime()}') 12 | print(f'The current time UTC is {time.asctime(time.gmtime())}') 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /03_The_Python_Language/argument_collector_parameters.py: -------------------------------------------------------------------------------- 1 | def sum_sequence(*numbers): 2 | return sum(numbers) 3 | print(sum_sequence(23, 42)) # prints: 65 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/assignment_expressions.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # := in an if/elif statement 4 | 5 | re_match = re.match(r'Name: (\S+)', input_string) 6 | if re_match: 7 | print(re_match.groups(1)) 8 | 9 | # collapsed version using := 10 | if (re_match := re.match(r'Name: (\S+)', input_string)): 11 | print(re_match.groups(1)) 12 | 13 | 14 | # := in a while statement 15 | 16 | current_value = get_next_value() 17 | while current_value is not None: 18 | if not filter_condition(current_value): 19 | continue # BUG! Current_value is not advanced to next 20 | # ... do some work with current_value ... 21 | current_value = get_next_value() 22 | 23 | # collapsed version using := 24 | while (current_value := get_next_value()) is not None: 25 | if not filter_condition(current_value): 26 | continue # no bug, current_value gets advanced in while statement 27 | # ... do some work with current_value ... 28 | 29 | 30 | # := in a list comprehension filter 31 | def safe_int(s): 32 | try: 33 | return int(s) 34 | except Exception: 35 | return None 36 | 37 | 38 | input_strings = ['1', '2', 'a', '11'] 39 | 40 | valid_int_strings = [safe_int(s) for s in input_strings 41 | if safe_int(s) is not None] 42 | 43 | # collapsed version using := 44 | valid_int_strings = [int_s for s in input_strings 45 | if (int_s := safe_int(s)) is not None] 46 | 47 | -------------------------------------------------------------------------------- /03_The_Python_Language/assignment_statements.py: -------------------------------------------------------------------------------- 1 | a = b = c = 0 2 | 3 | x = (1, 2, 3) 4 | a, b, c = x 5 | 6 | a, b = b, a 7 | 8 | first, *middle, last = x 9 | 10 | first, middle, last = x[0], x[1:-1], x[-1] 11 | 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/attributes_of_function_objects_docstrings.py: -------------------------------------------------------------------------------- 1 | def sum_sequence(*numbers): 2 | """Return the sum of multiple numerical arguments. 3 | 4 | The arguments are zero or more numbers. 5 | The result is their sum. 6 | """ 7 | 8 | return sum(numbers) 9 | 10 | 11 | print(sum_sequence(23, 42)) 12 | help(sum_sequence) 13 | -------------------------------------------------------------------------------- /03_The_Python_Language/attributes_of_function_objects_other.py: -------------------------------------------------------------------------------- 1 | def counter(): 2 | counter.count += 1 3 | return counter.count 4 | counter.count = 0 5 | 6 | 7 | for i in range(10): 8 | print(i, counter()) 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/beware_of_using_unicode_characters_that_are_homoglyphs.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> A = 100 3 | >>> Α = 200 # this variable is GREEK CAPITAL LETTER ALPHA 4 | >>> print(A, Α) 5 | 100 200 6 | """ 7 | 8 | if __name__ == '__main__': 9 | # use doctest to simulate console sessions 10 | import doctest 11 | doctest.testmod(verbose=True, exclude_empty=True) 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/break_statement.py: -------------------------------------------------------------------------------- 1 | i = -5 2 | def get_next(): 3 | global i 4 | i += 1 5 | return i 6 | 7 | def preprocess(i): 8 | return i * i 9 | 10 | def keep_looping(a, b): 11 | return a != b 12 | 13 | def process(a, b): 14 | x = a ** b 15 | print(a, b, x) 16 | 17 | 18 | while True: # this loop can never terminate “naturally” 19 | x = get_next() 20 | y = preprocess(x) 21 | if not keep_looping(x, y): 22 | break 23 | process(x, y) 24 | 25 | -------------------------------------------------------------------------------- /03_The_Python_Language/calling_functions_keyword_only_parameters.py: -------------------------------------------------------------------------------- 1 | def f(a, *, b, c=56): # b and c are keyword-only 2 | return a, b, c 3 | f(12,b=34) # returns (12, 34, 56) – c's optional, since it has a default 4 | f(12) # raises a TypeError exception, since you didn’t pass `b`: 5 | # error message is: missing 1 required keyword-only argument: 'b' 6 | 7 | 8 | def g(x, *a, b=23, **k): # b is keyword-only 9 | return x, a, b, k 10 | 11 | print(g(1, 2, 3, c=99)) # returns (1, (2, 3), 23, {'c': 99}) 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/calling_functions_positional_and_named_arguments.py: -------------------------------------------------------------------------------- 1 | def f(a, b, c=23, d=42, *x): 2 | print(a, b, c, d, x) 3 | f(1,2,3,4,5,6) # prints 1 2 3 4 (5, 6) 4 | 5 | 6 | def f(a, b, *x, c=23, d=42): 7 | print(a, b, x, c, d) 8 | f(1,2,3,4,5,6) # prints 1 2 (3, 4, 5, 6) 23 42 9 | 10 | 11 | def divide(divisor, dividend=94): 12 | return dividend // divisor 13 | print(divide(12)) # prints: 7 14 | print(divide(12, 94)) # prints: 7 15 | print(divide(dividend=94, divisor=12)) # prints: 7 16 | 17 | 18 | print(divide(divisor=12)) # prints: 7 19 | 20 | 21 | def f(middle, begin='init', end='finis'): 22 | return begin+middle+end 23 | print(f('tini', end='')) # prints: inittini 24 | 25 | 26 | print(f('a', 'c', 't')) # prints: cat 27 | 28 | 29 | def sum_sequence(*numbers): 30 | return sum(numbers) 31 | 32 | d = {'a': 1, 'b': 100, 'c': 1000} 33 | print(sum_sequence(*d.values())) 34 | 35 | 36 | -------------------------------------------------------------------------------- /03_The_Python_Language/calling_functions_semantics_of_argument_passing.py: -------------------------------------------------------------------------------- 1 | def f(x, y): 2 | x = 23 3 | y.append(42) 4 | 5 | a = 77 6 | b = [99] 7 | f(a, b) 8 | print(a, b) # prints: 77 [99, 42] 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/comparison_chaining.py: -------------------------------------------------------------------------------- 1 | a, b, c, d = 10, 20, 5, 0 2 | 3 | a < b <= c < d 4 | 5 | a < b and b <= c and c < d 6 | 7 | -------------------------------------------------------------------------------- /03_The_Python_Language/continue_statement.py: -------------------------------------------------------------------------------- 1 | some_container = list(range(-10, 11)) 2 | 3 | def seems_ok(i): 4 | return i % 3 5 | 6 | def bounds_to_test(): 7 | return (-4, 4) 8 | 9 | def pre_process(i): 10 | print(i, i * i) 11 | 12 | def final_check(i): 13 | return i % 2 14 | 15 | def do_processing(i): 16 | print(i, i * i * i) 17 | 18 | 19 | for x in some_container: 20 | if seems_ok(x): 21 | lowbound, highbound = bounds_to_test() 22 | if lowbound <= x < highbound: 23 | pre_process(x) 24 | if final_check(x): 25 | do_processing(x) 26 | 27 | 28 | # same for loop using continue 29 | for x in some_container: 30 | if not seems_ok(x): 31 | continue 32 | 33 | lowbound, highbound = bounds_to_test() 34 | if x < lowbound or x >= highbound: 35 | continue 36 | 37 | pre_process(x) 38 | if final_check(x): 39 | do_processing(x) 40 | -------------------------------------------------------------------------------- /03_The_Python_Language/control_flow_statements.py: -------------------------------------------------------------------------------- 1 | """ 2 | if expression: 3 | statement(s) 4 | elif expression: 5 | statement(s) 6 | elif expression: 7 | statement(s) 8 | ... 9 | else: 10 | statement(s) 11 | """ 12 | 13 | x = 100 14 | 15 | if x < 0: 16 | print('x is negative') 17 | elif x % 2: 18 | print('x is positive and odd') 19 | else: 20 | print('x is even and non-negative') 21 | -------------------------------------------------------------------------------- /03_The_Python_Language/count_trues.py: -------------------------------------------------------------------------------- 1 | def count_trues(seq): 2 | return sum(bool(x) for x in seq) 3 | -------------------------------------------------------------------------------- /03_The_Python_Language/dict_comprehensions.py: -------------------------------------------------------------------------------- 1 | d = {s: i for (i, s) in enumerate(['zero', 'one', 'two'])} 2 | print(d) # prints: {'zero': 0, 'one': 1, 'two': 2} 3 | -------------------------------------------------------------------------------- /03_The_Python_Language/dictionaries.py: -------------------------------------------------------------------------------- 1 | {'x':42, 'y':3.14, 'z':7} # Dictionary with three items, str keys 2 | {1:2, 3:4} # Dictionary with two items, int keys 3 | {1:'za', 'br':23} # Dictionary with different key types 4 | {} # Empty dictionary 5 | 6 | dict(x=42, y=3.14, z=7) # Dictionary with three items, str keys 7 | dict([(1, 2), (3, 4)]) # Dictionary with two items, int keys 8 | dict([(1,'za'), ('br',23)]) # Dictionary with different key types 9 | dict() # Empty dictionary 10 | 11 | dict.fromkeys('hello', 2) # same as {'h':2, 'e':2, 'l':2, 'o':2} 12 | dict.fromkeys([1, 2, 3]) # same as {1:None, 2:None, 3:None} 13 | -------------------------------------------------------------------------------- /03_The_Python_Language/dictionary_operations.py: -------------------------------------------------------------------------------- 1 | d = {'x':42, 'y':3.14, 'z':7} 2 | d['x'] # 42 3 | d['z'] # 7 4 | d['a'] # raises KeyError exception 5 | 6 | d = {'x':42, 'y':3.14} 7 | d['a'] = 16 # d is now {'x':42, 'y':3.14, 'a':16} 8 | 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/ellipsis.py: -------------------------------------------------------------------------------- 1 | tally = dict.fromkeys(['A', 'B', None, ...], 0) 2 | -------------------------------------------------------------------------------- /03_The_Python_Language/else_clause_on_loop_statements.py: -------------------------------------------------------------------------------- 1 | some_container = list(range(5)) 2 | 3 | def is_ok(i): 4 | return i > 10 5 | 6 | 7 | for x in some_container: 8 | if is_ok(x): 9 | break # item x is satisfactory, terminate loop 10 | else: 11 | print('Beware: no satisfactory item was found in container') 12 | x = None 13 | -------------------------------------------------------------------------------- /03_The_Python_Language/for_statement.py: -------------------------------------------------------------------------------- 1 | """ 2 | for target in iterable: 3 | statement(s) 4 | """ 5 | 6 | for letter in 'ciao': 7 | print(f'give me a {letter}...') 8 | 9 | 10 | d = {'a': 0, 'b': 1, 'c': 2} 11 | for key, value in d.items(): 12 | if key and value: # print only truish keys and values 13 | print(key, value) 14 | 15 | 16 | prototype = [1, 'placeholder', 3] 17 | for prototype[1] in 'xyz': 18 | print(prototype) 19 | # prints [1, 'x', 3], then [1, 'y', 3], then [1, 'z', 3] 20 | 21 | 22 | someseq = [1, 2, 3] 23 | def process(i): 24 | print("?" * i) 25 | 26 | for x in someseq: 27 | process(x) 28 | print(f'Last item processed was {x}') # potential NameError if someseq is empty -------------------------------------------------------------------------------- /03_The_Python_Language/function_annotations.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> def f(a:'foo', b)->'bar': pass 3 | ... 4 | >>> f.__annotations__ 5 | {'a': 'foo', 'return': 'bar'} 6 | """ 7 | 8 | if __name__ == '__main__': 9 | # use doctest to simulate console sessions 10 | import doctest 11 | doctest.testmod(verbose=True, exclude_empty=True) 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/functions.py: -------------------------------------------------------------------------------- 1 | from math import sin, cos, tan, asin, acos, atan, log, exp 2 | 3 | def add_inverses(i_dict): 4 | for f in list(i_dict): # iterates over keys while mutating i_dict 5 | i_dict[i_dict[f]] = f 6 | 7 | math_map = {sin: asin, cos: acos, tan: atan, log: exp} 8 | add_inverses(math_map) 9 | 10 | 11 | for k, v in math_map.items(): 12 | print(k.__name__, v.__name__) 13 | 14 | 15 | 16 | def twice(x): 17 | return x*2 18 | 19 | print(twice(100)) 20 | print(twice('ciao')) 21 | -------------------------------------------------------------------------------- /03_The_Python_Language/generators.py: -------------------------------------------------------------------------------- 1 | def updown(N): 2 | for x in range(1, N): 3 | yield x 4 | for x in range(N, 0, -1): 5 | yield x 6 | for i in updown(3): 7 | print(i) # prints: 1 2 3 2 1 8 | 9 | 10 | def frange(start, stop, stride=1.0): 11 | start = float(start) 12 | while start < stop: 13 | yield start 14 | start += stride 15 | 16 | for f in frange(1, 2, stride=0.125): 17 | print(f'{f:.3f}') 18 | -------------------------------------------------------------------------------- /03_The_Python_Language/generators_yield_from.py: -------------------------------------------------------------------------------- 1 | def updown(N): 2 | yield from range(1, N) 3 | yield from range(N, 0, -1) 4 | 5 | for i in updown(3): 6 | print(i) # prints: 1 2 3 2 1 7 | -------------------------------------------------------------------------------- /03_The_Python_Language/indexing_a_sequence.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, 3, 4] 2 | x[1] # 2 3 | x[-1] # 4 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/iterators.py: -------------------------------------------------------------------------------- 1 | """ 2 | for x in c: 3 | statement(s) 4 | 5 | is exactly equivalent to: 6 | 7 | _temporary_iterator = iter(c) 8 | while True: 9 | try: 10 | x = next(_temporary_iterator) 11 | except StopIteration: 12 | break 13 | statement(s) 14 | """ 15 | 16 | """ 17 | >>> iterable = [1, 2] 18 | >>> for i in iterable: 19 | ... for j in iterable: 20 | ... print(i, j) 21 | ... 22 | 1 1 23 | 1 2 24 | 2 1 25 | 2 2 26 | 27 | >>> iterator = iter([1, 2]) 28 | >>> for i in iterator: 29 | ... for j in iterator: 30 | ... print(i, j) 31 | ... 32 | 1 2 33 | """ 34 | 35 | if __name__ == '__main__': 36 | # use doctest to simulate console sessions 37 | import doctest 38 | doctest.testmod(verbose=True, exclude_empty=True) 39 | -------------------------------------------------------------------------------- /03_The_Python_Language/lambda_expressions.py: -------------------------------------------------------------------------------- 1 | a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9] 2 | low = 3 3 | high = 7 4 | print(list(filter(lambda x: low <= x < high, a_list))) # returns: [3, 4, 5, 6] 5 | 6 | 7 | a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9] 8 | def within_bounds(value, low=3, high=7): 9 | return low <= value < high 10 | print(list(filter(within_bounds, a_list))) # returns: [3, 4, 5, 6] 11 | -------------------------------------------------------------------------------- /03_The_Python_Language/list_comprehensions.py: -------------------------------------------------------------------------------- 1 | """ 2 | [ expression for target in iterable lc-clauses ] 3 | 4 | lc-clauses is a series of zero or more clauses, each with one of the two forms: 5 | for target in iterable 6 | or 7 | if expression 8 | """ 9 | 10 | some_sequence = [10, 20, 30] 11 | listoflists = [[1, 2, 3], [4, 5, 6], [90, 91, 92]] 12 | 13 | result = [x+1 for x in some_sequence] 14 | 15 | result = [] 16 | for x in some_sequence: 17 | result.append(x+1) 18 | 19 | 20 | result = [x+1 for x in some_sequence if x > 23] 21 | 22 | result = [] 23 | for x in some_sequence: 24 | if x>23: 25 | result.append(x+1) 26 | 27 | 28 | result = [x for sublist in listoflists for x in sublist] 29 | 30 | result = [] 31 | for sublist in listoflists: 32 | for x in sublist: 33 | result.append(x) 34 | -------------------------------------------------------------------------------- /03_The_Python_Language/lists.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, 3, 4] 2 | x[1] = 42 # x is now [1, 42, 3, 4] 3 | 4 | x = [1, 2, 3, 4] 5 | x[1:3] = [22, 33, 44] # x is now [1, 22, 33, 44, 4] 6 | x[1:4] = [8, 9] # x is now [1, 8, 9, 4] 7 | 8 | x = [1, 2, 3, 4, 5] 9 | del x[1] # x is now [1, 3, 4, 5] 10 | del x[::2] # x is now [3, 5] 11 | -------------------------------------------------------------------------------- /03_The_Python_Language/literals.py: -------------------------------------------------------------------------------- 1 | 42 # Integer literal 2 | 3.14 # Floating-point literal 3 | 1.0j # Imaginary literal 4 | 'hello' # String literal 5 | "world" # Another string literal 6 | """Good 7 | night""" # Triple-quoted string literal, spanning 2 lines 8 | 9 | [42, 3.14, 'hello'] # List 10 | [] # Empty list 11 | 100, 200, 300 # Tuple 12 | () # Empty tuple 13 | {'x': 42, 'y': 3.14} # Dictionary 14 | {} # Empty dictionary 15 | {1, 2, 4, 8, 'string'} # Set 16 | 17 | # There is no literal to denote an empty set; use set() instead 18 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_as_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> match subject: 3 | ... case ((0 | 1) as x) | 2: print(x) 4 | ... 5 | Traceback (most recent call last): 6 | ... 7 | SyntaxError: alternative patterns bind different names 8 | >>> match subject: 9 | ... case (2 | x): print(x) 10 | ... 11 | Traceback (most recent call last): 12 | ... 13 | SyntaxError: alternative patterns bind different names 14 | >>> match 42: 15 | ... case (1 | 2 | 42) as x: print(x) 16 | 42 17 | """ 18 | 19 | if __name__ == '__main__': 20 | # use doctest to simulate console sessions 21 | import doctest 22 | doctest.testmod(verbose=True, exclude_empty=True) 23 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_capture_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in 42, 'string', ('tu', 'ple'), ['list'], object: 3 | ... match subject: 4 | ... case x: assert x == subject 5 | ... 6 | """ 7 | 8 | if __name__ == '__main__': 9 | # use doctest to simulate console sessions 10 | import doctest 11 | doctest.testmod(verbose=True, exclude_empty=True) 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_class_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in 42, 42.0: 3 | ... match subject: 4 | ... case int(x): print('integer', x) 5 | ... case float(x): print('float', x) 6 | ... 7 | integer 42 8 | float 42.0 9 | """ 10 | 11 | if __name__ == '__main__': 12 | # use doctest to simulate console sessions 13 | import doctest 14 | doctest.testmod(verbose=True, exclude_empty=True) 15 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_configuring_classes_for_positional_matching.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> class Color: 3 | ... __match_args__ = ('red', 'green', 'blue') 4 | ... def __init__(self, r, g, b, name='anonymous'): 5 | ... self.name = name 6 | ... self.red, self.green, self.blue = r, g, b 7 | ... 8 | >>> color_red = Color(255, 0, 0, 'red') 9 | >>> color_blue = Color(0, 0, 255) 10 | >>> for subject in (42.0, color_red, color_blue): 11 | ... match subject: 12 | ... case float(x): 13 | ... print('float', x) 14 | ... case Color(red, green, blue, name='red'): 15 | ... print(type(subject).__name__, subject.name, red, green, blue) 16 | ... case Color(red, green, 255) as color: 17 | ... print(type(subject).__name__, color.name, red, green, color.blue) 18 | ... case _: print(type(subject), subject) 19 | ... 20 | float 42.0 21 | Color red 255 0 0 22 | Color anonymous 0 0 255 23 | >>> match color_red: 24 | ... case Color(red, green, blue, name): 25 | ... print("matched") 26 | ... 27 | Traceback (most recent call last): 28 | ... 29 | TypeError: Color() accepts 3 positional sub-patterns (4 given) 30 | """ 31 | 32 | if __name__ == '__main__': 33 | # use doctest to simulate console sessions 34 | import doctest 35 | doctest.testmod(verbose=True, exclude_empty=True) 36 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_guards.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in range(5): 3 | ... match subject: 4 | ... case int(i) if i % 2 == 0: print(i, "is even") 5 | ... 6 | 0 is even 7 | 2 is even 8 | 4 is even 9 | """ 10 | 11 | if __name__ == '__main__': 12 | # use doctest to simulate console sessions 13 | import doctest 14 | doctest.testmod(verbose=True, exclude_empty=True) 15 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_literal_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in (42, 42.0, 42.1, 1+1j, b'abc', 'abc'): 3 | ... print(subject, end=': ') 4 | ... match subject: 5 | ... case 42: print('integer') # note this matches 42.0,too! 6 | ... case 42.1: print('float') 7 | ... case 1+1j: print('complex') 8 | ... case b'abc': print('bytestring') 9 | ... case 'abc': print('string') 10 | 42: integer 11 | 42.0: integer 12 | 42.1: float 13 | (1+1j): complex 14 | b'abc': bytestring 15 | abc: string 16 | """ 17 | 18 | if __name__ == '__main__': 19 | # use doctest to simulate console sessions 20 | import doctest 21 | doctest.testmod(verbose=True, exclude_empty=True) 22 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_mapping_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> match {1: "two", "two": 1}: 3 | ... case {1: v1, "two": v2}: print(v1, v2) 4 | ... 5 | two 1 6 | 7 | >>> match {1: "two", "two": 1}: 8 | ... case {1: v1} as v2: print(v1, v2) 9 | ... 10 | two {1: 'two', 'two': 1} 11 | 12 | >>> match {1: 'one', 2: 'two', 3: 'three'}: 13 | ... case {2: middle, **others}: print(middle, others) 14 | ... 15 | two {1: 'one', 3: 'three'} 16 | """ 17 | 18 | if __name__ == '__main__': 19 | # use doctest to simulate console sessions 20 | import doctest 21 | doctest.testmod(verbose=True, exclude_empty=True) 22 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_or_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in range(5): 3 | ... match subject: 4 | ... case 1 | 3: print('odd') 5 | ... case 0 | 2 | 4: print('even') 6 | even 7 | odd 8 | even 9 | odd 10 | even 11 | """ 12 | 13 | if __name__ == '__main__': 14 | # use doctest to simulate console sessions 15 | import doctest 16 | doctest.testmod(verbose=True, exclude_empty=True) 17 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_sequence_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for sequence in (["one", "two", "three"], range(2), range(6)): 3 | ... match sequence: 4 | ... case (first, *vars, last): print(first, vars, last) 5 | one ['two'] three 6 | 0 [] 1 7 | 0 [1, 2, 3, 4] 5 8 | """ 9 | 10 | if __name__ == '__main__': 11 | # use doctest to simulate console sessions 12 | import doctest 13 | doctest.testmod(verbose=True, exclude_empty=True) 14 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_value_patterns.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> class m: v1 = "one"; v2 = 2; v3 = 2.56 3 | ... 4 | >>> match ('one', 2, 2.56): 5 | ... case (m.v1, m.v2, m.v3): print('matched') 6 | ... 7 | matched 8 | """ 9 | 10 | if __name__ == '__main__': 11 | # use doctest to simulate console sessions 12 | import doctest 13 | doctest.testmod(verbose=True, exclude_empty=True) 14 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_value_patterns_global_access.py: -------------------------------------------------------------------------------- 1 | import sys 2 | g = sys.modules[__name__] 3 | v1 = "one"; v2 = 2; v3 = 2.56 4 | match ('one', 2, 2.56): 5 | case (g.v1, g.v2, g.v3): print('matched') 6 | 7 | # prints 'matched' 8 | -------------------------------------------------------------------------------- /03_The_Python_Language/match_statement_wildcard_pattern.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> for subject in 42, 'string', ('tu', 'ple'), ['list'], object: 3 | ... match subject: 4 | ... case _: print('matched', subject) 5 | ... 6 | matched 42 7 | matched string 8 | matched ('tu', 'ple') 9 | matched ['list'] 10 | matched 11 | """ 12 | 13 | if __name__ == '__main__': 14 | # use doctest to simulate console sessions 15 | import doctest 16 | doctest.testmod(verbose=True, exclude_empty=True) 17 | -------------------------------------------------------------------------------- /03_The_Python_Language/mutable_default_parameter_values.py: -------------------------------------------------------------------------------- 1 | def f(x, y=[]): 2 | y.append(x) 3 | return id(y), y 4 | 5 | print(f(23)) # prints: (4302354376, [23]) 6 | z = [] 7 | print(f(42)) # prints: (4302354376, [23, 42]) 8 | 9 | 10 | def f(x, y=None): 11 | if y is None: 12 | y = [] 13 | y.append(x) 14 | return id(y), y 15 | 16 | print(f(23)) # prints: (4302354376, [23]) 17 | z = [] # force creation of object between calls so id's don't get reused 18 | # print(id(z)) 19 | print(f(42)) # prints: (4302180040, [42]) 20 | 21 | 22 | 23 | def costly_computation(a): 24 | print(f"compute using {a}") 25 | return a * a 26 | 27 | def cached_compute(x, _cache={}): 28 | if x not in _cache: 29 | _cache[x] = costly_computation(x) 30 | return _cache[x] 31 | 32 | 33 | print(cached_compute(100)) 34 | print(cached_compute(200)) 35 | print(cached_compute(100)) 36 | -------------------------------------------------------------------------------- /03_The_Python_Language/namespaces_global_statement.py: -------------------------------------------------------------------------------- 1 | _count = 0 2 | def counter(): 3 | global _count 4 | _count += 1 5 | return _count 6 | 7 | for i in range(10): 8 | print(i, counter()) 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/namespaces_nested_functions_and_nested_scopes.py: -------------------------------------------------------------------------------- 1 | def percent1(a, b, c): 2 | def pc(x, total=a+b+c): 3 | return (x*100.0) / total 4 | print('Percentages are:', pc(a), pc(b), pc(c)) 5 | 6 | 7 | def percent2(a, b, c): 8 | def pc(x): 9 | return (x*100.0) / (a+b+c) 10 | print('Percentages are:', pc(a), pc(b), pc(c)) 11 | 12 | 13 | def make_adder(augend): 14 | def add(addend): 15 | return addend+augend 16 | return add 17 | 18 | add5 = make_adder(5) 19 | add9 = make_adder(9) 20 | 21 | print(add5(100)) # prints 105 22 | print(add9(100)) # prints 109 23 | 24 | 25 | def make_counter(): 26 | count = 0 27 | 28 | def counter(): 29 | nonlocal count 30 | count += 1 31 | return count 32 | 33 | return counter 34 | 35 | 36 | c1 = make_counter() 37 | c2 = make_counter() 38 | print(c1(), c1(), c1()) # prints: 1 2 3 39 | print(c2(), c2()) # prints: 1 2 40 | print(c1(), c2(), c1()) # prints: 4 3 5 41 | -------------------------------------------------------------------------------- /03_The_Python_Language/numbers.py: -------------------------------------------------------------------------------- 1 | 1, 23, 3493 # Decimal integer literals 2 | 0b010101, 0b110010, 0B01 # Binary integer literals 3 | 0o1, 0o27, 0o6645, 0O777 # Octal integer literals 4 | 0x1, 0x17, 0xDA5, 0xda5, 0Xff # Hexadecimal integer literals 5 | 6 | 0., 0.0, .0, 1., 1.0, 1e0, 1.e0, 1.0e0 # Floating-point literals 7 | 8 | 0j, 0.j, 0.0j, .0j, 1j, 1.j, 1.0j, 1e0j, 1.e0j, 1.0e0j # Complex number literals 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/pass_statement.py: -------------------------------------------------------------------------------- 1 | if condition1(x): 2 | process1(x) 3 | elif x > 23 or (x < 5 and condition2(x)): 4 | pass # nothing to be done in this case 5 | elif condition3(x): 6 | process3(x) 7 | else: 8 | process_default(x) 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/recursion.py: -------------------------------------------------------------------------------- 1 | tree = (23, (42, (5, None, None), (55, None, None)), (94, None,None)) 2 | 3 | def rec(t): 4 | yield t[0] 5 | for i in (1, 2): 6 | if t[i] is not None: 7 | yield from rec(t[i]) 8 | 9 | for node in rec(tree): 10 | print(node) 11 | 12 | 13 | 14 | def norec(t): 15 | stack = [t] 16 | while stack: 17 | t = stack.pop() 18 | yield t[0] 19 | for i in (2, 1): 20 | if t[i] is not None: 21 | stack.append(t[i]) 22 | 23 | for node in norec(tree): 24 | print(node) 25 | -------------------------------------------------------------------------------- /03_The_Python_Language/sequences_bytearray.py: -------------------------------------------------------------------------------- 1 | ba = bytearray([97, 98, 99]) # like bytes, can construct from a sequence of ints 2 | ba[1] = 97 # unlike bytes, contents can be modified 3 | print(ba.decode()) # prints 'aac' 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/sequences_bytes.py: -------------------------------------------------------------------------------- 1 | b'abc' 2 | bytes([97, 98, 99]) # same as the previous line 3 | rb'\ = solidus' # a raw bytes literal, containing a '\' 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/sequences_lists.py: -------------------------------------------------------------------------------- 1 | [42, 3.14, 'hello'] # List with three items 2 | [100] # List with one item 3 | [] # Empty list 4 | 5 | list('wow') 6 | 7 | ['w', 'o', 'w'] 8 | -------------------------------------------------------------------------------- /03_The_Python_Language/sequences_strings.py: -------------------------------------------------------------------------------- 1 | 'This is a literal string' 2 | "This is another string" 3 | 4 | 'I\'m a Python fanatic' # you can escape a quote 5 | "I'm a Python fanatic" # this way may be more readable 6 | 7 | 'A not very long string \ 8 | that spans two lines' # comment not allowed on previous line 9 | 10 | 'A not very long string\n\ 11 | that prints on two lines' # comment not allowed on previous line 12 | 13 | """An even bigger 14 | string that spans 15 | three lines""" # comments not allowed on previous lines 16 | 17 | the_text = """\ 18 | First line 19 | Second line 20 | """ # the same as "First line\nSecond line\n" but more readable 21 | 22 | copyright_character = '\N{Copyright Sign}' 23 | 24 | marypop = ('supercalifragilistic' # Open paren->logical line continues 25 | 'expialidocious') # Indentation ignored in continuation line 26 | 27 | -------------------------------------------------------------------------------- /03_The_Python_Language/sequences_tuples.py: -------------------------------------------------------------------------------- 1 | (100, 200, 300) # Tuple with three items 2 | (3.14,) # Tuple with 1 item needs trailing comma 3 | () # Empty tuple (parentheses NOT optional) 4 | 5 | tuple('wow') 6 | 7 | ('w', 'o', 'w') 8 | 9 | -------------------------------------------------------------------------------- /03_The_Python_Language/set_comprehensions.py: -------------------------------------------------------------------------------- 1 | s = {n//2 for n in range(10)} 2 | print(sorted(s)) # prints: [0, 1, 2, 3, 4] 3 | -------------------------------------------------------------------------------- /03_The_Python_Language/set_operations.py: -------------------------------------------------------------------------------- 1 | S = set([1, 2, 3]) 2 | # destructive loop 3 | while S: 4 | item = S.pop() 5 | # ...handle item... 6 | 7 | # non-destructive loop 8 | for item in S: 9 | # ...handle item... 10 | pass 11 | 12 | -------------------------------------------------------------------------------- /03_The_Python_Language/sets.py: -------------------------------------------------------------------------------- 1 | {42, 3.14, 'hello'} # Literal for a set with three items 2 | {100} # Literal for a set with one item 3 | set() # Empty set (no literal for empty set, as {} in an empty dict!) 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/slicing_a_sequence.py: -------------------------------------------------------------------------------- 1 | x = [1, 2, 3, 4] 2 | x[1:3] # [2, 3] 3 | x[1:] # [2, 3, 4] 4 | x[:2] # [1, 2] 5 | 6 | y = list(range(10)) # values from 0-9 7 | y[-5:] # last five items 8 | # [5, 6, 7, 8, 9] 9 | y[::2] # every other item 10 | # [0, 2, 4, 6, 8] 11 | y[10:0:-2] # every other item, in reverse order 12 | # [9, 7, 5, 3, 1] 13 | y[:0:-2] # every other item, in reverse order (simpler) 14 | # [9, 7, 5, 3, 1] 15 | y[::-2] # every other item, in reverse order (best) 16 | # [9, 7, 5, 3, 1] 17 | -------------------------------------------------------------------------------- /03_The_Python_Language/sorting_a_list.py: -------------------------------------------------------------------------------- 1 | mylist = ['alpha', 'Beta', 'GAMMA'] 2 | mylist.sort() # ['Beta', 'GAMMA', 'alpha'] 3 | mylist.sort(key=str.lower) # ['alpha', 'Beta', 'GAMMA'] 4 | -------------------------------------------------------------------------------- /03_The_Python_Language/underscores_in_numeric_literals.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> 100_000.000_0001, 0x_FF_FF, 0o7_777, 0b_1010_1010 3 | (100000.0000001, 65535, 4095, 170) 4 | """ 5 | 6 | if __name__ == '__main__': 7 | # use doctest to simulate console sessions 8 | import doctest 9 | doctest.testmod(verbose=True, exclude_empty=True) 10 | -------------------------------------------------------------------------------- /03_The_Python_Language/unicode_normalization_can_create_unintended_overlap_between_variables.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> a, o = 100, 101 3 | >>> ª, º = 200, 201 4 | 5 | # not "100 101 200 201" 6 | >>> print(a, o, ª, º) 7 | 200 201 200 201 8 | """ 9 | 10 | if __name__ == '__main__': 11 | # use doctest to simulate console sessions 12 | import doctest 13 | doctest.testmod(verbose=True, exclude_empty=True) 14 | -------------------------------------------------------------------------------- /03_The_Python_Language/while_statement.py: -------------------------------------------------------------------------------- 1 | """ 2 | while expression: 3 | statement(s) 4 | """ 5 | 6 | count = 0 7 | x = 99 8 | while x > 0: 9 | x //= 2 # floor division 10 | count += 1 11 | print('The approximate log2 is', count) 12 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/attribute_reference_basics.py: -------------------------------------------------------------------------------- 1 | class B: 2 | a = 23 3 | b = 45 4 | def f(self): 5 | print('method f in class B') 6 | def g(self): 7 | print('method g in class B') 8 | class C(B): 9 | b = 67 10 | c = 89 11 | d = 123 12 | def g(self): 13 | print('method g in class C') 14 | def h(self): 15 | print('method h in class C') 16 | x = C() 17 | x.d = 77 18 | x.e = 88 19 | 20 | print(x.e, x.d, x.c, x.b, x.a) # prints: 88 77 89 67 23 21 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/bound_and_unbound_methods.py: -------------------------------------------------------------------------------- 1 | # excruciating low-level detail the conceptual steps involved in 2 | # a method call with the normal syntax x.name(arg) 3 | def f(a, b): 4 | '''a function f with two arguments''' 5 | print(a, b) 6 | class C: 7 | name = f 8 | x = C() 9 | 10 | arg = 99 11 | x.name(b=arg) 12 | x.__class__.__dict__['name'](x, arg) 13 | 14 | 15 | def make_adder_as_closure(augend): 16 | def add(addend, _augend=augend): 17 | return addend+_augend 18 | return add 19 | 20 | def make_adder_as_bound_method(augend): 21 | class Adder: 22 | def __init__(self, augend): 23 | self.augend = augend 24 | def add(self, addend): 25 | return addend+self.augend 26 | return Adder(augend).add 27 | 28 | def make_adder_as_callable_instance(augend): 29 | class Adder: 30 | def __init__(self, augend): 31 | self.augend = augend 32 | def __call__(self, addend): 33 | return addend+self.augend 34 | return Adder(augend) 35 | 36 | 37 | fn = make_adder_as_closure(1000) 38 | print(fn(1001)) 39 | 40 | fn = make_adder_as_bound_method(2000) 41 | print(fn(1001)) 42 | 43 | fn = make_adder_as_callable_instance(3000) 44 | print(fn(1001)) 45 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/class_body.py: -------------------------------------------------------------------------------- 1 | 2 | class C1: 3 | x = 23 4 | print(C1.x) # prints: 23 5 | 6 | 7 | class C2: 8 | pass 9 | C2.x = 23 10 | print(C2.x) # prints: 23 11 | 12 | 13 | print(C1.__name__, C1.__bases__) 14 | 15 | 16 | class C3: 17 | x = 23 18 | y = x + 22 # must use just x, not C3.x 19 | 20 | 21 | print(C3.x, C3.y) 22 | 23 | 24 | class C4: 25 | x = 23 26 | def amethod(self): 27 | print(C4.x) # must use C4.x or self.x, not just x! 28 | 29 | 30 | C4().amethod() 31 | 32 | 33 | class C5: 34 | def hello(self): 35 | print('Hello') 36 | 37 | 38 | an_instance = C5() 39 | an_instance.hello() 40 | 41 | 42 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/class_level_methods.py: -------------------------------------------------------------------------------- 1 | # staticmethod example 2 | class AClass: 3 | def astatic(): 4 | print('a static method') 5 | astatic = staticmethod(astatic) 6 | an_instance = AClass() 7 | AClass.astatic() # prints: a static method 8 | an_instance.astatic() # prints: a static method 9 | 10 | # classmethod example 11 | class ABase: 12 | def aclassmet(cls): 13 | print('a class method for', cls.__name__) 14 | aclassmet = classmethod(aclassmet) 15 | class ADeriv(ABase): 16 | pass 17 | 18 | b_instance = ABase() 19 | d_instance = ADeriv() 20 | ABase.aclassmet() # prints: a class method for ABase 21 | b_instance.aclassmet() # prints: a class method for ABase 22 | ADeriv.aclassmet() # prints: a class method for ADeriv 23 | d_instance.aclassmet() # prints: a class method for ADeriv -------------------------------------------------------------------------------- /04_Object_Oriented_Python/decorators_functools_wraps.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | 4 | def announce(f): 5 | @functools.wraps(f) 6 | def wrap(*a, **k): 7 | print(f'Calling {f.__name__}') 8 | return f(*a, **k) 9 | 10 | return wrap 11 | 12 | @announce 13 | def f1(): 14 | print("f1 doing work") 15 | 16 | f1() 17 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/decorators_showdoc.py: -------------------------------------------------------------------------------- 1 | def showdoc(f): 2 | if f.__doc__: 3 | print(f'{f.__name__}: {f.__doc__}') 4 | else: 5 | print(f'{f.__name__}: No docstring!') 6 | return f 7 | 8 | 9 | @showdoc 10 | def f1(): 11 | """a docstring""" # prints: f1: a docstring 12 | 13 | 14 | @showdoc 15 | def f2(): 16 | pass # prints: f2: No docstring! 17 | 18 | f1() 19 | f2() 20 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/descriptors.py: -------------------------------------------------------------------------------- 1 | class Const: # an overriding descriptor, see later 2 | def __init__(self, value): 3 | self.value = value 4 | 5 | def __set__(self, *_): # silently ignore any attempt at setting 6 | pass 7 | 8 | def __get__(self, *_): # always return the constant value 9 | return self.value 10 | 11 | def __delete__(self, *_): # const cannot be deleted 12 | pass 13 | 14 | 15 | class X: 16 | c = Const(23) 17 | 18 | 19 | x = X() 20 | print(x.c) # prints: 23 21 | x.c = 42 # silently ignored 22 | print(x.c) # prints: 23 23 | del x.c 24 | print(x.c) # prints: 23 25 | 26 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/enum_simple_example.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class Color(Enum): 5 | RED = 1 6 | GREEN = 2 7 | BLUE = 3 8 | 9 | 10 | class Size(Enum): 11 | XS = auto() 12 | S = auto() 13 | M = auto() 14 | L = auto() 15 | 16 | 17 | class Nothing(Enum): 18 | ZIP = 0 19 | ZILCH = 0 20 | NADA = 0 21 | 22 | 23 | print(list(Nothing)) 24 | print(Nothing.ZIP is Nothing.ZILCH) 25 | print(Nothing.ZIP == Nothing.ZILCH) 26 | 27 | # these improper comparisons and expressions raise 28 | # TypeError exceptions 29 | print(Color.RED > Size.XS) 30 | print(Size.L * Color.BLUE) 31 | 32 | 33 | import dataclasses 34 | 35 | 36 | @dataclasses.dataclass 37 | class Apparel: 38 | size: Size 39 | color: Color 40 | 41 | shirt = Apparel(Size.S, Color.RED) 42 | 43 | # fails type check 44 | coat = Apparel(Color.BLUE, Size.XS) 45 | 46 | coat = Apparel(Size.XS, Color.BLUE) 47 | 48 | class Customer: 49 | def __init__(self, size: Size, favorite_color: Color): 50 | self.size = size 51 | self.favorite_color = favorite_color 52 | 53 | def can_wear(self, item: Apparel) -> bool: 54 | return item.size is self.size and item.color is self.favorite_color 55 | 56 | def could_buy(self, inventory: list[Apparel]) -> list[Apparel]: 57 | return [item for item in inventory if self.can_wear(item)] 58 | 59 | 60 | def available_colors(inventory: list[Apparel]) -> set[Color]: 61 | return set(item.color for item in inventory) 62 | 63 | # fails type check 64 | z: set[int] = available_colors([shirt, coat]) 65 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/enum_stat_file_permissions.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import stat 3 | 4 | 5 | class Permission(enum.Flag): 6 | EXEC_OTH = stat.S_IXOTH 7 | WRITE_OTH = stat.S_IWOTH 8 | READ_OTH = stat.S_IROTH 9 | EXEC_GRP = stat.S_IXGRP 10 | WRITE_GRP = stat.S_IWGRP 11 | READ_GRP = stat.S_IRGRP 12 | EXEC_USR = stat.S_IXUSR 13 | WRITE_USR = stat.S_IWUSR 14 | READ_USR = stat.S_IRUSR 15 | 16 | @classmethod 17 | def from_stat(cls, stat_result): 18 | return cls(stat_result.st_mode & 0o777) 19 | 20 | 21 | from pathlib import Path 22 | 23 | cur_dir = Path.cwd() 24 | dir_perm = Permission.from_stat(cur_dir.stat()) 25 | if dir_perm & Permission.READ_OTH: 26 | print(f'{cur_dir} is readable by users outside the owner group') 27 | 28 | # the following raises TypeError; Flag enums do not support order comparisons 29 | print(Permission.READ_USR > Permission.READ_OTH) 30 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/enum_using_ints.py: -------------------------------------------------------------------------------- 1 | # colors 2 | RED = 1 3 | GREEN = 2 4 | BLUE = 3 5 | 6 | # sizes 7 | XS = 1 8 | S = 2 9 | M = 3 10 | L = 4 11 | 12 | # these improper comparisons and expressions are valid Python, 13 | # since all are ints 14 | print(RED > XS) 15 | print(L * BLUE) 16 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/factory_function.py: -------------------------------------------------------------------------------- 1 | class SpecialCase: 2 | def amethod(self): 3 | print('special') 4 | 5 | class NormalCase: 6 | def amethod(self): 7 | print('normal') 8 | 9 | def appropriate_case(isnormal=True): 10 | if isnormal: 11 | return NormalCase() 12 | else: 13 | return SpecialCase() 14 | 15 | aninstance = appropriate_case(isnormal=False) 16 | aninstance.amethod() # prints: special 17 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/getattribute_list_no_append.py: -------------------------------------------------------------------------------- 1 | class listNoAppend(list): 2 | def __getattribute__(self, name): 3 | if name == 'append': 4 | raise AttributeError(name) 5 | return list.__getattribute__(self, name) 6 | 7 | lna = listNoAppend([1, 2, 3]) 8 | lna.extend([4, 5, 6]) 9 | print(lna) 10 | 11 | # raises AttributeError 12 | lna.append(7) 13 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/inheritance_cooperative_superclass_method_calling.py: -------------------------------------------------------------------------------- 1 | print("Incorrect way (using explicit superclass)") 2 | 3 | class A: 4 | def met(self): 5 | print('A.met') 6 | class B(A): 7 | def met(self): 8 | print('B.met') 9 | A.met(self) 10 | class C(A): 11 | def met(self): 12 | print('C.met') 13 | A.met(self) 14 | class D(B,C): 15 | def met(self): 16 | print('D.met') 17 | B.met(self) 18 | C.met(self) 19 | 20 | D().met() 21 | 22 | 23 | print("\nCorrect way (using super())") 24 | 25 | class A: 26 | def met(self): 27 | print('A.met') 28 | class B(A): 29 | def met(self): 30 | print('B.met') 31 | super().met() 32 | class C(A): 33 | def met(self): 34 | print('C.met') 35 | super().met() 36 | class D(B,C): 37 | def met(self): 38 | print('D.met') 39 | super().met() 40 | 41 | D().met() 42 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/inheritance_delegating_to_superclass_methods.py: -------------------------------------------------------------------------------- 1 | class Base: 2 | def greet(self, name): 3 | print('Welcome', name) 4 | 5 | class Sub(Base): 6 | def greet(self, name): 7 | print('Well Met and', end=' ') 8 | Base.greet(self, name) 9 | 10 | x = Sub() 11 | x.greet('Alex') 12 | 13 | 14 | class Base: 15 | def __init__(self): 16 | self.anattribute = 23 17 | 18 | # this form (calling Base explicitly) is not encouraged 19 | class Derived(Base): 20 | def __init__(self): 21 | Base.__init__(self) 22 | self.anotherattribute = 45 23 | 24 | # using super() is the preferred form 25 | class Derived(Base): 26 | def __init__(self): 27 | super().__init__() 28 | self.anotherattribute = 45 29 | 30 | z = Derived() 31 | print(vars(z)) 32 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/inheritance_overriding_attributes.py: -------------------------------------------------------------------------------- 1 | class B: 2 | a = 23 3 | b = 45 4 | def f(self): 5 | print('method f in class B') 6 | def g(self): 7 | print('method g in class B') 8 | 9 | class C(B): 10 | b = 67 11 | c = 89 12 | d = 123 13 | def g(self): 14 | print('method g in class C') 15 | def h(self): 16 | print('method h in class C') 17 | 18 | b = B() 19 | c = C() 20 | for obj in (b, c): 21 | print(type(obj).__name__) 22 | print(f'obj.a = {getattr(obj, "a", "")}') 23 | print(f'obj.b = {getattr(obj, "b", "")}') 24 | print(f'obj.c = {getattr(obj, "c", "")}') 25 | print(f'obj.d = {getattr(obj, "d", "")}') 26 | print(f'obj.f() = {obj.f()}') 27 | print(f'obj.g() = {obj.g()}') 28 | print(f'obj.h() = {obj.h() if hasattr(obj, "h") else ""}') 29 | print() 30 | 31 | 32 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/instances.py: -------------------------------------------------------------------------------- 1 | class C5: 2 | def hello(self): 3 | print('Hello') 4 | 5 | class C6: 6 | def __init__(self, n): 7 | self.x = n 8 | 9 | another_instance = C6(42) 10 | 11 | an_instance = C5() 12 | an_instance.hello() # prints: Hello 13 | print(another_instance.x) # prints: 42 14 | 15 | 16 | class C7: 17 | pass 18 | z = C7() 19 | z.x = 23 20 | print(z.x) # prints: 23 21 | 22 | print(z.__class__.__name__, z.__dict__) # prints: C7 {'x':23} 23 | 24 | z.y = 45 25 | z.__dict__['z'] = 67 26 | print(z.x, z.y, z.z) # prints: 23 45 67 27 | 28 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/metaclass_alternatives.py: -------------------------------------------------------------------------------- 1 | # using __init_subclass__ 2 | class C: 3 | def __init_subclass__(cls, foo=None, **kw): 4 | print(cls, kw) 5 | super().__init_subclass__(**kw) 6 | 7 | class D(C, foo='bar'): 8 | pass 9 | 10 | 11 | 12 | # using __set_name__ 13 | class Attrib: 14 | def __set_name__(self, cls, name): 15 | print(f'Attribute {name!r} added to {cls}') 16 | 17 | class AClass: 18 | some_name = Attrib() 19 | 20 | 21 | 22 | class MyMeta(type): 23 | def __str__(cls): 24 | return f"Beautiful class {cls.__name__!r}" 25 | 26 | class MyClass(metaclass=MyMeta): pass 27 | 28 | x = MyClass() 29 | print(type(x)) 30 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/per_instance_methods.py: -------------------------------------------------------------------------------- 1 | def fake_get_item(idx): 2 | return idx 3 | 4 | class MyClass: 5 | pass 6 | 7 | n = MyClass() 8 | 9 | # add a method to n and call it 10 | n.instance_method = fake_get_item 11 | print(n.instance_method(100)) 12 | 13 | # special methods cannot be added per-instance 14 | n.__getitem__ = fake_get_item 15 | print(n[23]) # raises TypeError -------------------------------------------------------------------------------- /04_Object_Oriented_Python/point_using_dataclass.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | 4 | @dataclasses.dataclass 5 | class Point: 6 | x: float 7 | y: float 8 | 9 | 10 | pt = Point(0.5, 0.5) 11 | print(pt) 12 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/point_using_dataclass_with_post_init.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | import time 3 | import math 4 | 5 | 6 | @dataclasses.dataclass 7 | class Point: 8 | x: float 9 | y: float 10 | create_time: float = 0 11 | 12 | def __post_init__(self): 13 | self.create_time = time.time() 14 | 15 | def distance_from(self, other: "Point"): 16 | dx, dy = self.x - other.x, self.y - other.y 17 | return math.hypot(dx, dy) 18 | 19 | @property 20 | def distance_from_origin(self): 21 | return self.distance_from(Point(0, 0)) 22 | 23 | 24 | pt = Point(0.5, 0.5) 25 | 26 | # will print members, but not properties 27 | print(pt) 28 | 29 | print(f"{pt.distance_from(Point(-1, -1))}") 30 | print(f"{pt.distance_from_origin}") 31 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/properties.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | class Rectangle: 4 | def __init__(self, width, height): 5 | self.width = width 6 | self.height = height 7 | def get_area(self): 8 | return self.width * self.height 9 | area = property(get_area, doc='area of the rectangle') 10 | 11 | r = Rectangle(100, 100) 12 | print(vars(r)) 13 | print(r.area) # <- property, no ()'s 14 | 15 | # property decorator 16 | class Rectangle: 17 | def __init__(self, width, height): 18 | self.width = width 19 | self.height = height 20 | @property 21 | def area(self): 22 | '''area of the rectangle''' 23 | return self.width * self.height 24 | 25 | r = Rectangle(200, 200) 26 | print(vars(r)) 27 | print(r.area) 28 | 29 | 30 | class Rectangle: 31 | def __init__(self, width, height): 32 | self.width = width 33 | self.height = height 34 | @property 35 | def area(self): 36 | '''area of the rectangle''' 37 | return self.width * self.height 38 | @area.setter 39 | def area(self, value): 40 | scale = math.sqrt(value/self.area) 41 | self.width *= scale 42 | self.height *= scale 43 | 44 | r = Rectangle(300, 300) 45 | print(vars(r)) 46 | print(r.area) # <- property, no ()'s 47 | r.area = 10000 48 | print(r.area) 49 | print(vars(r)) 50 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/properties_and_inheritance.py: -------------------------------------------------------------------------------- 1 | # calls base class method from property, even though 2 | # method is overridden in derived class (property is 3 | # defined using the base class method, and not resolved 4 | # to the derived class method) 5 | class B: 6 | def f(self): 7 | return 23 8 | g = property(f) 9 | class C(B): 10 | def f(self): 11 | return 42 12 | c = C() 13 | print(c.g) # prints: 23, not 42 14 | 15 | # call subclass method from property by binding property 16 | # to method that calls self.f(), to resolve to the derived 17 | # class's method 18 | class B: 19 | def f(self): 20 | return 23 21 | def _f_getter(self): 22 | return self.f() 23 | g = property(_f_getter) 24 | class C(B): 25 | def f(self): 26 | return 42 27 | c = C() 28 | print(c.g) # prints: 42, as expected 29 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/simple_bunch.py: -------------------------------------------------------------------------------- 1 | class SimpleBunch: 2 | def __init__(self, **fields): 3 | self._dict__ = fields 4 | 5 | 6 | p = SimpleBunch(x=2.3, y=4.5) 7 | print(p) 8 | 9 | 10 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton: 2 | _singletons = {} 3 | 4 | def __new__(cls, *args, **kwds): 5 | if cls not in cls._singletons: 6 | cls._singletons[cls] = obj = super().__new__(cls) 7 | obj._initialized = False 8 | return cls._singletons[cls] 9 | 10 | 11 | # Singleton example - apologies to J.R.R. Tolkien 12 | 13 | class RingOfPower: 14 | def __init__(self, ring_owner): 15 | self.owner = ring_owner 16 | self.bound_rings = [] 17 | 18 | def bind_in_the_darkness(self): 19 | return self.bound_rings[:] 20 | 21 | def rules(self, rings): 22 | self.bound_rings.extend(rings) 23 | 24 | def __str__(self): 25 | return f'{id(self)}: {self.owner}' 26 | 27 | 28 | class TheOneRing(Singleton, RingOfPower): 29 | def __init__(self): 30 | # guard against re-init on subsequent reference to singleton 31 | if self._initialized: 32 | return 33 | self._initialized = True 34 | super().__init__('The Dark Lord on His Dark Throne') 35 | 36 | 37 | # The Rings of Power are created 38 | elven_rings = [RingOfPower('Elven King Under The Sky') for _ in range(3)] 39 | dwarven_rings = [RingOfPower('Dwarf Lord in Their Hall of Stone') for _ in range(7)] 40 | mortal_men_rings = [RingOfPower('Mortal Man Doomed to Die') for _ in range(9)] 41 | one_ring = TheOneRing() 42 | 43 | # One Ring to rule them all 44 | one_ring.rules(elven_rings) 45 | one_ring.rules(dwarven_rings) 46 | one_ring.rules(mortal_men_rings) 47 | 48 | # list all the rings 49 | for ring in one_ring.bind_in_the_darkness(): 50 | print(ring) 51 | print(one_ring) 52 | 53 | # try to create another One Ring 54 | another = TheOneRing() 55 | print('one_ring', id(one_ring), id(one_ring.bound_rings)) 56 | print('another ', id(another), id(another.bound_rings)) # same id as for one_ring, and same instance var 57 | print('They are the same ring', another is one_ring) # prints True, they are the same object 58 | -------------------------------------------------------------------------------- /04_Object_Oriented_Python/slots_rectangle.py: -------------------------------------------------------------------------------- 1 | class Rectangle: 2 | def __init__(self, width, height): 3 | self.width = width 4 | self.height = height 5 | @property 6 | def area(self): 7 | '''area of the rectangle''' 8 | return self.width * self.height 9 | 10 | class OptimizedRectangle(Rectangle): 11 | __slots__ = 'width', 'height' 12 | 13 | # 3.8++ 14 | class OptimizedRectangle(Rectangle): 15 | __slots__ = {'width': 'rectangle width in pixels', 16 | 'height': 'rectangle height in pixels'} 17 | 18 | # display docstrings with help() 19 | help(OptimizedRectangle) 20 | 21 | 22 | opt_r = OptimizedRectangle(100, 100) 23 | print(vars(opt_r)) # <- prints empty dict, since __slots__ objects have no __dict__ 24 | print({name: getattr(opt_r, name) for name in opt_r.__slots__}) 25 | print(opt_r.area) -------------------------------------------------------------------------------- /05_Type_Annotations/TYPE_CHECKING_usage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from pandas import DataFrame 7 | 8 | def function_x() -> DataFrame: 9 | return None 10 | -------------------------------------------------------------------------------- /05_Type_Annotations/examples_cast.py: -------------------------------------------------------------------------------- 1 | def func(x: list[int] | list[str]): 2 | try: 3 | return sum(x) 4 | except TypeError: 5 | x = cast(list[str], x) 6 | return ','.join(x) 7 | -------------------------------------------------------------------------------- /05_Type_Annotations/examples_overload.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | @typing.overload 4 | def fn(*, key: str, value: int): 5 | ... 6 | 7 | 8 | @typing.overload 9 | def fn(*, strict: bool): 10 | ... 11 | 12 | 13 | def fn(**kwargs): 14 | # implementation goes here, including handling of differing 15 | # named arguments 16 | pass 17 | 18 | 19 | # valid calls 20 | fn(key='abc', value=100) 21 | fn(strict=True) 22 | 23 | # invalid calls 24 | fn(1) 25 | fn('abc') 26 | fn('abc', 100) 27 | fn(key='abc') 28 | fn(True) 29 | fn(strict=True, value=100) 30 | -------------------------------------------------------------------------------- /05_Type_Annotations/forward_referencing_types_from_future_import_annotations.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | class A: 4 | @classmethod 5 | def factory_method(cls) -> A: 6 | "... method body goes here ..." 7 | -------------------------------------------------------------------------------- /05_Type_Annotations/forward_referencing_types_not_yet_defined.py: -------------------------------------------------------------------------------- 1 | class A: 2 | @classmethod 3 | def factory_method(cls) -> 'A': 4 | "... method body goes here ..." 5 | -------------------------------------------------------------------------------- /05_Type_Annotations/generics_and_type_vars_accumulator.py: -------------------------------------------------------------------------------- 1 | import typing 2 | T = typing.TypeVar('T') 3 | 4 | 5 | class Accumulator(typing.Generic[T]): 6 | def __init__(self): 7 | self._contents: list[T] = [] 8 | 9 | def update(self, *args: T) -> None: 10 | self._contents.extend(args) 11 | 12 | def undo(self) -> None: 13 | # remove last value added 14 | if self._contents: 15 | self._contents.pop() 16 | 17 | def __len__(self) -> int: 18 | return len(self._contents) 19 | 20 | def __iter__(self) -> typing.Iterator[T]: 21 | return iter(self._contents) 22 | 23 | 24 | acc: Accumulator[int] = Accumulator() 25 | acc.update(1, 2, 3) 26 | print(sum(acc)) # prints 6 27 | acc.undo() 28 | print(sum(acc)) # prints 3 29 | 30 | 31 | # statements that generate mypy errors 32 | acc.update('A') 33 | # error: Argument 1 to "update" of "Accumulator" has incompatible type "str"; expected "int" 34 | 35 | print(''.join(acc)) 36 | # error: Argument 1 to "join" of "str" has incompatible type "Accumulator[int]"; expected "Iterable[str]" 37 | -------------------------------------------------------------------------------- /05_Type_Annotations/generics_and_type_vars_color_lookup.py: -------------------------------------------------------------------------------- 1 | color_lookup: dict[str, tuple[int, int, int]] = {} 2 | 3 | color_lookup['red'] = (255, 0, 0) 4 | color_lookup['red'][2] 5 | 6 | 7 | # statements that generate mypy errors 8 | color_lookup[0] 9 | # error: Invalid index type "int" for "dict[str, tuple[int, int, int]]"; expected type "str" 10 | color_lookup['red'] = (255, 0, 0, 0) 11 | # error: Incompatible types in assignment (expression has type "tuple[int, int, int, int]", target has type "tuple[int, int, int]") 12 | -------------------------------------------------------------------------------- /05_Type_Annotations/generics_and_type_vars_type_var_defns.py: -------------------------------------------------------------------------------- 1 | import typing 2 | import collections.abc 3 | from abc import ABC 4 | 5 | class MyAbstractClass(ABC): pass 6 | 7 | 8 | # T must be one of the types listed (int, float, complex, or str) 9 | T = typing.TypeVar('T', int, float, complex, str) 10 | # T must be a subclass of the class MyAbstractClass 11 | T = typing.TypeVar('T', bound=MyAbstractClass) 12 | # T must implement __len__ to be a valid subclass of the Sized protocol 13 | T = typing.TypeVar('T', bound=collections.abc.Sized) 14 | -------------------------------------------------------------------------------- /05_Type_Annotations/namedtuple.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | class HouseListingTuple(typing.NamedTuple): 5 | address: str 6 | list_price: int 7 | square_footage: int = 0 8 | condition: str = 'Good' 9 | 10 | 11 | listing1 = HouseListingTuple( 12 | address='123 Main', 13 | list_price=100_000, 14 | square_footage=2400, 15 | condition='Good', 16 | ) 17 | 18 | print(listing1.address) # prints 123 Main 19 | print(type(listing1)) # prints 20 | 21 | 22 | # statements that generate mypy errors 23 | listing2 = HouseListingTuple( 24 | '123 Main', 25 | ) 26 | # raises a runtime error TypeError: HouseListingTuple.__new__() missing 1 required positional argument: 'list_price' 27 | -------------------------------------------------------------------------------- /05_Type_Annotations/newtype.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | # ambiguous types for key and value 4 | # employee_department_map: dict[str, str] = {} 5 | 6 | EmpId = typing.NewType('EmpId', str) 7 | DeptId = typing.NewType('DeptId', str) 8 | employee_department_map: dict[EmpId, DeptId] = {} 9 | 10 | 11 | def transfer_employee(empid: EmpId, to_dept: DeptId): 12 | # update department for employee 13 | employee_department_map[to_dept] = empid 14 | 15 | """ 16 | error: Invalid index type "DeptId" for "Dict[EmpId, DeptId]"; expected type "EmpId" 17 | error: Incompatible types in assignment (expression has type "EmpId", target has type "DeptId") 18 | """ 19 | 20 | 21 | ZipCode = typing.NewType("ZipCode", str) 22 | 23 | -------------------------------------------------------------------------------- /05_Type_Annotations/protocols.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | T = typing.TypeVar('T') 4 | 5 | 6 | @typing.runtime_checkable 7 | class SupportsUpdateUndo(typing.Protocol): 8 | def update(self, *args: T) -> None: 9 | ... 10 | 11 | def undo(self) -> None: 12 | ... 13 | 14 | 15 | from generics_and_type_vars_accumulator import Accumulator, acc 16 | 17 | assert issubclass(Accumulator, SupportsUpdateUndo) 18 | assert isinstance(acc, SupportsUpdateUndo) 19 | -------------------------------------------------------------------------------- /05_Type_Annotations/protocols_roman_numeral.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | class RomanNumeral: 4 | """Class for representing Roman numerals and their integer values""" 5 | int_values = {'I': 1, 'II': 2, 'III': 3, 'IV': 4, 'V': 5} 6 | 7 | def __init__(self, label: str): 8 | self.label = label 9 | 10 | def __int__(self) -> int: 11 | return RomanNumeral.int_values[self.label] 12 | 13 | 14 | movie_sequel = RomanNumeral('II') 15 | print(int(movie_sequel)) 16 | assert issubclass(RomanNumeral, typing.SupportsInt) 17 | assert isinstance(movie_sequel, typing.SupportsInt) 18 | -------------------------------------------------------------------------------- /05_Type_Annotations/type_annotations_and_the_typing_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | identifier: type_specification 3 | 4 | type_specifier[type_parameter, ...] 5 | """ 6 | import typing 7 | 8 | # an int 9 | count: int 10 | 11 | # a list of ints, with a default value 12 | counts: list[int] = [] 13 | 14 | # a dict with str keys, and values which are tuples containing 2 ints and a str 15 | employee_data: dict[str, tuple[int, int, str]] 16 | 17 | # a callable taking a single str or bytes argument and returning a bool 18 | str_predicate_function: typing.Callable[[str | bytes], bool] 19 | 20 | # a dict with str keys, whose values are functions that take and return an int 21 | str_function_map: dict[str, typing.Callable[[int], int]] = { 22 | 'square': lambda x: x * x, 23 | 'cube': lambda x: x * x * x, 24 | } 25 | 26 | 27 | """ 28 | def identifier(argument, ...) -> type_specification : 29 | 30 | identifier: type_specification = default_value 31 | """ 32 | 33 | def pad(a: list[str], minimum_len: int = 1, padstr: str = ' ') -> list[str]: 34 | """ 35 | Given a list of strings and a minimum length, return a copy of the list 36 | extended with empty strings to be at least the minimum length. 37 | """ 38 | return a + ([padstr] * (minimum_len - len(a))) 39 | 40 | print(pad(["A", "B"], minimum_len=4)) -------------------------------------------------------------------------------- /05_Type_Annotations/typealias.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | Identifier = int 4 | 5 | # for the purposes of type checking, use TypeAlias: 6 | Identifier: typing.TypeAlias = int 7 | 8 | # Python will treat this like a standard str assignment 9 | TBDType = 'ClassNotDefinedYet' 10 | 11 | # indicates that this is actually a forward reference to a class 12 | TBDType: typing.TypeAlias = 'ClassNotDefinedYet' 13 | -------------------------------------------------------------------------------- /05_Type_Annotations/typeddict.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | class HouseListingDict(typing.TypedDict): 5 | address: str 6 | list_price: int 7 | square_footage: int 8 | condition: str 9 | 10 | 11 | 12 | listing1 = HouseListingDict( 13 | address='123 Main', 14 | list_price=100_000, 15 | square_footage=2400, 16 | condition='Good', 17 | ) 18 | 19 | print(listing1['address']) # prints 123 Main 20 | print(type(listing1)) # prints 21 | 22 | # statements that generate mypy errors 23 | listing2 = HouseListingDict( 24 | address='124 Main', 25 | list_price=110_000, 26 | ) 27 | -------------------------------------------------------------------------------- /05_Type_Annotations/typeddict_generic.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | T = typing.TypeVar('T') 5 | 6 | class Node(typing.TypedDict, typing.Generic[T]): 7 | label: T 8 | neighbors: list[T] 9 | 10 | 11 | n = Node(label='Acme', neighbors=['anvil', 'magnet', 'bird seed']) 12 | 13 | 14 | # statements that generate mypy errors 15 | n['label'] = 100 16 | n['neighbors'].append(100) 17 | -------------------------------------------------------------------------------- /05_Type_Annotations/typeddict_required.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | class HouseListing(typing.TypedDict): 5 | address: typing.Required[str] 6 | list_price: int 7 | square_footage: typing.NotRequired[int] 8 | condition: str 9 | 10 | -------------------------------------------------------------------------------- /05_Type_Annotations/typing_syntax_changes_in_python_39_and_310.py: -------------------------------------------------------------------------------- 1 | # ||--3.10|| prior to 3.10, specifying alternative types requires 2 | # use of the Union type 3 | from typing import Callable, Union 4 | str_predicate_function: Callable[Union[str, bytes], bool] 5 | 6 | # ||--3.9|| prior to 3.9, builtins such as list, tuple, dict, set, 7 | # etc. required types imported from the typing module 8 | from typing import Dict, Tuple, Callable, Union 9 | employee_data: Dict[str, Tuple[int, int, str]] 10 | str_predicate_function: Callable[Union[str, bytes], bool] -------------------------------------------------------------------------------- /05_Type_Annotations/using_type_annotations_at_runtime.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> def f(a:list[str], b) -> int: 3 | ... pass 4 | ... 5 | >>> f.__annotations__ 6 | {'a': list[str], 'return': } 7 | >>> class Customer: 8 | ... name: str 9 | ... reward_points: int = 0 10 | ... 11 | >>> Customer.__annotations__ 12 | {'name': , 'reward_points': } 13 | """ 14 | 15 | if __name__ == '__main__': 16 | # use doctest to simulate console sessions 17 | import doctest 18 | doctest.testmod(verbose=True, exclude_empty=True) 19 | -------------------------------------------------------------------------------- /06_Exceptions/cross_product.py: -------------------------------------------------------------------------------- 1 | def cross_product(seq1, seq2): 2 | if not seq1 or not seq2: 3 | raise ValueError('Sequence arguments must be non-empty') 4 | return [(x1, x2) for x1 in seq1 for x2 in seq2] 5 | 6 | print(cross_product([1, 2, 3], [4, 5, 6])) 7 | 8 | # no need to check for this error, Python will raise TypeError 9 | print(cross_product(1, [4, 5, 6])) 10 | 11 | # cross_product will raise ValueError 12 | print(cross_product([1, 2, 3], [])) 13 | -------------------------------------------------------------------------------- /06_Exceptions/custom_exception_class.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | class InvalidAttribute(AttributeError): 4 | """Used to indicate attributes that could never be valid""" 5 | 6 | class SomeFunkyClass: 7 | """much hypothetical functionality snipped""" 8 | def __getattr__(self, name): 9 | """only clarifies the kind of attribute error""" 10 | if name.startswith('_'): 11 | raise InvalidAttribute(f'Unknown private attribute {name!r}') 12 | else: 13 | raise AttributeError(f'Unknown attribute {name!r}') 14 | 15 | s = SomeFunkyClass() 16 | 17 | for thename in ('_abc', 'def'): 18 | try: 19 | value = getattr(s, thename) 20 | except InvalidAttribute as err: 21 | warnings.warn(str(err), stacklevel=2) 22 | value = None 23 | -------------------------------------------------------------------------------- /06_Exceptions/enclosing_tag_context_manager_class.py: -------------------------------------------------------------------------------- 1 | # The 'enclosing_tag' class as context manager is equivalent to this code: 2 | # 3 | # _normal_exit = True 4 | # _manager = expression 5 | # varname = _manager.__enter__() 6 | # try: 7 | # statement(s) 8 | # except: 9 | # _normal_exit = False 10 | # if not _manager.__exit__(*sys.exc_info()): 11 | # raise 12 | # # note that exception does not propagate if __exit__ returns a true value 13 | # finally: 14 | # if _normal_exit: 15 | # _manager.__exit__(None, None, None) 16 | 17 | class enclosing_tag: 18 | def __init__(self, tagname): 19 | self.tagname = tagname 20 | def __enter__(self): 21 | print(f'<{self.tagname}>', end='') 22 | def __exit__(self, etyp, einst, etb): 23 | print(f'') 24 | 25 | with enclosing_tag('sometag'): 26 | print('some output') 27 | -------------------------------------------------------------------------------- /06_Exceptions/enclosing_tag_context_manager_using_contextlib_contextmanager.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | 3 | @contextlib.contextmanager 4 | def tag(tagname): 5 | print(f'<{tagname}>', end='') 6 | try: 7 | yield 8 | finally: 9 | print(f'') 10 | 11 | tt = tag('sometag') 12 | with tt: 13 | print('some output') -------------------------------------------------------------------------------- /06_Exceptions/exception_add_note.py: -------------------------------------------------------------------------------- 1 | try: 2 | try: 3 | # intentional div-by-zero (for demonstration) 4 | 1 / 0 5 | 6 | # unintentional TypeError (comment out previous line to see change in exception handling) 7 | 1 / "0" 8 | 9 | except ZeroDivisionError as zde: 10 | zde.add_note("This was intentional") 11 | raise 12 | except Exception as e: 13 | e.add_note("This wasn't intentional") 14 | raise 15 | 16 | except Exception as e: 17 | # flag unintentional exceptions for attention 18 | if "This was intentional" not in getattr(e, "__notes__", []): 19 | e.add_note("Needs attention!!!") 20 | raise 21 | -------------------------------------------------------------------------------- /06_Exceptions/exception_group.py: -------------------------------------------------------------------------------- 1 | class GrammarError(Exception): 2 | def __init__(self, found, suggestion): 3 | self.found = found 4 | self.suggestion = suggestion 5 | 6 | 7 | class InvalidWordError(GrammarError): 8 | """Misused or non-existent word""" 9 | 10 | 11 | class MisspelledWordError(GrammarError): 12 | """Spelling error""" 13 | 14 | 15 | invalid_words = { 16 | 'irregardless': 'regardless', 17 | "ain't": "isn't", 18 | } 19 | misspelled_words = { 20 | 'tacco': 'taco', 21 | } 22 | 23 | 24 | def check_grammar(s): 25 | exceptions = [] 26 | for word in s.lower().split(): 27 | if (suggestion := invalid_words.get(word)) is not None: 28 | exceptions.append(InvalidWordError(word, suggestion)) 29 | elif (suggestion := misspelled_words.get(word)) is not None: 30 | exceptions.append(MisspelledWordError(word, suggestion)) 31 | if exceptions: 32 | raise ExceptionGroup('Found grammar errors', exceptions) 33 | 34 | 35 | text = "Irregardless a hot dog ain't a tacco" 36 | try: 37 | check_grammar(text) 38 | except* InvalidWordError as iwe: 39 | print('\n'.join(f'{e.found!r} is not a word, use {e.suggestion!r}' 40 | for e in iwe.exceptions)) 41 | except* MisspelledWordError as mwe: 42 | print('\n'.join(f'Found {e.found!r}, perhaps you meant {e.suggestion!r}?' 43 | for e in mwe.exceptions)) 44 | else: 45 | print('No errors!') 46 | -------------------------------------------------------------------------------- /06_Exceptions/exception_propagation_at_work.py: -------------------------------------------------------------------------------- 1 | def f(): 2 | print('in f, before 1/0') 3 | 1/0 # raises a ZeroDivisionError exception 4 | print('in f, after 1/0') 5 | 6 | def g(): 7 | print('in g, before f()') 8 | f() 9 | print('in g, after f()') 10 | 11 | def h(): 12 | print('in h, before g()') 13 | try: 14 | g() 15 | print('in h, after g()') 16 | except ZeroDivisionError: 17 | print('ZD exception caught') 18 | print('function h ends') 19 | 20 | # Calling the h function prints the following: 21 | # in h, before g() 22 | # in g, before f() 23 | # in f, before 1/0 24 | # ZD exception caught 25 | # function h ends 26 | h() 27 | -------------------------------------------------------------------------------- /06_Exceptions/exception_wrapping_other_exception.py: -------------------------------------------------------------------------------- 1 | try: 2 | 1/0 3 | except ZeroDivisionError: 4 | 1+'x' 5 | -------------------------------------------------------------------------------- /06_Exceptions/exception_wrapping_raise_from_none.py: -------------------------------------------------------------------------------- 1 | class FileSystemDirectory: 2 | def __init__(self): 3 | self._files = {} 4 | 5 | def write_file(self, filename, contents): 6 | self._files[filename] = contents 7 | 8 | def read_file(self, filename): 9 | try: 10 | return self._files[filename] 11 | except KeyError: 12 | raise FileNotFoundError(filename) from None 13 | 14 | 15 | fs = FileSystemDirectory() 16 | 17 | fs.write_file("romeo.txt", 18 | "What's in a name? That which we call a rose\n" 19 | "By any other name would smell as sweet") 20 | print(fs.read_file("romeo.txt")) 21 | print(fs.read_file("data.txt")) 22 | -------------------------------------------------------------------------------- /06_Exceptions/logging_example.py: -------------------------------------------------------------------------------- 1 | # common setup steps 2 | import logging 3 | logging.basicConfig( 4 | format='%(asctime)s %(levelname)8s %(message)s', 5 | filename='/tmp/logfile.txt', filemode='w') 6 | 7 | 8 | logging.getLogger().setLevel(logging.DEBUG) 9 | logging.getLogger().setLevel(logging.ERROR) 10 | 11 | 12 | def cpu_intensive_function(): 13 | return 2**100 14 | 15 | if logging.getLogger().isEnabledFor(logging.DEBUG): 16 | foo = cpu_intensive_function() 17 | logging.debug('foo is %r', foo) 18 | -------------------------------------------------------------------------------- /06_Exceptions/read_or_default.py: -------------------------------------------------------------------------------- 1 | def read_or_default(filepath, default): 2 | try: 3 | with open(filepath) as f: 4 | return f.read() 5 | except FileNotFoundError: 6 | return default 7 | 8 | # read and output contents of this source file 9 | for filename in (__file__, __file__ + 'y'): 10 | print(filename) 11 | print('-' * 40) 12 | print(read_or_default(filename, "")) 13 | print('-' * 40) 14 | -------------------------------------------------------------------------------- /06_Exceptions/safe_divide.py: -------------------------------------------------------------------------------- 1 | def safe_divide_1(x, y): 2 | if y==0: 3 | print('Divide-by-0 attempt detected') 4 | return None 5 | else: 6 | return x/y 7 | 8 | 9 | def safe_divide_2(x, y): 10 | try: 11 | return x/y 12 | except ZeroDivisionError: 13 | print('Divide-by-0 attempt detected') 14 | return None 15 | 16 | 17 | for divisor in (10, 0): 18 | print(f'safe_divide_1(1, {divisor}) = {safe_divide_1(1, divisor)}') 19 | print(f'safe_divide_2(1, {divisor}) = {safe_divide_2(1, divisor)}') 20 | -------------------------------------------------------------------------------- /06_Exceptions/the_try_statement.py: -------------------------------------------------------------------------------- 1 | try: 2 | 1/0 3 | print('not executed') 4 | except ZeroDivisionError: 5 | print('caught divide-by-0 attempt') 6 | 7 | 8 | try: 9 | try: 10 | 1/0 11 | except: 12 | print('caught an exception') 13 | except ZeroDivisionError: 14 | print('caught divide-by-0 attempt') 15 | 16 | 17 | value = 'ABC' 18 | print(repr(value), 'is ', end=' ') 19 | try: 20 | value + 0 21 | except TypeError: 22 | # not a number, maybe a string...? 23 | try: 24 | value + '' 25 | except TypeError: 26 | print('neither a number nor a string') 27 | else: 28 | print('some kind of string') 29 | else: 30 | print('some kind of number') 31 | -------------------------------------------------------------------------------- /06_Exceptions/try_calling.py: -------------------------------------------------------------------------------- 1 | # Accidentally hides any error case where AttributeError is raised inside 2 | # the sought-after method, silently returning default in those cases. 3 | # This may easily hide bugs in other code. 4 | def trycalling(obj, attrib, default, *args, **kwds): 5 | try: 6 | return getattr(obj, attrib)(*args, **kwds) 7 | except AttributeError: 8 | return default 9 | 10 | # This implementation of trycalling separates the getattr call, placed 11 | # in the try clause and therefore guarded by the handler in the except 12 | # clause, from the call of the method, placed in the else clause and 13 | # therefore free to propagate any exception. 14 | def trycalling(obj, attrib, default, *args, **kwds): 15 | try: 16 | method = getattr(obj, attrib) 17 | except AttributeError: 18 | return default 19 | else: 20 | return method(*args, **kwds) 21 | -------------------------------------------------------------------------------- /06_Exceptions/try_finally.py: -------------------------------------------------------------------------------- 1 | # using try/finally 2 | f = open(some_file, 'w') 3 | try: 4 | do_something_with_file(f) 5 | finally: 6 | f.close() 7 | 8 | 9 | # using 'with' statement, with file object as a context manager 10 | # (this is the preferred form for new Python code) 11 | with open(some_file, 'w') as f: 12 | do_something_with_file(f) 13 | -------------------------------------------------------------------------------- /07_Modules/attributes_of_module_objects.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import mymodule 4 | a = mymodule.f() 5 | 6 | 7 | import mymodule as alias 8 | a = alias.f() 9 | 10 | 11 | # get dynamically generated attribute 12 | print(f'10,000th prime is {mymodule.first_10000_primes[-1]}') 13 | 14 | def find_prime_triples(): 15 | for a, b, c in zip(mymodule.first_10000_primes, 16 | mymodule.first_10000_primes[1:], 17 | mymodule.first_10000_primes[2:]): 18 | if c-a == 6: 19 | yield a, b, c 20 | 21 | for x in find_prime_triples(): 22 | print(x) 23 | 24 | print(dir(mymodule)) 25 | -------------------------------------------------------------------------------- /07_Modules/custom_importers_import_hooks.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class ImporterAndLoader: 4 | '''importer and loader can be a single class''' 5 | fake_path = '!dummy!' 6 | def __init__(self, path): 7 | # only handle our own fake-path marker 8 | if path != self.fake_path: 9 | raise ImportError 10 | def find_module(self, fullname): 11 | # don't even try to handle any qualified module name 12 | if '.' in fullname: 13 | return None 14 | return self 15 | def create_module(self, spec): 16 | # create module "the default way" 17 | return None 18 | def exec_module(self, mod): 19 | # populate the already-initialized module 20 | # just print a message in this toy example 21 | print(f'NOTE: module {mod!r} not yet written') 22 | 23 | sys.path_hooks.append(ImporterAndLoader) 24 | sys.path.append(ImporterAndLoader.fake_path) 25 | 26 | if __name__ == '__main__': # self-test when run as main script 27 | import missing_module # importing a simple *missing* module 28 | print(missing_module) # ...should succeed 29 | print(sys.modules.get('missing_module')) # ...should also succeed -------------------------------------------------------------------------------- /07_Modules/mymodule.py: -------------------------------------------------------------------------------- 1 | # sample module, to be used to illustrate importing a module, and module-level __getattr__ 2 | from collections.abc import MutableSequence 3 | 4 | def f(): 5 | return None 6 | 7 | 8 | def __getattr__(name): 9 | if name == 'first_10000_primes': 10 | import sys 11 | print(f"initializing {name}") 12 | # get current module object by looking up __name__ in sys.modules 13 | this_module = sys.modules[__name__] 14 | 15 | def generate_n_primes(n): 16 | found = [2] 17 | i = 3 18 | while len(found) < n: 19 | if not any(i % x == 0 for x in found): 20 | found.append(i) 21 | i += 2 22 | return found 23 | 24 | this_module.first_10000_primes = generate_n_primes(10000) 25 | return this_module.first_10000_primes 26 | 27 | raise AttributeError(f'no such attribute {name!r}') 28 | -------------------------------------------------------------------------------- /07_Modules/python_builtins.py: -------------------------------------------------------------------------------- 1 | # abs takes a numeric argument; let's make it accept a string as well 2 | import builtins 3 | _abs = builtins.abs # save original built-in 4 | 5 | def abs(str_or_num): 6 | if isinstance(str_or_num, str): # if arg is a string 7 | return ''.join(sorted(set(str_or_num))) # get this instead 8 | return _abs(str_or_num) # call real built-in 9 | 10 | builtins.abs = abs # override built-in w/wrapper 11 | 12 | 13 | # conventional abs 14 | print(abs(-100)) 15 | 16 | # special handling of abs(str) 17 | print(abs("Bubble, bubble, toil and trouble")) 18 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/argparse_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | ap = argparse.ArgumentParser(description='Just an example') 3 | ap.add_argument('who', nargs='?', default='World') 4 | ap.add_argument('--formal', action='store_true') 5 | ns = ap.parse_args() 6 | if ns.formal: 7 | greet = 'Most felicitous salutations, o {}.' 8 | else: 9 | greet = 'Hello, {}!' 10 | print(greet.format(ns.who)) 11 | 12 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/collections_chain_map_equivalent.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | 4 | class ChainMap(collections.abc.MutableMapping): 5 | def __init__(self, *maps): 6 | self.maps = list(maps) 7 | self._keys = set() 8 | for m in self.maps: 9 | self._keys.update(m) 10 | def __len__(self): return len(self._keys) 11 | def __iter__(self): return iter(self._keys) 12 | def __getitem__(self, key): 13 | if key not in self._keys: raise KeyError(key) 14 | for m in self.maps: 15 | try: return m[key] 16 | except KeyError: pass 17 | def __setitem__(self, key, value): 18 | self.maps[0][key] = value 19 | self._keys.add(key) 20 | def __delitem__(self, key): 21 | del self.maps[0][key] 22 | self._keys = set() 23 | for m in self.maps: 24 | self._keys.update(m) 25 | 26 | 27 | # uncomment this line to compare the above class with the actual collections.ChainMap 28 | # from collections import ChainMap 29 | 30 | args = {'a': 100, 'b': 101} 31 | defaults = {'a': 1, 'b': 2, 'c': 3} 32 | 33 | cm = ChainMap(args, defaults) 34 | print(list(cm)) 35 | print(list(cm.items())) 36 | 37 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/collections_defaultdict_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent behavior to collections.defaultdict 2 | class defaultdict(dict): 3 | def __init__(self, default_factory=None, *a, **k): 4 | super().__init__(*a, **k) 5 | self.default_factory = default_factory 6 | def __getitem__(self, key): 7 | if key not in self and self.default_factory is not None: 8 | self[key] = self.default_factory() 9 | return dict.__getitem__(self, key) 10 | 11 | 12 | # uncomment this line to compare with actual collections.defaultdict 13 | from collections import defaultdict 14 | 15 | dd = defaultdict(lambda: None, a=10, b=20) 16 | print(dd['a']) 17 | print(dd['c']) 18 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/decorate_sort_undecorate.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | 3 | 4 | class KeyHeap: 5 | def __init__(self, alist, key): 6 | self.heap = [(key(o), i, o) for i, o in enumerate(alist)] 7 | heapq.heapify(self.heap) 8 | self.key = key 9 | if alist: 10 | self.nexti = self.heap[-1][1] + 1 11 | else: 12 | self.nexti = 0 13 | 14 | def __len__(self): 15 | return len(self.heap) 16 | 17 | def push(self, o): 18 | heapq.heappush(self.heap, (self.key(o), self.nexti, o)) 19 | self.nexti += 1 20 | 21 | def pop(self): 22 | return heapq.heappop(self.heap)[-1] 23 | 24 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/exec_with_data.py: -------------------------------------------------------------------------------- 1 | def exec_with_data(user_code_string): 2 | user_code = compile(user_code_string, '', 'exec') 3 | datadict = {} 4 | for name in user_code.co_names: 5 | if name.startswith('data_'): 6 | with open(f'data/{name[5:]}', 'rb') as datafile: 7 | datadict[name] = datafile.read() 8 | elif name.startswith('result_'): 9 | pass # user code can assign to variables named `result_…` 10 | else: 11 | raise ValueError(f'invalid variable name {name!r}') 12 | exec(user_code, datadict) 13 | for name in datadict: 14 | if name.startswith('result_'): 15 | with open('data/{}'.format(name[7:]), 'wb') as datafile: 16 | datafile.write(datadict[name]) 17 | 18 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/functools_reduce_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent of functools.reduce 2 | def reduce_equiv(func,seq,init=None): 3 | seq = iter(seq) 4 | if init is None: 5 | init = next(seq) 6 | for item in seq: 7 | init = func(init,item) 8 | return init 9 | 10 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/greet.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | ap = argparse.ArgumentParser(description='Just an example') 4 | ap.add_argument('who', nargs='?', default='World') 5 | ap.add_argument('--formal', action='store_true') 6 | 7 | ns = ap.parse_args() 8 | if ns.formal: 9 | greet = 'Most felicitous salutations, o {}.' 10 | else: 11 | greet = 'Hello, {}!' 12 | 13 | print(greet.format(ns.who)) 14 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/iter_examples.py: -------------------------------------------------------------------------------- 1 | 2 | # equivalent of iter(sequence) when sequence does not implement __iter__ 3 | def iter_sequence(obj): 4 | i = 0 5 | while True: 6 | try: 7 | yield obj[i] 8 | except IndexError: 9 | raise StopIteration 10 | i += 1 11 | 12 | 13 | # equivalent of iter(func, sentinel) 14 | def iter_sentinel(func, sentinel): 15 | while True: 16 | item = func() 17 | if item == sentinel: 18 | raise StopIteration 19 | yield item 20 | 21 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/itertools_count_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent of itertools.count 2 | def count(start=0, step=1): 3 | while True: 4 | yield start 5 | start += step 6 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/itertools_cycle_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent of itertools.cycle 2 | def cycle(iterable): 3 | saved = [] 4 | for item in iterable: 5 | yield item 6 | saved.append(item) 7 | while saved: 8 | for item in saved: 9 | yield item 10 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/itertools_dropwhile_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent of itertools.dropwhile 2 | def dropwhile(func, iterable): 3 | iterator = iter(iterable) 4 | for item in iterator: 5 | if not func(item): 6 | yield item 7 | break 8 | for item in iterator: 9 | yield item 10 | 11 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/itertools_groupby_example.py: -------------------------------------------------------------------------------- 1 | import itertools as it 2 | import operator 3 | def set2dict(aset): 4 | first = operator.itemgetter(0) 5 | words = sorted(aset, key=first) 6 | adict = {} 7 | for initial, group in it.groupby(words, key=first): 8 | adict[initial] = max(group, key=len) 9 | return adict 10 | 11 | 12 | word_set = set("tomorrow and tomorrow and tomorrow creeps in this petty pace from day to day".split()) 13 | print(set2dict(word_set)) 14 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/itertools_islice_equivalent.py: -------------------------------------------------------------------------------- 1 | # equivalent of itertools.islice 2 | def islice(iterable, start, stop, step=1): 3 | en = enumerate(iterable) 4 | n = stop 5 | for n, item in en: 6 | if n>=start: 7 | break 8 | while n', 'eval') 3 | if code.co_names: 4 | raise ValueError(f'Names {code.co_names!r} not allowed in expression {s!r}') 5 | return eval(code) 6 | 7 | x = safer_eval("10+35") 8 | y = safer_eval("min(1,2,3)") 9 | -------------------------------------------------------------------------------- /08_Core_Builtins_and_Standard_Library_Modules/sys_ps1_ps2.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | class Ps1: 5 | def __init__(self): 6 | self.p = 0 7 | def __str__(self): 8 | self.p += 1 9 | return f'[{self.p}]>>> ' 10 | 11 | class Ps2: 12 | def __str__(self): 13 | return f'[{sys.ps1.p}]... ' 14 | 15 | 16 | sys.ps1 = Ps1() 17 | sys.ps2 = Ps2() 18 | -------------------------------------------------------------------------------- /09_Strings_and_Things/digit_grouping.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> '{:,}'.format(12345678) 3 | '12,345,678' 4 | 5 | """ 6 | 7 | if __name__ == '__main__': 8 | # use doctest to simulate console sessions 9 | import doctest 10 | doctest.testmod(verbose=True, exclude_empty=True) 11 | -------------------------------------------------------------------------------- /09_Strings_and_Things/field_width.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> s = 'a string' 3 | >>> '{:^12s}'.format(s) 4 | ' a string ' 5 | >>> '{:.>12s}'.format(s) 6 | '....a string' 7 | 8 | >>> '{:.>{}s}'.format(s, 20) 9 | '............a string' 10 | 11 | """ 12 | 13 | if __name__ == '__main__': 14 | # use doctest to simulate console sessions 15 | import doctest 16 | doctest.testmod(verbose=True, exclude_empty=True) 17 | -------------------------------------------------------------------------------- /09_Strings_and_Things/format_type.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> n = [3.1415, -42, 1024.0] 3 | >>> for num in n: 4 | ... '{:>+9,.2f}'.format(num) 5 | ... 6 | ' +3.14' 7 | ' -42.00' 8 | '+1,024.00' 9 | """ 10 | 11 | if __name__ == '__main__': 12 | # use doctest to simulate console sessions 13 | import doctest 14 | doctest.testmod(verbose=True, exclude_empty=True) 15 | -------------------------------------------------------------------------------- /09_Strings_and_Things/formatted_string_literals.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> name = 'Dawn' 3 | >>> print('{name!r} is {l} characters long' 4 | ... .format(name=name, l=len(name))) 5 | 'Dawn' is 4 characters long 6 | 7 | >>> print(f'{name!r} is {len(name)} characters long') 8 | 'Dawn' is 4 characters long 9 | 10 | >>> for width in 8, 11: 11 | ... for precision in 2, 3, 4, 5: 12 | ... print(f'{3.14159:{width}.{precision}}') 13 | ... 14 | 3.1 15 | 3.14 16 | 3.142 17 | 3.1416 18 | 3.1 19 | 3.14 20 | 3.142 21 | 3.1416 22 | 23 | >>> a = '*-' 24 | >>> s = 12 25 | >>> f'{a*s=}' 26 | "a*s='*-*-*-*-*-*-*-*-*-*-*-*-'" 27 | >>> f'{a*s = :30}' 28 | 'a*s = *-*-*-*-*-*-*-*-*-*-*-*- ' 29 | 30 | """ 31 | 32 | if __name__ == '__main__': 33 | # use doctest to simulate console sessions 34 | import doctest 35 | doctest.testmod(verbose=True, exclude_empty=True) 36 | -------------------------------------------------------------------------------- /09_Strings_and_Things/formatting_of_user_coded_classes.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> object().__format__('') 3 | '' 4 | >>> import math 5 | >>> math.pi.__format__('18.6') 6 | ' 3.14159' 7 | 8 | >>> class S: 9 | ... def __init__(self, value): 10 | ... self.value = value 11 | ... def __format__(self, fstr): 12 | ... match fstr: 13 | ... case "U": 14 | ... return self.value.upper() 15 | ... case 'L': 16 | ... return self.value.lower() 17 | ... case 'T': 18 | ... return self.value.title() 19 | ... case _: 20 | ... return ValueError(f'Unrecognised format code {fstr!r}') 21 | ... 22 | >>> my_s = S('random string') 23 | >>> f'{my_s:L}, {my_s:U}, {my_s:T}' 24 | 'random string, RANDOM STRING, Random String' 25 | 26 | >>> import datetime 27 | >>> d = datetime.datetime.now() 28 | >>> d.__format__("%d/%m/%y") 29 | '10/04/22' 30 | >>> "{:%d/%m/%y}".format(d) 31 | '10/04/22' 32 | >>> f"{d:%d/%m/%y}" # See “Formatted String Literals” 33 | '10/04/22' 34 | 35 | """ 36 | 37 | if __name__ == '__main__': 38 | # use doctest to simulate console sessions 39 | import doctest 40 | doctest.testmod(verbose=True, exclude_empty=True) 41 | -------------------------------------------------------------------------------- /09_Strings_and_Things/legacy_string_formatting_with_percent.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.getLogger().setLevel(logging.INFO) 4 | x = 42 5 | y = 3.14 6 | z = 'george' 7 | 8 | logging.info('result = %d', x) # logs: result = 42 9 | logging.info('answers: %d %f', x, y) # logs: answers: 42 3.140000 10 | logging.info('hello %s', z) # logs: hello george 11 | -------------------------------------------------------------------------------- /09_Strings_and_Things/nested_format_specifications.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> s = 'a string' 3 | >>> '{0:>{1}s}'.format(s, len(s)+4) 4 | ' a string' 5 | >>> '{0:_^{1}s}'.format(s, len(s)+4) 6 | '__a string__' 7 | 8 | >>> def columnar_strings(str_seq, widths): 9 | ... for cols in str_seq: 10 | ... row = ['{c:{w}.{w}s}'.format(c=c, w=w) 11 | ... for c, w in zip(cols, widths)] 12 | ... print(' '.join(row)) 13 | 14 | >>> c = [ 15 | ... 'four score and'.split(), 16 | ... 'seven years ago'.split(), 17 | ... 'our forefathers brought'.split(), 18 | ... 'forth on this'.split(), 19 | ... ] 20 | 21 | >>> columnar_strings(c, (8, 8, 8)) 22 | four score and 23 | seven years ago 24 | our forefath brought 25 | forth on this 26 | """ 27 | 28 | if __name__ == '__main__': 29 | # use doctest to simulate console sessions 30 | import doctest 31 | doctest.testmod(verbose=True, exclude_empty=True) 32 | -------------------------------------------------------------------------------- /09_Strings_and_Things/precision_specification.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> s = 'a string' 3 | >>> x = 1.12345 4 | >>> 'as f: {:.4f}'.format(x) 5 | 'as f: 1.1235' 6 | >>> 'as g: {:.4g}'.format(x) 7 | 'as g: 1.123' 8 | >>> 'as s: {:.6s}'.format(s) 9 | 'as s: a stri' 10 | """ 11 | 12 | if __name__ == '__main__': 13 | # use doctest to simulate console sessions 14 | import doctest 15 | doctest.testmod(verbose=True, exclude_empty=True) 16 | -------------------------------------------------------------------------------- /09_Strings_and_Things/str_split.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> x = 'a b' # two spaces between a and b 3 | >>> x.split() # or x.split(None) 4 | ['a', 'b'] 5 | >>> x.split(' ') 6 | ['a', '', 'b'] 7 | """ 8 | 9 | if __name__ == '__main__': 10 | # use doctest to simulate console sessions 11 | import doctest 12 | doctest.testmod(verbose=True, exclude_empty=True) 13 | -------------------------------------------------------------------------------- /09_Strings_and_Things/string_formatting.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> n = 10; s = 'zero', 'one', 'two', 'three'; i=2 3 | >>> f'start {"-"*n} : {s[i]} end' 4 | 'start ---------- : two end' 5 | 6 | >>> "This is a {0}, {1}, type of {type}".format("large", "green", type="vase") 7 | 'This is a large, green, type of vase' 8 | """ 9 | 10 | if __name__ == '__main__': 11 | # use doctest to simulate console sessions 12 | import doctest 13 | doctest.testmod(verbose=True, exclude_empty=True) 14 | -------------------------------------------------------------------------------- /09_Strings_and_Things/unicodedata_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import unicodedata 3 | >>> unicodedata.name('⚀') 4 | 'DIE FACE-1' 5 | >>> unicodedata.name('Ⅵ') 6 | 'ROMAN NUMERAL SIX' 7 | >>> int('Ⅵ') 8 | Traceback (most recent call last): 9 | ... 10 | ValueError: invalid literal for int() with base 10: 'Ⅵ' 11 | >>> unicodedata.numeric('Ⅵ') # use unicodedata to get the numeric value 12 | 6.0 13 | >>> unicodedata.lookup('RECYCLING SYMBOL FOR TYPE-1 PLASTICS') 14 | '♳' 15 | 16 | """ 17 | 18 | if __name__ == '__main__': 19 | # use doctest to simulate console sessions 20 | import doctest 21 | doctest.testmod(verbose=True, exclude_empty=True) 22 | -------------------------------------------------------------------------------- /09_Strings_and_Things/values_by_argument_lookup.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> 'First: {} second: {}'.format(1, 'two') 3 | 'First: 1 second: two' 4 | 5 | >>> 'Second: {1}, first: {0}'.format(42, 'two') 6 | 'Second: two, first: 42' 7 | 8 | >>> 'a: {a}, 1st: {}, 2nd: {}, a again: {a}'.format(1, 'two', a=3) 9 | 'a: 3, 1st: 1, 2nd: two, a again: 3' 10 | >>> 'a: {a} first:{0} second: {1} first: {0}'.format(1, 'two', a=3) 11 | 'a: 3 first:1 second: two first: 1' 12 | 13 | >>> 'p0[1]: {[1]} p1[0]: {[0]}'.format(('zero', 'one'), ('two', 'three')) 14 | 'p0[1]: one p1[0]: two' 15 | >>> 'p1[0]: {1[0]} p0[1]: {0[1]}'.format(('zero', 'one'), ('two', 'three')) 16 | 'p1[0]: two p0[1]: one' 17 | >>> '{} {} {a[2]}'.format(1, 2, a=(5, 4, 3)) 18 | '1 2 3' 19 | 20 | >>> 'First r: {.real} Second i: {a.imag}'.format(1+2j, a=3+4j) 21 | 'First r: 1.0 Second i: 4.0' 22 | """ 23 | 24 | if __name__ == '__main__': 25 | # use doctest to simulate console sessions 26 | import doctest 27 | doctest.testmod(verbose=True, exclude_empty=True) 28 | -------------------------------------------------------------------------------- /10_Regular_Expressions/afile.txt: -------------------------------------------------------------------------------- 1 | The Life and Death of King Richard III 2 | by William Shakespeare 3 | 4 | ACT I 5 | 6 | Scene 1 7 | 8 | Now is the winter of our discontent 9 | Made glorious summer by this son of York, 10 | And all the clouds that loured upon our house 11 | In the deep bosom of the ocean buried. 12 | 13 | -------------------------------------------------------------------------------- /10_Regular_Expressions/anchoring_at_string_start_and_end.py: -------------------------------------------------------------------------------- 1 | import re 2 | digatend = re.compile(r'\d$', re.MULTILINE) 3 | with open('afile.txt') as f: 4 | if digatend.search(f.read()): 5 | print('some lines end with digits') 6 | else: 7 | print('no line ends with digits') 8 | -------------------------------------------------------------------------------- /10_Regular_Expressions/match_vs_search.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | r1 = re.compile(r'box') 4 | if r1.match('inbox'): 5 | print('match succeeds') 6 | else: 7 | print('match fails') # prints: match fails 8 | 9 | if r1.search('inbox'): 10 | print('search succeeds') # prints: search succeeds 11 | else: 12 | print('search fails') 13 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_and_the_walrus_operator.py: -------------------------------------------------------------------------------- 1 | """ 2 | The introduction of the := operator in Python 3.8 established support for a 3 | successive-match idiom in Python similar to one that’s common in Perl. In 4 | this idiom, a series of if-elif branches tests a string against different 5 | regular expressions. In Perl, the if ($var =~ /regExpr/) statement both 6 | evaluates the regular expression and saves the successful match in the 7 | variable var. 8 | 9 | if ($statement =~ /I love (\w+)/) { 10 | print "He loves $1\n"; 11 | } 12 | elsif ($statement =~ /Ich liebe (\w+)/) { 13 | print "Er liebt $1\n"; 14 | } 15 | elsif ($statement =~ /Je t\'aime (\w+)/) { 16 | print "Il aime $1\n"; 17 | } 18 | 19 | """ 20 | # Prior to Python 3.8, this behavior of evaluate-and-store was not possible 21 | # in a single statement, so developers would have to use a cumbersome cascade 22 | # of nested if-else statements: 23 | import re 24 | statement = "Ich liebe Maria" 25 | 26 | m = re.match("I love (\w+)", statement) 27 | if m: 28 | print(f"He loves {m.group(1)}") 29 | else: 30 | m = re.match("Ich liebe (\w+)", statement) 31 | if m: 32 | print(f"Er liebt {m.group(1)}") 33 | else: 34 | m = re.match("Je t'aime (\w+)", statement) 35 | if m: 36 | print(f"Il aime {m.group(1)}") 37 | 38 | # Using the := operator, this code simplifies to: 39 | if m := re.match(r"I love (\w+)", statement): 40 | print(f"He loves {m.group(1)}") 41 | 42 | elif m := re.match(r"Ich liebe (\w+)", statement): 43 | print(f"Er liebt {m.group(1)}") 44 | 45 | elif m := re.match(r"Je t'aime (\w+)", statement): 46 | print(f"Il aime {m.group(1)}") 47 | 48 | 49 | # Example taken from regex - Match groups in Python - Stack Overflow 50 | # (https://stackoverflow.com/questions/2554185/match-groups-in-python/2555047) 51 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_findall_1.py: -------------------------------------------------------------------------------- 1 | import re 2 | reword = re.compile(r'\w+') 3 | with open('afile.txt') as f: 4 | for aword in reword.findall(f.read()): 5 | print(aword) 6 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_findall_2.py: -------------------------------------------------------------------------------- 1 | import re 2 | first_last = re.compile(r'^\W*(\w+)\b.*\b(\w+)\W*$',re.MULTILINE) 3 | with open('afile.txt') as f: 4 | for first, last in first_last.findall(f.read()): 5 | print(first, last) 6 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_match.py: -------------------------------------------------------------------------------- 1 | import re 2 | digs = re.compile(r'\d') 3 | with open('afile.txt') as f: 4 | for line in f: 5 | if digs.match(line): 6 | print(line, end='') 7 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_optional_flags.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | r1 = re.compile(r'(?i)hello') 5 | r2 = re.compile(r'hello', re.I) 6 | r3 = re.compile(r'hello', re.IGNORECASE) 7 | 8 | 9 | repat_num1 = r'(0o[0-7]*|0x[\da-fA-F]+|[1-9]\d*)\Z' 10 | repat_num2 = r'''(?x) # (re.VERBOSE) pattern matching int literals 11 | ( 0o [0-7]* # octal: leading 0o, 0+ octal digits 12 | | 0x [\da-fA-F]+ # hex: 0x, then 1+ hex digits 13 | | [1-9] \d* # decimal: leading non-0, 0+ digits 14 | )\Z # end of string 15 | ''' 16 | 17 | -------------------------------------------------------------------------------- /10_Regular_Expressions/re_search.py: -------------------------------------------------------------------------------- 1 | import re 2 | digs = re.compile(r'\d') 3 | with open('afile.txt') as f: 4 | for line in f: 5 | if digs.search(line): 6 | print(line, end='') 7 | -------------------------------------------------------------------------------- /10_Regular_Expressions/res_and_bytes_vs_str.py: -------------------------------------------------------------------------------- 1 | import re 2 | print(re.findall(r'\w+', 'cittá'))# prints ['cittá'] 3 | print(re.findall(rb'\w+', 'cittá'.encode())) # prints [b'citt'] 4 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/console_io_msvcrt_module.py: -------------------------------------------------------------------------------- 1 | import msvcrt 2 | print("press z to exit, or any other key " 3 | "to see the key's code:") 4 | while True: 5 | c = msvcrt.getch() 6 | if c == b'z': 7 | break 8 | print(f'{ord(c)} ({c!r})') 9 | 10 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/errno_module.py: -------------------------------------------------------------------------------- 1 | import os 2 | import errno 3 | 4 | try: 5 | os.some_os_function_or_other() 6 | except FileNotFoundError as err: 7 | print('Warning: file', err.filename, 'not found; continuing') 8 | except OSError as oserr: 9 | print(f'Error {errno.errorcode[oserr.errno]}; continuing') 10 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/fileinput_module.py: -------------------------------------------------------------------------------- 1 | import fileinput 2 | for line in fileinput.input(inplace=True): 3 | print(line.replace('foo', 'bar'), end='') 4 | 5 | 6 | with fileinput.input('file1.txt', 'file2.txt') as fin: 7 | dostuff(fin) 8 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/fnmatch_translate.py: -------------------------------------------------------------------------------- 1 | import fnmatch, re 2 | 3 | def fnmatchnocase(filename, pattern): 4 | re_pat = fnmatch.translate(pattern) 5 | return re.match(re_pat, filename, re.IGNORECASE) 6 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/gettext_install.py: -------------------------------------------------------------------------------- 1 | # commands to create pian.mo: 2 | # python /Tools/i18n/pygettext.py gettext_module.py 3 | # mkdir en/LC_MESSAGES 4 | # python /Tools/i18n/msgfmt.py pian.po -o en/LC_MESSAGES/pian.mo 5 | import os, gettext 6 | os.environ.setdefault('LANG', 'en') # application-default language 7 | gettext.install('pian', '.') 8 | 9 | import gettext_module 10 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/gettext_module.py: -------------------------------------------------------------------------------- 1 | from gettext import * 2 | 3 | try: 4 | _ 5 | except NameError: 6 | def _(s): return s 7 | 8 | def greet(): 9 | print(_('Hello world')) 10 | 11 | greet() 12 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/locale_format_string.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import locale 3 | 4 | >>> locale.setlocale(locale.LC_NUMERIC, 'en_us') 5 | 'en_us' 6 | >>> n=1000*1000 7 | >>> locale.format_string('%d', n) 8 | '1000000' 9 | >>> locale.setlocale(locale.LC_MONETARY, 'it_it') 10 | 'it_it' 11 | >>> locale.format_string('%f', n) # uses decimal_point 12 | '1000000.000000' 13 | >>> locale.format_string('%f', n, monetary=True) # uses mon_decimal_point 14 | '1000000,000000' 15 | >>> locale.format_string('%0.2f', n, grouping=True) # separators & decimal from LC_NUMERIC 16 | '1,000,000.00' 17 | >>> locale.format_string('%0.2f', n, grouping=True, monetary=True) # separators & decimal from LC_MONETARY 18 | '1.000.000,00' 19 | """ 20 | 21 | if __name__ == '__main__': 22 | # use doctest to simulate console sessions 23 | import doctest 24 | doctest.testmod(verbose=True, exclude_empty=True) 25 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/locale_strxfrm.py: -------------------------------------------------------------------------------- 1 | import locale 2 | 3 | def locale_sort_inplace(list_of_strings): 4 | list_of_strings.sort(key=locale.strxfrm) 5 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/os_path_expand_vars.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.environ['HOME'] = "/u/alex" 3 | 4 | print(os.path.expandvars('$HOME/foo/')) 5 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/os_path_join.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | print(os.path.join('a/b', 'c/d', 'e/f')) 4 | # on Unix prints: a/b/c/d/e/f 5 | print(os.path.join('a/b', '/c/d', 'e/f')) 6 | # on Unix prints: /c/d/e/f 7 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/os_stat.py: -------------------------------------------------------------------------------- 1 | import os 2 | print(os.path.getsize(path)) 3 | print(os.stat(path)[6]) 4 | print(os.stat(path).st_size) 5 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/pathlib_glob.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import pathlib 3 | 4 | >>> td = pathlib.Path("tempdir") 5 | >>> sorted(td.glob('*')) 6 | [WindowsPath('tempdir/bar'), WindowsPath('tempdir/foo')] 7 | >>> sorted(td.glob('**/*')) 8 | [WindowsPath('tempdir/bar'), WindowsPath('tempdir/bar/baz'), WindowsPath('tempdir/bar/boo'), WindowsPath('tempdir/foo')] 9 | >>> sorted(td.glob('*/**/*')) # expanding at 2nd+ level 10 | [WindowsPath('tempdir/bar/baz'), WindowsPath('tempdir/bar/boo')] 11 | >>> sorted(td.rglob('*')) # just like glob('**/*') 12 | [WindowsPath('tempdir/bar'), WindowsPath('tempdir/bar/baz'), WindowsPath('tempdir/bar/boo'), WindowsPath('tempdir/foo')] 13 | """ 14 | 15 | if __name__ == '__main__': 16 | # use doctest to simulate console sessions 17 | import doctest 18 | doctest.testmod(verbose=True, exclude_empty=True) 19 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/pathlib_mkdir.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import pathlib 3 | >>> 4 | >>> td=pathlib.Path('tempdir/') 5 | >>> td.mkdir() 6 | >>> td.is_dir() 7 | True 8 | >>> td.resolve() 9 | WindowsPath('C:/Users/annar/tempdir') 10 | """ 11 | 12 | if __name__ == '__main__': 13 | # use doctest to simulate console sessions 14 | import doctest 15 | doctest.testmod(verbose=True, exclude_empty=True) 16 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/pathlib_replace.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import pathlib 3 | >>> p = pathlib.Path('p.txt') 4 | >>> t = pathlib.Path('testfile.txt') 5 | 6 | 7 | >>> p.read_text() 8 | 'spam' 9 | >>> t.read_text() 10 | 'and eggs' 11 | >>> p.replace(t) 12 | WindowsPath('testfile.txt') 13 | >>> t.read_text() 14 | 'spam' 15 | >>> p.read_text() 16 | Traceback (most recent call last): 17 | ... 18 | FileNotFoundError: [Errno 2] No such file… 19 | """ 20 | 21 | if __name__ == '__main__': 22 | # use doctest to simulate console sessions 23 | import doctest 24 | doctest.testmod(verbose=True, exclude_empty=True) 25 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/pathlib_touch.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> from pathlib import Path 3 | 4 | >>> d 5 | WindowsPath('C:/Users/annar/Documents') 6 | >>> f = Path(d) / 'testfile.txt' 7 | >>> f.is_file() 8 | False 9 | >>> f.touch() 10 | >>> f.is_file() 11 | True 12 | """ 13 | 14 | if __name__ == '__main__': 15 | # use doctest to simulate console sessions 16 | import doctest 17 | doctest.testmod(verbose=True, exclude_empty=True) 18 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/pian.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR ORGANIZATION 3 | # FIRST AUTHOR , YEAR. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: PACKAGE VERSION\n" 8 | "POT-Creation-Date: 2022-07-19 01:21-0500\n" 9 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 10 | "Last-Translator: FULL NAME \n" 11 | "Language-Team: LANGUAGE \n" 12 | "MIME-Version: 1.0\n" 13 | "Content-Type: text/plain; charset=cp1252\n" 14 | "Content-Transfer-Encoding: 8bit\n" 15 | "Generated-By: pygettext.py 1.5\n" 16 | 17 | 18 | #: .\gettext_module.py:9 19 | msgid "Hello world" 20 | msgstr "Hey World, how are ya?" 21 | 22 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/print_function.py: -------------------------------------------------------------------------------- 1 | def redirect(func: Callable, *a, **k) -> (str, Any): 2 | """redirect(func, *a, **k) -> (func's results, return value) 3 | func is a callable emitting results to standard output. 4 | redirect captures the results as a str and returns a pair 5 | (output string, return value). 6 | """ 7 | import sys, io 8 | save_out = sys.stdout 9 | sys.stdout = io.StringIO() 10 | try: 11 | retval = func(*args, **kwds) 12 | return sys.stdout.getvalue(), retval 13 | finally: 14 | sys.stdout.close() 15 | sys.stdout = save_out 16 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/shutil_copytree.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | 3 | ignore = shutil.ignore_patterns('.*', '*.bak') 4 | shutil.copytree('src', 'dst', ignore=ignore) 5 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/standard_input.py: -------------------------------------------------------------------------------- 1 | import ast 2 | print(ast.literal_eval('23')) # prints 23 3 | print(ast.literal_eval(' 23')) # prints 23 (||3.10++||) 4 | print(ast.literal_eval('[2,-3]')) # prints [2, -3] 5 | print(ast.literal_eval('2+3')) # raises ValueError 6 | print(ast.literal_eval('2+')) # raises SyntaxError 7 | 8 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/tempfile_module.py: -------------------------------------------------------------------------------- 1 | import tempfile, shutil 2 | 3 | path = tempfile.mkdtemp() 4 | try: 5 | use_dirpath(path) 6 | finally: 7 | shutil.rmtree(path) 8 | 9 | 10 | import tempfile, os 11 | fd, path = tempfile.mkstemp(suffix='.txt', text=True) 12 | try: 13 | os.close(fd) 14 | use_filepath(path) 15 | finally: 16 | os.unlink(path) 17 | 18 | 19 | -------------------------------------------------------------------------------- /11_File_and_Text_Operations/zipfile_module.py: -------------------------------------------------------------------------------- 1 | import zipfile 2 | 3 | with zipfile.ZipFile('archive.zip') as z: 4 | data = z.read('data.txt') 5 | 6 | 7 | with zipfile.ZipFile('z.zip', 'w') as zz: 8 | data = 'four score\nand seven\nyears ago\n' 9 | zz.writestr('zinfo.zip', data) 10 | 11 | 12 | import zipfile 13 | with zipfile.ZipFile('z.zip') as zz: 14 | zz.printdir() 15 | for name in zz.namelist(): 16 | print('{}: {!r}'.format(name, zz.read(name))) 17 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/csv_colors.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | color_data = '''\ 4 | color,r,g,b 5 | red,255,0,0 6 | green,0,255,0 7 | blue,0,0,255 8 | cyan,0,255,255 9 | magenta,255,0,255 10 | yellow,255,255,0 11 | '''.splitlines() 12 | 13 | colors = {row['color']: row 14 | for row in csv.DictReader(color_data)} 15 | 16 | print(colors['red']) # prints {'color': 'red', 'r': '255', 'g': '0', 'b': '0'} 17 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/dbm_find.py: -------------------------------------------------------------------------------- 1 | import sys, os, dbm, linecache 2 | 3 | sep = os.pathsep 4 | sep2 = sep * 2 5 | with dbm.open('indexfile') as dbm_in: 6 | for word in sys.argv[1:]: 7 | e_word = word.encode('utf-8') 8 | if e_word not in dbm_in: 9 | print(f'Word {word!r} not found in index file', 10 | file=sys.stderr) 11 | continue 12 | places = dbm_in[e_word].decode('utf-8').split(sep2) 13 | for place in places: 14 | fname, lineno = place.split(sep) 15 | print(f'Word {word!r} occurs in line {lineno} of file {filename}:') 16 | print(linecache.getline(fname, int(lineno)), end='') 17 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/dbm_save.py: -------------------------------------------------------------------------------- 1 | import collections, fileinput, os, dbm 2 | word_pos = collections.defaultdict(list) 3 | for line in fileinput.input(): 4 | pos = f'{fileinput.filename()}{os.pathsep}{fileinput.filelineno()}' 5 | for word in line.split(): 6 | word_pos[word].append(pos) 7 | sep2 = os.pathsep * 2 8 | with dbm.open('indexfile','n') as dbm_out: 9 | for word in word_pos: 10 | dbm_out[word.encode('utf-8')] = sep2.join(word_pos[word]).encode('utf-8') 11 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/json_find.py: -------------------------------------------------------------------------------- 1 | import sys, json, dbm, linecache 2 | with dbm.open('indexfilem') as dbm_in: 3 | for word in sys.argv[1:]: 4 | if word not in dbm_in: 5 | print(f'Word {word!r} not found in index file',file=sys.stderr) 6 | continue 7 | places = json.loads(dbm_in[word]) 8 | for fname, lineno in places: 9 | print(f'Word {word!r} occurs in line {lineno} of file {fname!r}:') 10 | print(linecache.getline(fname, lineno), end='') 11 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/json_save.py: -------------------------------------------------------------------------------- 1 | import collections, fileinput, json, dbm 2 | word_pos = collections.defaultdict(list) 3 | for line in fileinput.input(): 4 | pos = fileinput.filename(), fileinput.filelineno() 5 | for word in line.split(): 6 | word_pos[word].append(pos) 7 | with dbm.open('indexfilem', 'n') as dbm_out: 8 | for word, word_positions in word_pos.items(): 9 | dbm_out[word] = json.dumps(word_positions) 10 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/pickle_find.py: -------------------------------------------------------------------------------- 1 | import sys, pickle, dbm, linecache 2 | with dbm.open('indexfilep') as dbm_in: 3 | for word in sys.argv[1:]: 4 | if word not in dbm_in: 5 | print(f'Word {word!r} not found in index file',file=sys.stderr) 6 | continue 7 | places = pickle.loads(dbm_in[word]) 8 | for fname, lineno in places: 9 | print(f'Word {word!r} occurs in line {lineno} of file {fname!r}:') 10 | print(linecache.getline(fname, lineno), end='') 11 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/pickle_save.py: -------------------------------------------------------------------------------- 1 | 2 | import collections, fileinput, pickle, dbm 3 | word_pos = collections.defaultdict(list) 4 | for line in fileinput.input(): 5 | pos = fileinput.filename(), fileinput.filelineno() 6 | for word in line.split(): 7 | word_pos[word].append(pos) 8 | 9 | with dbm.open('indexfilep', 'n') as dbm_out: 10 | for word, word_positions in word_pos.items(): 11 | dbm_out[word] = pickle.dumps(word_positions, protocol=2) 12 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/shelve_find.py: -------------------------------------------------------------------------------- 1 | import sys, shelve, linecache 2 | with shelve.open('indexfiles') as sh_in: 3 | for word in sys.argv[1:]: 4 | if word not in sh_in: 5 | print(f'Word {word!r} not found in index file',file=sys.stderr) 6 | continue 7 | places = sh_in[word] 8 | for fname, lineno in places: 9 | print(f'Word {word!r} occurs in line {lineno} of file {fname!r}:') 10 | print(linecache.getline(fname, lineno), end='') 11 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/shelve_save.py: -------------------------------------------------------------------------------- 1 | import collections, fileinput, shelve 2 | word_pos = collections.defaultdict(list) 3 | for line in fileinput.input(): 4 | pos = fileinput.filename(), fileinput.filelineno() 5 | for word in line.split(): 6 | word_pos[word].append(pos) 7 | with shelve.open('indexfiles','n') as sh_out: 8 | sh_out.update(word_pos) 9 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/sql_find.py: -------------------------------------------------------------------------------- 1 | import sys, sqlite3, linecache 2 | connect = sqlite3.connect('database.db') 3 | cursor = connect.cursor() 4 | for word in sys.argv[1:]: 5 | cursor.execute('SELECT File, Line FROM Words ' 6 | 'WHERE Word=?', [word]) 7 | places = cursor.fetchall() 8 | if not places: 9 | print(f'Word {word!r} not found in index file', 10 | file=sys.stderr) 11 | continue 12 | for fname, lineno in places: 13 | print(f'Word {word!r} occurs in line {lineno} of file {fname!r}:') 14 | print(linecache.getline(fname, lineno), end='') 15 | connect.close() 16 | -------------------------------------------------------------------------------- /12_Persistence_and_Databases/sql_save.py: -------------------------------------------------------------------------------- 1 | import fileinput, sqlite3 2 | connect = sqlite3.connect('database.db') 3 | cursor = connect.cursor() 4 | with connect: 5 | cursor.execute('CREATE TABLE IF NOT EXISTS Words ' 6 | '(Word TEXT, File TEXT, Line INT)') 7 | for line in fileinput.input(): 8 | f, l = fileinput.filename(), fileinput.filelineno() 9 | cursor.executemany('INSERT INTO Words VALUES (:w, :f, :l)', 10 | [{'w':w, 'f':f, 'l':l} for w in line.split()]) 11 | connect.close() 12 | -------------------------------------------------------------------------------- /13_Time_Operations/sched_module.py: -------------------------------------------------------------------------------- 1 | def enter_repeat(s, first_delay, period, priority, func, args): 2 | def repeating_wrapper(): 3 | s.enter(period, priority, repeating_wrapper, ()) 4 | func(*args) 5 | s.enter(first_delay, priority, repeating_wrapper, args) 6 | -------------------------------------------------------------------------------- /13_Time_Operations/zoneinfo_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> from datetime import datetime 3 | >>> import zoneinfo 4 | >>> d=datetime.now(tz=zoneinfo.ZoneInfo("America/Los_Angeles")) 5 | >>> d 6 | datetime.datetime(2021,10,21,16,32,23,96782,tzinfo=zoneinfo.ZoneInfo(key='America/Los_Angeles')) 7 | 8 | # Update the timezone to a different one without changing other attributes 9 | >>> dny=d.replace(tzinfo=zoneinfo.ZoneInfo("America/New_York")) 10 | >>> dny 11 | datetime.datetime(2021,10,21,16,32,23,96782,tzinfo=zoneinfo.ZoneInfo(key='America/New_York')) 12 | 13 | # Convert a datetime instance to UTC 14 | >>> dutc=d.astimezone(tz=zoneinfo.ZoneInfo("UTC")) 15 | >>> dutc 16 | datetime.datetime(2021,10,21,23,32,23,96782,tzinfo=zoneinfo.ZoneInfo(key='UTC')) 17 | 18 | # Get an aware timestamp of current time in UTC: 19 | >>> dutc=datetime.utcnow().replace(tzinfo=zoneinfo.ZoneInfo("UTC")) 20 | >>> dutc 21 | datetime.datetime(2021,10,21,23,32,23,96782,tzinfo=zoneinfo.ZoneInfo(key='UTC')) 22 | 23 | # Convert the datetime instance into a different timezone: 24 | >>> dutc.astimezone(zoneinfo.ZoneInfo("Europe/Rome")) 25 | datetime.datetime(2021,10,22,1,32,23,96782,tzinfo=zoneinfo.ZoneInfo(key='Europe/Rome')) 26 | 27 | # Get the local timezone: 28 | >>> tz_local=datetime.now().astimezone().tzinfo 29 | >>> tz_local 30 | datetime.timezone(datetime.timedelta(days=-1, seconds=68400), 'Central Daylight Time') 31 | 32 | # Get list of all timezones: 33 | >>> tz_list=zoneinfo.available_timezones() 34 | >>> sorted(tz_list)[0] 35 | 'Africa/Abidjan' 36 | 37 | """ 38 | 39 | if __name__ == '__main__': 40 | # use doctest to simulate console sessions 41 | import doctest 42 | doctest.testmod(verbose=True, exclude_empty=True) 43 | -------------------------------------------------------------------------------- /14_Contolling_Execution/code_object_type.py: -------------------------------------------------------------------------------- 1 | def g(x): 2 | print('g', x) 3 | 4 | code_object = g.__code__ 5 | 6 | def f(x): 7 | pass 8 | 9 | f.__code__ = code_object 10 | f(23) # prints: g 23 11 | -------------------------------------------------------------------------------- /14_Contolling_Execution/gc_module.py: -------------------------------------------------------------------------------- 1 | import gc 2 | 3 | gc_was_enabled = gc.isenabled() 4 | if gc_was_enabled: 5 | gc.collect() 6 | gc.disable() 7 | 8 | # insert some time-critical code here 9 | 10 | if gc_was_enabled: 11 | gc.enable() 12 | 13 | 14 | import contextlib 15 | 16 | @contextlib.contextmanager 17 | def gc_disabled(): 18 | gc_was_enabled = gc.isenabled() 19 | if gc_was_enabled: 20 | gc.collect() 21 | gc.disable() 22 | 23 | try: 24 | yield 25 | finally: 26 | if gc_was_enabled: 27 | gc.enable() 28 | 29 | 30 | with gc_disabled(): 31 | # insert some time-critical code here 32 | ... 33 | -------------------------------------------------------------------------------- /14_Contolling_Execution/weakref_module.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | 3 | 4 | class Tracking: 5 | _instances_dict = weakref.WeakValueDictionary() 6 | 7 | def __init__(self): 8 | Tracking._instances_dict[id(self)] = self 9 | 10 | @classmethod 11 | def instances(cls): 12 | return cls._instances_dict.values() 13 | 14 | 15 | 16 | tracked = [Tracking() for _ in range(10)] 17 | 18 | print(len(list(Tracking.instances()))) 19 | 20 | del tracked[3] 21 | print(len(list(Tracking.instances()))) 22 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/barrier_objects.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import threading 4 | import time 5 | 6 | 7 | def runner(): 8 | print('starting') 9 | time.sleep(random.randint(1, 3)) 10 | print('waiting') 11 | if barrier is not None: 12 | try: 13 | my_number = barrier.wait() 14 | except threading.BrokenBarrierError: 15 | print('Barrier abort() or reset() called, thread exiting...') 16 | return 17 | print(f'running ({my_number}) at {datetime.datetime.now()}') 18 | else: 19 | print(f'running at {datetime.datetime.now()}') 20 | 21 | 22 | def announce_release(): 23 | print('releasing') 24 | 25 | 26 | num_threads = 10 27 | barrier = threading.Barrier(num_threads, action=announce_release) 28 | # uncomment to see the behavior of threads without Barrier synchronization 29 | # barrier = None 30 | 31 | threads = [threading.Thread(target=runner) for _ in range(num_threads)] 32 | for t in threads: 33 | t.start() 34 | 35 | for t in threads: 36 | t.join() 37 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/concurrent_futures_as_completed.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures as cf 2 | 3 | from concurrent_futures_map import runner 4 | 5 | def make_dict(strings): 6 | with cf.ProcessPoolExecutor() as e: 7 | futures = [e.submit(runner, s) for s in strings] 8 | d = dict(f.result() 9 | for f in cf.as_completed(futures)) 10 | return d 11 | 12 | if __name__ == '__main__': 13 | dd = make_dict(['A', 'B', 'C', 'D', 'E']) 14 | print(dd) 15 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/concurrent_futures_map.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures as cf 2 | import os 3 | 4 | def f(s): 5 | """Run a long time, and eventually return a result.""" 6 | import time, random 7 | time.sleep(random.random()*2) # simulate slowness 8 | return s+s # some computation or other 9 | 10 | def runner(s): 11 | print(os.getpid(), s) 12 | return s, f(s) 13 | 14 | def make_dict(strings): 15 | with cf.ProcessPoolExecutor() as e: 16 | d = dict(e.map(runner, strings)) 17 | return d 18 | 19 | if __name__ == '__main__': 20 | dd = make_dict(['A', 'B', 'C', 'D', 'E']) 21 | print(dd) 22 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/condition_objects.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | c = threading.Condition() 4 | 5 | # thread function waits on Condition c 6 | def reacting_thread_function(s): 7 | with c: 8 | while not is_ok_state(s): 9 | c.wait() 10 | do_some_work_using_state(s) 11 | 12 | # thread function modifies state, and notifies all waiting threads 13 | def modifying_thread_function(s): 14 | with c: 15 | do_something_that_modifies_state(s) 16 | c.notify() # or, c.notify_all() 17 | # no need to call c.release(), exiting 'with' intrinsically does that 18 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/event_objects.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import threading 4 | import time 5 | 6 | 7 | def runner(): 8 | print('starting') 9 | time.sleep(random.randint(1, 3)) 10 | print('waiting') 11 | if event is not None: 12 | event.wait() 13 | print(f'running at {datetime.datetime.now()}') 14 | 15 | 16 | num_threads = 10 17 | event = threading.Event() 18 | # uncomment to see the behavior of threads without Event synchronization 19 | # event = None 20 | 21 | threads = [threading.Thread(target=runner) for _ in range(num_threads)] 22 | for t in threads: 23 | t.start() 24 | 25 | if event is not None: 26 | event.set() 27 | 28 | for t in threads: 29 | t.join() 30 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/manager_list.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | 3 | def make_list(): 4 | some_manager = mp.Manager() 5 | p = some_manager.list() 6 | p[:] = [1, 2, 3] 7 | print(p == [1, 2, 3]) # prints False, as it compares with p itself 8 | print(list(p) == [1, 2, 3]) # prints True, as it compares with copy 9 | 10 | # this raises an Exception, because functions are not picklable 11 | p.append(lambda: None) 12 | 13 | 14 | if __name__ == '__main__': 15 | make_list() 16 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/mmap_ipc_reader.py: -------------------------------------------------------------------------------- 1 | import mmap, os, time 2 | mx = mmap.mmap(os.open('xxx',os.O_RDWR), 1) 3 | last = None 4 | while True: 5 | mx.resize(mx.size()) 6 | data = mx[:] 7 | if data != last: 8 | print(data) 9 | last = data 10 | time.sleep(1) 11 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/mmap_ipc_writer.py: -------------------------------------------------------------------------------- 1 | fileob = open('xxx','wb') 2 | while True: 3 | data = input('Enter some text:') 4 | fileob.seek(0) 5 | fileob.write(data.encode()) 6 | fileob.truncate() 7 | fileob.flush() 8 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/multiprocessing_array.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | a = multiprocessing.Array('c', b'four score and seven') 4 | a.value = b'five' 5 | print(a.value) 6 | print(a[:]) 7 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/multiprocessing_manager.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | import os 3 | 4 | def f(s): 5 | """Run a long time, and eventually return a result.""" 6 | import time, random 7 | time.sleep(random.random()*2) # simulate slowness 8 | return s+s # some computation or other 9 | 10 | def runner(s, d): 11 | print(os.getpid(), s) 12 | d[s] = f(s) 13 | 14 | def make_dict(strings): 15 | mgr = mp.Manager() 16 | d = mgr.dict() 17 | workers = [] 18 | for s in strings: 19 | p = mp.Process(target=runner, args=(s, d)) 20 | p.start() 21 | workers.append(p) 22 | for p in workers: 23 | p.join() 24 | return {**d} 25 | 26 | if __name__ == '__main__': 27 | dd = make_dict(['A', 'B', 'C', 'D', 'E']) 28 | print(dd) 29 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/multiprocessing_pool.py: -------------------------------------------------------------------------------- 1 | import multiprocessing as mp 2 | import os 3 | 4 | def f(s): 5 | """Run a long time, and eventually return a result.""" 6 | import time, random 7 | time.sleep(random.random()*2) # simulate slowness 8 | return s+s # some computation or other 9 | 10 | def runner(s): 11 | print(os.getpid(), s) 12 | return s, f(s) 13 | 14 | def make_dict(strings): 15 | with mp.Pool() as pool: 16 | d = dict(pool.imap_unordered(runner, strings)) 17 | return d 18 | 19 | if __name__ == '__main__': 20 | dd = make_dict(['A', 'B', 'C', 'D', 'E']) 21 | print(dd) 22 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/multiprocessing_threadpool.py: -------------------------------------------------------------------------------- 1 | from multiprocessing.pool import ThreadPool 2 | 3 | from multiprocessing_pool import runner 4 | 5 | 6 | def make_dict(strings): 7 | d = {} 8 | with ThreadPool(3) as pool: 9 | for key, value in pool.imap_unordered(runner, strings): 10 | d[key] = value 11 | return d 12 | 13 | 14 | if __name__ == '__main__': 15 | dd = make_dict(['A', 'B', 'C', 'D', 'E']) 16 | print(dd) 17 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/process_environment.py: -------------------------------------------------------------------------------- 1 | import os 2 | shell = os.environ.get('COMSPEC') 3 | if shell is None: 4 | shell = os.environ.get('SHELL') 5 | if shell is None: 6 | shell = 'an unknown command processor' 7 | print('Running under', shell) 8 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/queue_eafp.py: -------------------------------------------------------------------------------- 1 | import queue 2 | 3 | q = queue.Queue() 4 | 5 | try: 6 | x = q.get_nowait() 7 | except queue.Empty: 8 | print('no work to perform') 9 | else: 10 | work_on(x) 11 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/queue_lbyl.py: -------------------------------------------------------------------------------- 1 | import queue 2 | 3 | q = queue.Queue() 4 | 5 | if q.empty(): 6 | print('no work to perform') 7 | else: 8 | x = q.get_nowait() 9 | work_on(x) 10 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/rlock_objects.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | lock = threading.RLock() 4 | global_state = [] 5 | 6 | def recursive_function(some, args): 7 | with lock: # acquires lock, guarantees release at end 8 | # ...modify global_state... 9 | if more_changes_needed(global_state): 10 | recursive_function(other, args) 11 | 12 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/thread_local_storage.py: -------------------------------------------------------------------------------- 1 | import threading 2 | L = threading.local() 3 | print('in main thread, setting zop to 42') 4 | L.zop = 42 5 | 6 | def targ(): 7 | print('in subthread, setting zop to 23') 8 | L.zop = 23 9 | print('in subthread, zop is now', L.zop) 10 | 11 | t = threading.Thread(target=targ) 12 | t.start() 13 | t.join() 14 | print('in main thread, zop is now', L.zop) 15 | 16 | # prints: 17 | # in main thread, setting zop to 42 18 | # in subthread, setting zop to 23 19 | # in subthread, zop is now 23 20 | # in main thread, zop is now 42 21 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/threaded_program_architecture_complete.py: -------------------------------------------------------------------------------- 1 | import random, time, queue, operator 2 | # copy here class Worker as defined above 3 | from threaded_program_architecture_worker import Worker 4 | 5 | requests_queue = queue.Queue() 6 | results_queue = queue.Queue() 7 | 8 | number_of_workers = 3 9 | workers = [Worker(requests_queue, results_queue) 10 | for i in range(number_of_workers)] 11 | work_requests = {} 12 | 13 | operations = { 14 | '+': operator.add, 15 | '-': operator.sub, 16 | '*': operator.mul, 17 | '/': operator.truediv, 18 | '%': operator.mod, 19 | } 20 | 21 | def pick_a_worker(): 22 | return random.choice(workers) 23 | 24 | def make_work(): 25 | o1 = random.randrange(2, 10) 26 | o2 = random.randrange(2, 10) 27 | op = random.choice(list(operations)) 28 | return f'{o1} {op} {o2}' 29 | 30 | def slow_evaluate(expression_string): 31 | time.sleep(random.randrange(1, 5)) 32 | op1, oper, op2 = expression_string.split() 33 | arith_function = operations[oper] 34 | return arith_function(int(op1), int(op2)) 35 | 36 | def show_results(): 37 | while True: 38 | try: 39 | completed_id, results = results_queue.get_nowait() 40 | except queue.Empty: 41 | return 42 | work_expression = work_requests.pop(completed_id) 43 | print(f'Result {completed_id}: {work_expression} -> {results}') 44 | 45 | 46 | for i in range(10): 47 | expression_string = make_work() 48 | worker = pick_a_worker() 49 | request_id = worker.perform_work(slow_evaluate, expression_string) 50 | work_requests[request_id] = expression_string 51 | print(f'Submitted request {request_id}: {expression_string}') 52 | time.sleep(1.0) 53 | show_results() 54 | 55 | while work_requests: 56 | time.sleep(1.0) 57 | show_results() 58 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/threaded_program_architecture_external_interfacing.py: -------------------------------------------------------------------------------- 1 | import threading, queue 2 | 3 | 4 | class ExternalInterfacing(threading.Thread): 5 | def __init__(self, external_callable, **kwds): 6 | super().__init__(**kwds) 7 | self.daemon = True 8 | self.external_callable = external_callable 9 | self.request_queue = queue.Queue() 10 | self.result_queue = queue.Queue() 11 | self.start() 12 | 13 | def request(self, *args, **kwds): 14 | """called by other threads as external_callable would be""" 15 | self.request_queue.put((args, kwds)) 16 | return self.result_queue.get() 17 | 18 | def run(self): 19 | while True: 20 | a, k = self.request_queue.get() 21 | self.result_queue.put(self.external_callable(*a, **k)) 22 | 23 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/threaded_program_architecture_serializer.py: -------------------------------------------------------------------------------- 1 | import queue 2 | import threading 3 | import time 4 | from itertools import count 5 | 6 | 7 | class Serializer(threading.Thread): 8 | def __init__(self, **kwds): 9 | super().__init__(**kwds) 10 | self.daemon = True 11 | self.work_request_queue = queue.Queue() 12 | self.work_results = {} 13 | self.request_id_generator = count() 14 | self.start() 15 | 16 | def run(self): 17 | while True: 18 | seq_num, callable, args, kwds = self.work_request_queue.get(timeout=3) 19 | self.work_results[seq_num] = callable(*args, **kwds) 20 | 21 | def apply(self, callable, *args, **kwds): 22 | """called by other threads as `callable` would be""" 23 | request_seq_num = next(self.request_id_generator) 24 | self.work_request_queue.put((request_seq_num, callable, args, kwds)) 25 | result_not_yet_available_sentinel = object() 26 | while True: 27 | result = self.work_results.pop(request_seq_num, result_not_yet_available_sentinel) 28 | if result is not result_not_yet_available_sentinel: 29 | return result 30 | time.sleep(0.05) 31 | 32 | 33 | # ====== demo only - do not include in book copy ====== 34 | 35 | if __name__ == '__main__': 36 | 37 | """Tests the Serializer class.""" 38 | serializer = Serializer() 39 | 40 | def request_fn(ii): 41 | expected = ii*ii 42 | actual = serializer.apply(lambda x: x * x, ii) 43 | if True: # actual != expected: 44 | print( 45 | f"\n{expected=}, {actual=} {'<<<' if (expected != actual) else ''}", 46 | end="", 47 | flush=True 48 | ) 49 | 50 | num_threads = 100 51 | for i in range(num_threads): 52 | thread = threading.Thread(target=request_fn, args=(i,)) 53 | thread.start() 54 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/threaded_program_architecture_worker.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class Worker(threading.Thread): 4 | IDlock = threading.Lock() 5 | request_ID = 0 6 | 7 | def __init__(self, requests_queue, results_queue, **kwds): 8 | super().__init__(**kwds) 9 | self.daemon = True 10 | self.request_queue = requests_queue 11 | self.result_queue = results_queue 12 | self.start() 13 | 14 | def perform_work(self, callable, *args, **kwds): 15 | """called by main thread as `callable` would be, but w/o return""" 16 | with self.IDlock: 17 | Worker.request_ID += 1 18 | self.request_queue.put( 19 | (Worker.request_ID, callable, args, kwds)) 20 | return Worker.request_ID 21 | 22 | def run(self): 23 | while True: 24 | request_ID, callable, a, k = self.request_queue.get() 25 | self.result_queue.put((request_ID, callable(*a, **k))) 26 | 27 | 28 | if __name__ == '__main__': 29 | import queue 30 | 31 | requests_queue = queue.Queue() 32 | results_queue = queue.Queue() 33 | number_of_workers = 5 34 | for i in range(number_of_workers): 35 | worker = Worker(requests_queue, results_queue) 36 | -------------------------------------------------------------------------------- /15_Threads_and_Processes/timer_objects.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | class Periodic(threading.Timer): 4 | def __init__(self, interval, callback, args=None, kwargs=None): 5 | super().__init__(interval, self._f, args, kwargs) 6 | self.callback = callback 7 | 8 | def _f(self, *args, **kwargs): 9 | p = type(self)(self.interval, self.callback, args, kwargs) 10 | p.start() 11 | try: 12 | self.callback(*args, **kwargs) 13 | except Exception: 14 | p.cancel() 15 | 16 | 17 | def countdown(): 18 | global times 19 | times -= 1 20 | if times < 0: 21 | raise Exception('all done!') 22 | print('...', times, sep='', end='') 23 | 24 | 25 | times = 11 26 | Periodic(1, countdown).start() 27 | -------------------------------------------------------------------------------- /16_Numeric_Processing/decimal_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> from decimal import Decimal 3 | >>> df = Decimal(0.1) 4 | >>> df 5 | Decimal('0.1000000000000000055511151231257827021181583404541015625') 6 | 7 | >>> ds = Decimal(str(0.1)) # or, directly, Decimal('0.1') 8 | >>> ds 9 | Decimal('0.1') 10 | 11 | def dfs(x): 12 | return Decimal(str(x)) 13 | 14 | >>> dq = Decimal(0.1).quantize(Decimal('.00')) 15 | >>> dq 16 | Decimal('0.10') 17 | 18 | >>> pidigits = (3, 1, 4, 1, 5) 19 | >>> Decimal((1, pidigits, -4)) 20 | Decimal('-3.1415') 21 | 22 | >>> import math 23 | >>> a = 1.1 24 | >>> d = Decimal('1.1') 25 | >>> a == d 26 | False 27 | >>> math.isclose(a, d) 28 | True 29 | >>> a + d 30 | Traceback (most recent call last): 31 | ... 32 | TypeError: unsupported operand type(s) for +: 'float' and 'decimal.Decimal' 33 | >>> d + Decimal(a) # new decimal constructed from a 34 | Decimal('2.200000000000000088817841970') 35 | >>> d + Decimal(str(a)) # convert a to decimal with str(a) 36 | Decimal('2.2') 37 | """ 38 | 39 | if __name__ == '__main__': 40 | # use doctest to simulate console sessions 41 | import doctest 42 | doctest.testmod(verbose=True, exclude_empty=True) 43 | -------------------------------------------------------------------------------- /16_Numeric_Processing/dont_use_a_float_as_a_loop_control_variable.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> f = 1 3 | >>> while f != 0: 4 | ... f -= 0.2 # even though f started as int, it's now a float 5 | ... if isinstance(f, float): break 6 | 7 | >>> # This code shows why: 8 | >>> 1 - 0.2 - 0.2 - 0.2 - 0.2 - 0.2 # should be 0, but... 9 | 5.551115123125783e-17 10 | 11 | 12 | >>> f = 1 13 | >>> count = 0 14 | >>> while f > 0: 15 | ... count += 1 16 | ... f -= 0.2 17 | >>> # 1 time too many! 18 | >>> print(count) 19 | 6 20 | 21 | >>> import math 22 | >>> 23 | >>> f = 1 24 | >>> count = 0 25 | >>> while not math.isclose(0, f, abs_tol=1e-15): 26 | ... count += 1 27 | ... f -= 0.2 28 | >>> # just right this time! 29 | >>> print(count) 30 | 5 31 | """ 32 | 33 | if __name__ == '__main__': 34 | # use doctest to simulate console sessions 35 | import doctest 36 | doctest.testmod(verbose=True, exclude_empty=True) 37 | -------------------------------------------------------------------------------- /16_Numeric_Processing/dont_use_eqeq_between_floats_or_complex_numbers.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import math 3 | >>> f = 1.1 + 2.2 - 3.3 # f is intuitively equal to 0 4 | 5 | >>> f==0 6 | False 7 | >>> f 8 | 4.440892098500626e-16 9 | >>> # default tolerance is fine for this comparison 10 | >>> math.isclose(-1, f-1) 11 | True 12 | 13 | >>> # near-0 comparison with default tolerances 14 | >>> math.isclose(0, f) 15 | False 16 | >>> # must use abs_tol for comparison with 0 17 | >>> math.isclose(0, f, abs_tol=1e-15) 18 | True 19 | """ 20 | 21 | if __name__ == '__main__': 22 | # use doctest to simulate console sessions 23 | import doctest 24 | doctest.testmod(verbose=True, exclude_empty=True) 25 | 26 | -------------------------------------------------------------------------------- /16_Numeric_Processing/floating_point_numbers.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> f = 1.1 + 2.2 - 3.3 # f should be equal to 0 3 | >>> f 4 | 4.440892098500626e-16 5 | 6 | >>> f = 2**53 7 | >>> f 8 | 9007199254740992 9 | >>> # integer arithmetic is not bounded 10 | >>> f + 1 11 | 9007199254740993 12 | >>> # conversion to float loses integer precision at 2**53 13 | >>> f + 1.0 14 | 9007199254740992.0 15 | """ 16 | 17 | if __name__ == '__main__': 18 | # use doctest to simulate console sessions 19 | import doctest 20 | doctest.testmod(verbose=True, exclude_empty=True) 21 | -------------------------------------------------------------------------------- /16_Numeric_Processing/fractions_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> from fractions import Fraction 3 | >>> from decimal import Decimal 4 | >>> Fraction(1,10) 5 | Fraction(1, 10) 6 | >>> Fraction(Decimal('0.1')) 7 | Fraction(1, 10) 8 | >>> Fraction('0.1') 9 | Fraction(1, 10) 10 | >>> Fraction('1/10') 11 | Fraction(1, 10) 12 | >>> Fraction(0.1) 13 | Fraction(3602879701896397, 36028797018963968) 14 | >>> Fraction(-1, 10) 15 | Fraction(-1, 10) 16 | >>> Fraction(-1,-10) 17 | Fraction(1, 10) 18 | """ 19 | 20 | if __name__ == '__main__': 21 | # use doctest to simulate console sessions 22 | import doctest 23 | doctest.testmod(verbose=True, exclude_empty=True) 24 | -------------------------------------------------------------------------------- /16_Numeric_Processing/math_cmath_atan2.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import math 3 | >>> math.atan(-1./-1.) 4 | 0.7853981633974483 5 | >>> math.atan2(-1., -1.) 6 | -2.356194490192345 7 | """ 8 | 9 | if __name__ == '__main__': 10 | # use doctest to simulate console sessions 11 | import doctest 12 | doctest.testmod(verbose=True, exclude_empty=True) 13 | -------------------------------------------------------------------------------- /16_Numeric_Processing/numpy_array.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | np.array([1, 2, 3, 4]) # from a Python list 4 | # array([1, 2, 3, 4]) 5 | 6 | # a common error: passing items separately (they 7 | # must be passed as a sequence, e.g. a list) 8 | np.array(5, 6, 7) 9 | 10 | s = 'alph', 'abet' # a tuple of two strings 11 | np.array(s) 12 | # array(['alph', 'abet'], dtype=' sin_degrees._maxsize: 27 | oldest_key = next(iter(cache)) 28 | del cache[oldest_key] 29 | return cache[x] 30 | sin_degrees._cached_values = {} 31 | sin_degrees._maxsize = 512 32 | 33 | # function with bounded LRU cache 34 | import functools 35 | @functools.lru_cache(maxsize=512) 36 | def sin_degrees(x): 37 | return math.sin(math.radians(x)) 38 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/mod.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module supplies a single function reverse_words that reverses 3 | a string by words. 4 | 5 | >>> reverse_words('four score and seven years') 6 | 'years seven and score four' 7 | >>> reverse_words('justoneword') 8 | 'justoneword' 9 | >>> reverse_words('') 10 | '' 11 | 12 | You must call reverse_words with one argument, a string: 13 | 14 | >>> reverse_words() 15 | Traceback (most recent call last): 16 | ... 17 | TypeError: reverse_words() missing 1 required positional argument: 'astring' 18 | >>> reverse_words('one', 'another') 19 | Traceback (most recent call last): 20 | ... 21 | TypeError: reverse_words() takes 1 positional argument but 2 were given 22 | >>> reverse_words(1) 23 | Traceback (most recent call last): 24 | ... 25 | AttributeError: 'int' object has no attribute 'split' 26 | >>> reverse_words('𝒰𝓷𝓲𝓬𝓸𝓭𝓮 is all right too') 27 | 'too right all is 𝒰𝓷𝓲𝓬𝓸𝓭𝓮' 28 | 29 | As a side effect, reverse_words eliminates any redundant spacing: 30 | 31 | >>> reverse_words('with redundant spacing') 32 | 'spacing redundant with' 33 | 34 | """ 35 | 36 | 37 | def reverse_words(astring): 38 | words = astring.split() 39 | words.reverse() 40 | return ' '.join(words) 41 | 42 | 43 | if __name__ == '__main__': 44 | import doctest 45 | 46 | doctest.testmod(verbose=True, exclude_empty=True) 47 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/nose2_example.py: -------------------------------------------------------------------------------- 1 | # run in the current directory using "nose2 nose2_example.TestCase" 2 | 3 | import unittest 4 | from nose2.tools import params 5 | 6 | 7 | class TestCase(unittest.TestCase): 8 | 9 | @params((5, 5), 10 | (-1, 1), 11 | ('a', None, TypeError)) 12 | def test_abs_value(self, x, expected, should_raise=None): 13 | if should_raise is not None: 14 | with self.assertRaises(should_raise): 15 | abs(x) 16 | else: 17 | assert abs(x) == expected 18 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/optimizing_loops.py: -------------------------------------------------------------------------------- 1 | def slower(anobject, ahugenumber): 2 | for i in range(ahugenumber): 3 | anobject.amethod(i) 4 | 5 | # hoisting attribute lookup of method 'amethod' out of the loop 6 | def faster(anobject, ahugenumber): 7 | themethod = anobject.amethod 8 | for i in range(ahugenumber): 9 | themethod(i) 10 | 11 | def slightly_slower(asequence, adict): 12 | for x in asequence: 13 | adict[x] = hex(x) 14 | 15 | # copying a global builtin to a local name 16 | def slightly_faster(asequence, adict): 17 | myhex = hex 18 | for x in asequence: 19 | adict[x] = myhex(x) 20 | 21 | 22 | # list comprehension compared to append and list(map(...)) 23 | import timeit, operator 24 | 25 | 26 | def slow(asequence): 27 | result = [] 28 | for x in asequence: 29 | result.append(-x) 30 | return result 31 | 32 | 33 | def middling(asequence): 34 | return list(map(operator.neg, asequence)) 35 | 36 | 37 | def fast(asequence): 38 | return [-x for x in asequence] 39 | 40 | 41 | for afunc in slow, middling, fast: 42 | timing = timeit.repeat('afunc(big_seq)', 43 | setup='big_seq=range(500*1000)', 44 | globals={'afunc': afunc}, 45 | repeat=5, 46 | number=100) 47 | # print(f'{afunc.__name__:<10}: {timing}') 48 | for t in timing: 49 | print(f'{afunc.__name__},{t}') 50 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/pdb_example.py: -------------------------------------------------------------------------------- 1 | import pdb 2 | 3 | def f(x): 4 | pdb.set_trace() 5 | # At (Pdb) prompt, 'x' will display the value for x 6 | 7 | # Enter 'c' command to advance to the next breakpoint 8 | if x > 100: 9 | q = x**2 10 | else: 11 | q = x 12 | 13 | pdb.set_trace() 14 | # At (Pdb) prompt, 'q' will *not* display the value for q 15 | # since 'q' is defined as an abbreviation for the Pdb 'quit' 16 | # command - exiting the debugger and the program! 17 | # 18 | # To view the value for q, enter '!q' or `p q`. 19 | return q 20 | 21 | f(10) 22 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/pre_computing_a_lookup_table.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | _sin_degrees_lookup = {x: math.sin(math.radians(x)) for x in range(0, 360+1)} 4 | sin_degrees = _sin_degrees_lookup.__getitem__ 5 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/pytest_example.py: -------------------------------------------------------------------------------- 1 | # pytest discovery scans for files named test_*.py or *_test.py, 2 | # or scans files explicitly passed to pytest. 3 | # 4 | # Within those files, tests are discovered if they: 5 | # - are a function at module scope and start with "test_", or 6 | # - are in a class named "Test*" and are a method with name starting with "test_" 7 | # 8 | # Run this file using "pytest pytest_example.py" 9 | # 10 | import pytest 11 | 12 | def test_passing_at_module_level(): 13 | assert 1 == 1 14 | 15 | def test_failing_at_module_level(): 16 | assert 1 == 0 17 | 18 | def test_error_at_module_level(): 19 | 1/0 20 | 21 | 22 | class TestA: 23 | def test_passing(self): 24 | assert 1 == 1 25 | 26 | def test_failing(self): 27 | assert 1 == 0 28 | 29 | def test_error(self): 30 | 1 / 0 31 | 32 | 33 | # use raises to test when an expected exception is raised 34 | with pytest.raises(ZeroDivisionError, match="zero"): 35 | 1/0 36 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/pytest_parametrize.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | @pytest.mark.parametrize("x,y,expected", 4 | [ 5 | (1, 0, True), 6 | (0, 1, False) 7 | ]) 8 | def test_is_greater(x, y, expected): 9 | assert (x > y) == expected 10 | 11 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/short_circuiting_of_iterators.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | 4 | def is_prime(n): 5 | # only valid for n < 200 6 | if n > 200: 7 | raise ValueError(f"is_prime() only valid up to n=200 ({n})") 8 | 9 | return n in ( 10 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 11 | 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 12 | 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 13 | 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199 14 | ) 15 | 16 | 17 | any(x ** 2 > 100 for x in range(50)) 18 | # returns True once it reaches 10, skips the rest. 19 | 20 | odd_numbers_greater_than_1 = range(3, 100, 2) 21 | all(is_prime(x) for x in odd_numbers_greater_than_1) 22 | # returns False: 3, 5, and 7 are prime but 9 is not 23 | 24 | next(c for c in string.ascii_uppercase if c in "AEIOU") 25 | # returns 'A' without checking the remaining characters. 26 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/using_doctest.py: -------------------------------------------------------------------------------- 1 | __test__ = { 2 | "reverse_words (using_doctest.py)": 3 | """ 4 | This module supplies a single function reverse_words that reverses 5 | a string by words. 6 | 7 | >>> reverse_words('four score and seven years') 8 | 'years seven and score four' 9 | >>> reverse_words('justoneword') 10 | 'justoneword' 11 | >>> reverse_words('') 12 | '' 13 | 14 | You must call reverse_words with one argument, a string: 15 | 16 | >>> reverse_words() 17 | Traceback (most recent call last): 18 | ... 19 | TypeError: reverse_words() missing 1 required positional argument: 'astring' 20 | >>> reverse_words('one', 'another') 21 | Traceback (most recent call last): 22 | ... 23 | TypeError: reverse_words() takes 1 positional argument but 2 were given 24 | >>> reverse_words(1) 25 | Traceback (most recent call last): 26 | ... 27 | AttributeError: 'int' object has no attribute 'split' 28 | >>> reverse_words('𝒰𝓷𝓲𝓬𝓸𝓭𝓮 is all right too') 29 | 'too right all is 𝒰𝓷𝓲𝓬𝓸𝓭𝓮' 30 | 31 | As a side effect, reverse_words eliminates any redundant spacing: 32 | 33 | >>> reverse_words('with redundant spacing') 34 | 'spacing redundant with' 35 | 36 | """ 37 | } 38 | 39 | def reverse_words(astring): 40 | words = astring.split() 41 | words.reverse() 42 | return ' '.join(words) 43 | 44 | 45 | if __name__ == '__main__': 46 | import doctest 47 | 48 | doctest.testmod(verbose=True, exclude_empty=True) 49 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/using_unittest.py: -------------------------------------------------------------------------------- 1 | """ This module tests function reverse_words 2 | provided by module mod.py. """ 3 | import unittest 4 | import mod 5 | 6 | 7 | class ModTest(unittest.TestCase): 8 | 9 | def testNormalCaseWorks(self): 10 | self.assertEqual( 11 | mod.reverse_words('four score and seven years'), 12 | 'years seven and score four') 13 | 14 | def testSingleWordIsNoop(self): 15 | self.assertEqual( 16 | mod.reverse_words('justoneword'), 17 | 'justoneword') 18 | 19 | def testEmptyWorks(self): 20 | self.assertEqual(mod.reverse_words(''), '') 21 | 22 | def testRedundantSpacingGetsRemoved(self): 23 | self.assertEqual( 24 | mod.reverse_words('with redundant spacing'), 25 | 'spacing redundant with') 26 | 27 | def testUnicodeWorks(self): 28 | self.assertEqual( 29 | mod.reverse_words('𝒰𝓷𝓲𝓬𝓸𝓭𝓮 is all right too'), 30 | 'too right all is 𝒰𝓷𝓲𝓬𝓸𝓭𝓮') 31 | 32 | def testExactlyOneArgumentIsEnforced(self): 33 | with self.assertRaises(TypeError): 34 | mod.reverse_words('one', 'another') 35 | 36 | def testArgumentMustBeString(self): 37 | with self.assertRaises((AttributeError, TypeError)): 38 | mod.reverse_words(1) 39 | 40 | 41 | if __name__ == '__main__': 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /17_Testing_Debugging_and_Optimizing/warnings_warn_to_unicode.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | 4 | def to_unicode(bytestr): 5 | try: 6 | return bytestr.decode() 7 | except UnicodeError: 8 | warnings.warn(f'Invalid characters in {bytestr!r}', 9 | stacklevel=2) 10 | return bytestr.decode(errors='ignore') 11 | 12 | 13 | # call to_unicode with invalid characters, warning will point to this line 14 | to_unicode(b'ABC\xff\xfeDEF') 15 | -------------------------------------------------------------------------------- /18_Basic_Networking/tcpclient.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | IP_ADDR = 'localhost' 4 | IP_PORT = 8881 5 | MESSAGE = """\ 6 | A few lines of text 7 | including non-ASCII characters: €£ 8 | to test the operation 9 | of both server 10 | and client.""" 11 | 12 | with socket.socket(socket.AF_INET, # IPv4 13 | socket.SOCK_STREAM # TCP 14 | ) as sock: 15 | sock.connect((IP_ADDR, IP_PORT)) 16 | print(f'Connected to server {IP_ADDR}:{IP_PORT}') 17 | for line in MESSAGE.splitlines(): 18 | data = line.encode('utf-8') 19 | sock.sendall(data) 20 | print(f'SENT {data!r} ({len(data)})') 21 | response, address = sock.recvfrom(1024) # buffer size: 1024 22 | print(f'RCVD {response!r} ({len(response)}) from {address}') 23 | 24 | print('Disconnected from server') 25 | -------------------------------------------------------------------------------- /18_Basic_Networking/tcpclient6.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | IP_ADDR = 'localhost' 4 | IP_PORT = 8881 5 | MESSAGE = """\ 6 | A few lines of text 7 | including non-ASCII characters: €£ 8 | to test the operation 9 | of both server 10 | and client.""" 11 | 12 | with socket.socket(socket.AF_INET6, # IPv6 13 | socket.SOCK_STREAM # TCP 14 | ) as sock: 15 | sock.connect((IP_ADDR, IP_PORT)) 16 | print(f'Connected to server {IP_ADDR}:{IP_PORT}') 17 | for line in MESSAGE.splitlines(): 18 | data = line.encode('utf-8') 19 | sock.sendall(data) 20 | print(f'SENT {data!r} ({len(data)})') 21 | response, address = sock.recvfrom(1024) # buffer size: 1024 22 | print(f'RCVD {response!r} ({len(response)}) from {address}') 23 | 24 | print('Disconnected from server') 25 | -------------------------------------------------------------------------------- /18_Basic_Networking/tcpserver.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures as cf 2 | import socket 3 | 4 | IP_ADDR = 'localhost' 5 | IP_PORT = 8881 6 | 7 | def handle(new_sock, address): 8 | print('Connected from', address) 9 | with new_sock: 10 | while True: 11 | received = new_sock.recv(1024) 12 | if not received: 13 | break 14 | s = received.decode('utf-8', errors='replace') 15 | print(f'Recv: {s!r}') 16 | new_sock.sendall(received) 17 | print(f'Echo: {s!r}') 18 | print(f'Disconnected from {address}') 19 | 20 | 21 | with socket.socket(socket.AF_INET, # IPv4 22 | socket.SOCK_STREAM # TCP 23 | ) as servsock: 24 | servsock.bind((IP_ADDR, IP_PORT)) 25 | servsock.listen(5) 26 | print(f'Serving at {servsock.getsockname()}') 27 | with cf.ThreadPoolExecutor(20) as e: 28 | while True: 29 | new_sock, address = servsock.accept() 30 | e.submit(handle, new_sock, address) 31 | -------------------------------------------------------------------------------- /18_Basic_Networking/tcpserver6.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures as cf 2 | import socket 3 | 4 | IP_ADDR = 'localhost' 5 | IP_PORT = 8881 6 | 7 | def handle(new_sock, address): 8 | print('Connected from', address) 9 | with new_sock: 10 | while True: 11 | received = new_sock.recv(1024) 12 | if not received: 13 | break 14 | s = received.decode('utf-8', errors='replace') 15 | print(f'Recv: {s!r}') 16 | new_sock.sendall(received) 17 | print(f'Echo: {s!r}') 18 | print(f'Disconnected from {address}') 19 | 20 | 21 | with socket.socket(socket.AF_INET6, # IPv6 22 | socket.SOCK_STREAM # TCP 23 | ) as servsock: 24 | servsock.bind((IP_ADDR, IP_PORT)) 25 | servsock.listen(5) 26 | print(f'Serving at {servsock.getsockname()}') 27 | with cf.ThreadPoolExecutor(20) as e: 28 | while True: 29 | new_sock, address = servsock.accept() 30 | e.submit(handle, new_sock, address) 31 | -------------------------------------------------------------------------------- /18_Basic_Networking/udpclient.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import socket 3 | 4 | UDP_IP = 'localhost' 5 | UDP_PORT = 8883 6 | MESSAGE = """\ 7 | This is a bunch of lines, each 8 | of which will be sent in a single 9 | UDP datagram. No error detection 10 | or correction will occur. 11 | Crazy bananas! £€ should go through.""" 12 | 13 | server = UDP_IP, UDP_PORT 14 | with socket.socket(socket.AF_INET, # IPv4 15 | socket.SOCK_DGRAM, # UDP 16 | ) as sock: 17 | for line in MESSAGE.splitlines(): 18 | data = line.encode('utf-8') 19 | bytes_sent = sock.sendto(data, server) 20 | print(f'SENT {data!r} ({bytes_sent}/{len(data)}) to {server}') 21 | response, address = sock.recvfrom(1024) # buffer size: 1024 22 | print(f'RCVD {response.decode("utf-8")!r} ({len(response)}) from {address}') 23 | 24 | print('Disconnected from server') 25 | -------------------------------------------------------------------------------- /18_Basic_Networking/udpclient6.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import socket 3 | 4 | UDP_IP = 'localhost' 5 | UDP_PORT = 8883 6 | MESSAGE = """\ 7 | This is a bunch of lines, each 8 | of which will be sent in a single 9 | UDP datagram. No error detection 10 | or correction will occur. 11 | Crazy bananas! £€ should go through.""" 12 | 13 | server = UDP_IP, UDP_PORT 14 | with socket.socket(socket.AF_INET6, # IPv6 15 | socket.SOCK_DGRAM, # UDP 16 | ) as sock: 17 | for line in MESSAGE.splitlines(): 18 | data = line.encode('utf-8') 19 | bytes_sent = sock.sendto(data, server) 20 | print(f'SENT {data!r} ({bytes_sent}/{len(data)}) to {server}') 21 | response, address = sock.recvfrom(1024) # buffer size: 1024 22 | print(f'RCVD {response.decode("utf-8")!r} ({len(response)}) from {address}') 23 | 24 | print('Disconnected from server') 25 | -------------------------------------------------------------------------------- /18_Basic_Networking/udpserver.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | UDP_IP = 'localhost' 4 | UDP_PORT = 8883 5 | 6 | with socket.socket(socket.AF_INET, # IPv4 7 | socket.SOCK_DGRAM # UDP 8 | ) as sock: 9 | sock.bind((UDP_IP, UDP_PORT)) 10 | print(f'Serving UDP at {UDP_IP}:{UDP_PORT}') 11 | while True: 12 | data, sender_addr = sock.recvfrom(1024) # buffer size is 1024 bytes 13 | print(f'RCVD {data!r} ({len(data)}) from {sender_addr}') 14 | bytes_sent = sock.sendto(data, sender_addr) 15 | print(f'SENT {data!r} ({bytes_sent}/{len(data)}) to {sender_addr}') 16 | -------------------------------------------------------------------------------- /18_Basic_Networking/udpserver6.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | UDP_IP = 'localhost' 4 | UDP_PORT = 8883 5 | 6 | with socket.socket(socket.AF_INET6, # IPv6 7 | socket.SOCK_DGRAM # UDP 8 | ) as sock: 9 | sock.bind((UDP_IP, UDP_PORT)) 10 | print(f'Serving UDP at {UDP_IP}:{UDP_PORT}') 11 | while True: 12 | data, sender_addr = sock.recvfrom(1024) # buffer size is 1024 bytes 13 | print(f'RCVD {data!r} ({len(data)}) from {sender_addr}') 14 | bytes_sent = sock.sendto(data, sender_addr) 15 | print(f'SENT {data!r} ({bytes_sent}/{len(data)}) to {sender_addr}') 16 | -------------------------------------------------------------------------------- /19_client_side_network_protocol_modules/third_party_requests_package.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | r = requests.Request('GET', 'http://www.example.com', 4 | data={'foo': 'bar'}, params={'fie': 'foo'}) 5 | p = r.prepare() 6 | print(p.url) 7 | print(p.headers) 8 | print(p.body) 9 | 10 | 11 | r = requests.Request('POST', 'http://www.example.com', 12 | data={'foo': 'bar'}, files={'fie': 'foo'}) 13 | p = r.prepare() 14 | print(p.headers) 15 | print(p.body) 16 | 17 | -------------------------------------------------------------------------------- /19_client_side_network_protocol_modules/urllib_parse_module.py: -------------------------------------------------------------------------------- 1 | import urllib.parse as urlparse 2 | 3 | print(urlparse.urljoin('http://host.com/some/path/here','../other/path')) 4 | # 'http://host.com/some/other/path' 5 | 6 | print(urlparse.urlsplit('http://www.python.org:80/faq.cgi?src=file')) 7 | # ('http','www.python.org:80','/faq.cgi','src=file','') 8 | 9 | print(urlparse.urlunsplit(('http','www.python.org','/faq.cgi','src=fie',''))) 10 | # 'http://www.python.org/faq.cgi?src=fie' 11 | 12 | print(urlparse.urlsplit('http://a.com/path/a?')) 13 | # 'http://a.com/path/a' 14 | -------------------------------------------------------------------------------- /19_client_side_network_protocol_modules/urllib_request_urlopen_example.py: -------------------------------------------------------------------------------- 1 | import urllib.request 2 | unicode_url = ("https://www.unicode.org/Public" 3 | "/14.0.0/ucd/UnicodeData.txt") 4 | with urllib.request.urlopen(unicode_url) as url_response: 5 | unicode_db = url_response.read() 6 | 7 | print(f'{len(unicode_db):,} bytes read') 8 | -------------------------------------------------------------------------------- /20_serving_http/client1.py: -------------------------------------------------------------------------------- 1 | import requests, json 2 | 3 | result = requests.post('http://localhost:8000/items/new/', 4 | json={"name": "Item1", 5 | "price": 12.34, 6 | "description": "Rusty old bucket"}) 7 | print(result.status_code, result.json()) 8 | 9 | result = requests.get('http://localhost:8000/items/Item1/') 10 | print(result.status_code, result.json()) 11 | 12 | result = requests.post('http://localhost:8000/items/new/', 13 | json={"name": "Item2", 14 | "price": "Not a number"}) 15 | print(result.status_code, result.json()) 16 | -------------------------------------------------------------------------------- /20_serving_http/clitest.py: -------------------------------------------------------------------------------- 1 | from server2 import connect, MItem 2 | -------------------------------------------------------------------------------- /20_serving_http/debug_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | debug_test.py: create and interact with the FastAPI app object. 3 | """ 4 | from server2 import app 5 | from wsgiref.simple_server import make_server 6 | 7 | app.debug = True 8 | 9 | with make_server('', 8000, app) as httpd: 10 | print("Serving HTTP on port 8000...") 11 | 12 | # Respond to requests until process is killed 13 | httpd.serve_forever() 14 | 15 | # Alternative: serve one request, then exit 16 | httpd.handle_request() 17 | 18 | -------------------------------------------------------------------------------- /20_serving_http/flask_example.py: -------------------------------------------------------------------------------- 1 | # run this server using the command "flask --app flask_example run" 2 | 3 | import datetime, flask 4 | 5 | app = flask.Flask(__name__) 6 | 7 | # secret key for cryptographic components such as encoding session cookies 8 | # for production use, use secrets.token_bytes() 9 | app.secret_key = b'\xc5\x8f\xbc\xa2\x1d\xeb\xb3\x94;:d\x03' 10 | 11 | 12 | @app.route('/') 13 | def greet(): 14 | lastvisit = flask.session.get('lastvisit') 15 | now = datetime.datetime.now() 16 | newvisit = now.ctime() 17 | template = ''' 18 | Hello, visitor! 19 | 20 | {% if lastvisit %} 21 |

Welcome back to this site!

22 |

You last visited on {{lastvisit}} UTC

23 |

This visit on {{newvisit}} UTC

24 | {% else %} 25 |

Welcome to this site on your first visit!

26 |

This visit on {{newvisit}} UTC

27 |

Please Refresh the web page to proceed

28 | {% endif %} 29 | ''' 30 | flask.session['lastvisit'] = newvisit 31 | return flask.render_template_string( 32 | template, newvisit=newvisit, lastvisit=lastvisit) 33 | 34 | 35 | @app.after_request 36 | def set_lastvisit(response): 37 | now = datetime.datetime.now() 38 | flask.session['lastvisit'] = now.ctime() 39 | return response 40 | 41 | -------------------------------------------------------------------------------- /20_serving_http/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

FastAPI Demonstrator

5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
Name
Price
Description
Tax
13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /20_serving_http/models.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from pydantic import BaseModel, Field 3 | from mongoengine import Document, StringField, DecimalField 4 | from typing import Optional 5 | 6 | class PItem(BaseModel): 7 | "pydantic typed data class." 8 | name: str 9 | price: Decimal 10 | description: Optional[str] = None 11 | tax: Optional[Decimal] = None 12 | 13 | class MItem(Document): 14 | "mongoengine document." 15 | name = StringField(primary_key=True) 16 | price = DecimalField() 17 | description = StringField(required=False) 18 | tax = DecimalField(required=False) 19 | 20 | -------------------------------------------------------------------------------- /20_serving_http/server1.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | import uvicorn 3 | from fastapi import FastAPI 4 | from pydantic import BaseModel 5 | 6 | app = FastAPI() 7 | 8 | 9 | class Item(BaseModel): 10 | name: str 11 | price: float 12 | is_offer: Optional[bool] = None 13 | 14 | 15 | @app.get("/") 16 | def read_root(): 17 | return {"Hello": "World"} 18 | 19 | 20 | @app.get("/items/{item_id}") 21 | def read_item(item_id: int, q: Optional[str] = None): 22 | return {"item_id": item_id, "q": q} 23 | 24 | 25 | @app.put("/items/{item_id}") 26 | def update_item(item_id: int, item: Item): 27 | return {"item_name": item.name, "item_id": item_id} 28 | 29 | if __name__ == "__main__": 30 | uvicorn.run("server1:app", host="127.0.0.1", port=8000, reload=True) 31 | 32 | -------------------------------------------------------------------------------- /20_serving_http/server2.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from fastapi import FastAPI, Form 3 | from fastapi.responses import HTMLResponse, FileResponse 4 | from mongoengine import connect 5 | from mongoengine.errors import NotUniqueError 6 | from typing import Optional 7 | import json 8 | import uvicorn 9 | from models import PItem, MItem 10 | 11 | DATABASE_URI = "mongodb://localhost:27017" 12 | db=DATABASE_URI+"/mydatabase" 13 | connect(host=db) 14 | app = FastAPI() 15 | 16 | def save(item): 17 | try: 18 | return item.save(force_insert=True) 19 | except NotUniqueError: 20 | return None 21 | 22 | @app.get('/') 23 | def home_page(): 24 | "View function to display a simple form." 25 | return FileResponse("index.html") 26 | 27 | @app.post("/items/new/form/", response_class=HTMLResponse) 28 | def create_item_from_form(name: str=Form(...), 29 | price: Decimal=Form(...), 30 | description: Optional[str]=Form(""), 31 | tax: Optional[Decimal]=Form(Decimal("0.0"))): 32 | "View function to accept form data and create an item." 33 | mongoitem = MItem(name=name, price=price, description=description, tax=tax) 34 | value = save(mongoitem) 35 | if value: 36 | body = f"Item({name!r}, {price!r}, {description!r}, {tax!r})" 37 | else: 38 | body = f"Item {name!r} already present." 39 | return f"""

{body}

""" 40 | 41 | @app.post("/items/new/") 42 | def create_item_from_json(item: PItem): 43 | "View function to accept JSON data and create an item." 44 | mongoitem = MItem(**item.dict()) 45 | value = save(mongoitem) 46 | if not value: 47 | return f"Primary key {item.name!r} already present" 48 | return item.dict() 49 | 50 | @app.get("/items/{name}/") 51 | def retrieve_item(name: str): 52 | "View function to return the JSON contents of an item." 53 | m_item = MItem.objects(name=name).get() 54 | return json.loads(m_item.to_json()) 55 | 56 | if __name__ == "__main__": 57 | uvicorn.run("__main__:app", host="127.0.0.1", port=8000, reload=True) 58 | -------------------------------------------------------------------------------- /21_Email_MIME_and_Other_Network_Encodings/email_example.py: -------------------------------------------------------------------------------- 1 | import os, email 2 | 3 | 4 | def unpack_mail(mail_file, dest_dir): 5 | ''' Given file object mail_file, open for reading, and dest_dir, a 6 | string that is a path to an existing, writable directory, 7 | unpack each part of the mail message from mail_file to a 8 | file within dest_dir. 9 | ''' 10 | with mail_file: 11 | msg = email.message_from_file(mail_file) 12 | for part_number, part in enumerate(msg.walk()): 13 | if part.get_content_maintype() == 'multipart': 14 | # we get each specific part later in the loop, 15 | # so, nothing to do for the 'multipart' itself 16 | continue 17 | dest = part.get_filename() 18 | if dest is None: 19 | dest = part.get_param('name') 20 | if dest is None: 21 | dest = 'part-{}'.format(part_number) 22 | # In real life, make sure that dest is a reasonable filename 23 | # for your OS; otherwise, mangle that name until it is 24 | with open(os.path.join(dest_dir, dest), 'wb') as f: 25 | f.write(part.get_payload(decode=True)) 26 | 27 | 28 | def pack_mail(source_dir, **headers): 29 | ''' Given source_dir, a string that is a path to an existing, 30 | readable directory, and arbitrary header name/value pairs 31 | passed in as named arguments, packs all the files directly 32 | under source_dir (assumed to be plain text files) into a 33 | mail message returned as a MIME-formatted string. 34 | ''' 35 | 36 | msg = email.message.Message() 37 | for name, value in headers.items(): 38 | msg[name] = value 39 | msg['Content-type'] = 'multipart/mixed' 40 | filenames = next(os.walk(source_dir))[-1] 41 | for filename in filenames: 42 | m = email.message.Message() 43 | m.add_header('Content-type', 'text/plain', name=filename) 44 | with open(os.path.join(source_dir, filename), 'r') as f: 45 | m.set_payload(f.read()) 46 | msg.attach(m) 47 | return msg.as_string() 48 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_attribute_references_on_bs4_and_tag.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import bs4 3 | 4 | # contents, children, descendants 5 | 6 | >>> soup = bs4.BeautifulSoup('

Plain bold

') 7 | >>> list(t.name for t in soup.p.children) 8 | [None, 'b'] 9 | >>> list(t.name for t in soup.p.descendants) 10 | [None, 'b', None] 11 | 12 | # parent, parents 13 | 14 | >>> soup = bs4.BeautifulSoup('

Plain bold

') 15 | >>> soup.b.parent.name 16 | 'p' 17 | 18 | # next_sibling, previous_sibling, next_siblings, previous_siblings 19 | 20 | >>> soup = bs4.BeautifulSoup('

Plain bold

') 21 | >>> soup.b.previous_sibling, soup.b.next_sibling 22 | ('Plain ', None) 23 | 24 | # next_element, previous_element, next_elements, previous_elements 25 | 26 | >>> soup = bs4.BeautifulSoup('

Plain bold

') 27 | >>> soup.b.previous_element, soup.b.next_element 28 | ('Plain ', 'bold') 29 | """ 30 | 31 | if __name__ == '__main__': 32 | # use doctest to simulate console sessions 33 | import doctest 34 | doctest.testmod(verbose=True, exclude_empty=True) 35 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_building_html.py: -------------------------------------------------------------------------------- 1 | import bs4 2 | 3 | def mktable_with_bs4(seq_of_rows): 4 | tabsoup = bs4.BeautifulSoup('', 'html.parser') 5 | tab = tabsoup.table 6 | for row in seq_of_rows: 7 | tr = tabsoup.new_tag('tr') 8 | tab.append(tr) 9 | for item in row: 10 | td = tabsoup.new_tag('td') 11 | tr.append(td) 12 | td.string = str(item) 13 | return tab 14 | 15 | 16 | # Here is an example using the function we just defined: 17 | example = ( 18 | ('foo', 'g>h', 'g&h'), 19 | ('zip', 'zap', 'zop'), 20 | ) 21 | 22 | print(mktable_with_bs4(example)) 23 | # prints: 24 | #
foog>hg&h
zipzapzop
25 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_css_selectors.py: -------------------------------------------------------------------------------- 1 | import bs4 2 | 3 | def foo_child_of_bar(t): 4 | return t.name == 'foo' and t.parent and t.parent.name == 'bar' 5 | 6 | soup = bs4.BeautifulSoup('Plain bold') 7 | 8 | # return tags with name 'foo' children of tags with name 'bar' 9 | soup.find_all(foo_child_of_bar) 10 | 11 | # exactly equivalent, with no custom filter function needed 12 | soup.select('bar > foo') 13 | 14 | 15 | ### 16 | # code to execute the above search statements and print the results of each 17 | with open(__file__) as source: 18 | for line in source: 19 | line = line.rstrip() 20 | if line == "###": 21 | break 22 | 23 | print(line) 24 | if line.startswith("soup."): 25 | statement = line.partition("#")[0] 26 | exec(f"print({statement})", globals()) 27 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_editing_and_creating_html.py: -------------------------------------------------------------------------------- 1 | import bs4 2 | 3 | # Building and adding new nodes 4 | 5 | s = bs4.NavigableString(' some text ') 6 | 7 | soup = bs4.BeautifulSoup() 8 | t = soup.new_tag('foo', bar='baz') 9 | print(t) 10 | 11 | t.append(s) 12 | print(t) 13 | 14 | print(t.string.wrap(soup.new_tag('moo', zip='zaap'))) 15 | print(t) 16 | 17 | 18 | # Replacing and removing nodes 19 | 20 | soup = bs4.BeautifulSoup( 21 | '

first second third

') 22 | i = soup.i.replace_with('last') 23 | soup.b.append(i) 24 | print(soup) 25 | 26 | empty_i = soup.i.unwrap() 27 | print(soup.b.wrap(empty_i)) 28 | print(soup.body) 29 | 30 | soup.i.clear() 31 | print(soup) 32 | 33 | soup.p.decompose() 34 | print(soup) 35 | 36 | soup.body.decompose() 37 | print(soup) 38 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_getting_an_actual_string.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import bs4 3 | >>> soup = bs4.BeautifulSoup('

Plain bold

') 4 | >>> print(soup.p.string) 5 | None 6 | >>> print(soup.p.b.string) 7 | bold 8 | >>> print(soup.get_text()) 9 | Plain bold 10 | >>> print(soup.text) 11 | Plain bold 12 | >>> print(soup.get_text(strip=True)) 13 | Plainbold 14 | """ 15 | 16 | if __name__ == '__main__': 17 | # use doctest to simulate console sessions 18 | import doctest 19 | doctest.testmod(verbose=True, exclude_empty=True) 20 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_html_parsing_example.py: -------------------------------------------------------------------------------- 1 | import urllib.request, urllib.parse, bs4 2 | 3 | f = urllib.request.urlopen('http://www.python.org') 4 | b = bs4.BeautifulSoup(f) 5 | 6 | seen = set() 7 | for anchor in b('a'): 8 | url = anchor.get('href') 9 | if url is None or url in seen: 10 | continue 11 | seen.add(url) 12 | pieces = urllib.parse.urlparse(url) 13 | if pieces[0] == 'http': 14 | print(urllib.parse.urlunparse(pieces)) 15 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_indexing_instances_of_tag.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import bs4 3 | 4 | >>> s = bs4.BeautifulSoup('

baz') 5 | >>> s.get('foo') 6 | >>> s.p.get('foo') 7 | 'bar' 8 | >>> s.p.attrs 9 | {'foo': 'bar', 'class': ['ic']} 10 | 11 | """ 12 | 13 | if __name__ == '__main__': 14 | # use doctest to simulate console sessions 15 | import doctest 16 | doctest.testmod(verbose=True, exclude_empty=True) 17 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_search_methods.py: -------------------------------------------------------------------------------- 1 | import bs4 2 | import re 3 | 4 | """ 5 | For any Tag instance t and any group of positional and named arguments 6 | represented by ... the following equivalence always holds: 7 | 8 | just_one = t.find(...) 9 | other_way_list = t.find_all(..., limit=1) 10 | other_way = other_way_list[0] if other_way_list else None 11 | assert just_one == other_way 12 | """ 13 | 14 | soup = bs4.BeautifulSoup('''\ 15 | a B tag 16 | with inner abahh 17 | 18 | and bb 19 | 20 | 21 | foo foo other 22 | ''') 23 | 24 | def child_of_foo(tag): 25 | return tag.parent.name == 'foo' 26 | 27 | # search method arguments: name 28 | 29 | # return all instances of Tag 'b' in the document 30 | soup.find_all('b') # or soup.find_all(name='b') 31 | 32 | # return all instances of Tags 'b' and 'bah' in the document 33 | soup.find_all(['b', 'bah']) 34 | 35 | # return all instances of Tags starting with 'b' in the document 36 | soup.find_all(re.compile(r'^b')) 37 | 38 | # return all instances of Tags including string 'bah' in the document 39 | soup.find_all(re.compile(r'bah')) 40 | 41 | # return all instances of Tags whose parent's name is 'foo' 42 | soup.find_all(child_of_foo) 43 | 44 | 45 | # search method arguments: string 46 | 47 | # return all instances of NavigableString whose text is 'foo' 48 | soup.find_all(string='foo') 49 | 50 | # return all instances of Tag 'b' whose .string's text is 'foo' 51 | soup.find_all('b', string='foo') 52 | 53 | 54 | # search method arguments: attrs 55 | 56 | # return all instances of Tag 'b' w/an attribute 'foo' and no 'bar' 57 | soup.find_all('b', {'foo': True, 'bar': None}) 58 | 59 | 60 | ### 61 | # code to execute the above search statements and print the results of each 62 | with open(__file__) as source: 63 | for line in source: 64 | line = line.rstrip() 65 | if line == "###": 66 | break 67 | 68 | print(line) 69 | if line.startswith("soup."): 70 | statement = line.partition("#")[0] 71 | exec(f"print({statement})", globals()) 72 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_unicode_and_encoding.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import bs4 3 | 4 | >>> s = bs4.BeautifulSoup('

hello', 'html.parser') 5 | >>> print(s.prettify()) 6 |

7 | hello 8 |

9 | >>> print(s.decode()) 10 |

hello

11 | >>> print(s.encode()) 12 | b'

hello

' 13 | 14 | """ 15 | 16 | if __name__ == '__main__': 17 | # use doctest to simulate console sessions 18 | import doctest 19 | doctest.testmod(verbose=True, exclude_empty=True) 20 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/bs4_which_parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> import bs4 3 | >>> s = bs4.BeautifulSoup('

hello', 'html.parser') 4 | >>> # requires lxml be installed 5 | >>> sx = bs4.BeautifulSoup('

hello', 'xml') 6 | >>> sl = bs4.BeautifulSoup('

hello', 'lxml') 7 | >>> s5 = bs4.BeautifulSoup('

hello', 'html5lib') 8 | >>> print(s, s.is_xml) 9 |

hello

False 10 | >>> print(sx, sx.is_xml) 11 |

hello

True 12 | >>> print(sl, sl.is_xml) 13 |

hello

False 14 | >>> print(s5, s5.is_xml) 15 |

hello

False 16 | """ 17 | 18 | if __name__ == '__main__': 19 | # use doctest to simulate console sessions 20 | import doctest 21 | doctest.testmod(verbose=True, exclude_empty=True) 22 | -------------------------------------------------------------------------------- /22_Structured_Text_HTML/jinja2_building_html.py: -------------------------------------------------------------------------------- 1 | import jinja2 2 | 3 | TABLE_TEMPLATE = '''\ 4 | 5 | {% for s in s_of_s %} 6 | 7 | {% for item in s %} 8 | 9 | {% endfor %} 10 | 11 | {% endfor %} 12 |
{{item}}
''' 13 | 14 | def mktable_with_jinja2(s_of_s): 15 | env = jinja2.Environment( 16 | trim_blocks=True, 17 | lstrip_blocks=True, 18 | autoescape=True) 19 | t = env.from_string(TABLE_TEMPLATE) 20 | return t.render(s_of_s=s_of_s) 21 | 22 | example = ( 23 | ('foo', 'g>h', 'g&h'), 24 | ('zip', 'zap', 'zop'), 25 | ) 26 | print(mktable_with_jinja2(example)) 27 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/building_an_elementtree_from_scratch.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from xml.etree import ElementTree as et 3 | 4 | menu = et.Element('menu') 5 | tree = et.ElementTree(menu) 6 | with open('menu.csv') as f: 7 | r = csv.reader(f) 8 | for calories, namestr in r: 9 | food = et.SubElement(menu, 'food') 10 | cals = et.SubElement(food, 'calories') 11 | cals.text = calories 12 | name = et.SubElement(food, 'name') 13 | name.text = namestr 14 | 15 | tree.write('menu.xml') 16 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/menu.csv: -------------------------------------------------------------------------------- 1 | 600,French Toast 2 | 650,Belgian Waffles 3 | 900,Berry-Berry Belgian Waffles 4 | 900,Strawberry Belgian Waffles 5 | 950,Homestyle Breakfast 6 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/parsing_xml_iteratively_1.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from xml.etree import ElementTree as et 3 | 4 | 5 | def cals_and_name(): 6 | # generator for (calories, name) pairs 7 | for _, elem in et.iterparse('menu.xml'): 8 | if elem.tag != 'food': 9 | continue 10 | # just finished parsing a food, get calories and name 11 | cals = int(elem.find('calories').text) 12 | name = elem.find('name').text 13 | yield (cals, name) 14 | 15 | 16 | lowest10 = heapq.nsmallest(10, cals_and_name()) 17 | 18 | for cals, name in lowest10: 19 | print(cals, name) 20 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/parsing_xml_iteratively_2.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from xml.etree import ElementTree as et 3 | 4 | 5 | def cals_and_name(): 6 | # memory-thrifty generator for (calories, name) pairs 7 | root = None 8 | for event, elem in et.iterparse('menu.xml', ['start', 'end']): 9 | if event == 'start': 10 | if root is None: 11 | root = elem 12 | continue 13 | if elem.tag != 'food': 14 | continue 15 | # just finished parsing a food, get calories and name 16 | cals = int(elem.find('calories').text) 17 | name = elem.find('name').text 18 | yield (cals, name) 19 | root.remove(elem) 20 | 21 | 22 | lowest10 = heapq.nsmallest(10, cals_and_name()) 23 | 24 | for cals, name in lowest10: 25 | print(cals, name) 26 | 27 | 28 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/parsing_xml_with_elementtree_parse.py: -------------------------------------------------------------------------------- 1 | from urllib import request 2 | from xml.etree import ElementTree as et 3 | content = request.urlopen('http://www.w3schools.com/xml/simple.xml') 4 | tree = et.parse(content) 5 | 6 | 7 | def bycal_and_name(e): 8 | return int(e.find('calories').text), e.find('name').text 9 | for e in sorted(tree.findall('food'), key=bycal_and_name): 10 | print(f"{e.find('calories').text} {e.find('name').text}") 11 | 12 | print() 13 | 14 | # add Buttered Toast to the menu 15 | menu = tree.getroot() 16 | toast = et.SubElement(menu, 'food') 17 | tcals = et.SubElement(toast, 'calories') 18 | tcals.text = '180' 19 | tname = et.SubElement(toast, 'name') 20 | tname.text = 'Buttered Toast' 21 | # remove anything related to 'berry' from the menu 22 | for e in menu.findall('food'): 23 | name = e.find('name').text 24 | if 'berry' in name.lower(): 25 | menu.remove(e) 26 | 27 | for e in sorted(tree.findall('food'), key=bycal_and_name): 28 | print(f"{e.find('calories').text} {e.find('name').text}") 29 | 30 | -------------------------------------------------------------------------------- /23_Structured_Text_XML/simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Belgian Waffles 4 | 650 5 | 6 | 7 | Strawberry Belgian Waffles 8 | 900 9 | 10 | 11 | Berry-Berry Belgian Waffles 12 | 900 13 | 14 | 15 | French Toast 16 | 600 17 | 18 | 19 | Homestyle Breakfast 20 | 950 21 | 22 | 23 | -------------------------------------------------------------------------------- /24_Distributing_Extensions_and_Programs/flask_setup.py: -------------------------------------------------------------------------------- 1 | """__doc__ for long_description goes here; omitted. """ 2 | import re 3 | import ast 4 | 5 | from setuptools import setup 6 | 7 | _version_re = re.compile(r'__version__\s+=\s+(.*)') 8 | 9 | with open('flask/__init_.py', 'rb') as f: 10 | version = str(ast.literal_eval(_version_re.search( 11 | f.read().decode('utf-8')).group(1))) 12 | 13 | setup( 14 | name='Flask', 15 | version=version, 16 | url='http://github.com/pallets/flask/', 17 | license='BSD', 18 | author='Armin Ronacher', 19 | author_email='armin.ronacher@active-4.com', 20 | description='A microframework based on Werkzeug, Jinja2 ' 21 | 'and good intentions', 22 | long_description=__doc__, 23 | packages=['flask', 'flask.ext'], 24 | include_package_data=True, 25 | zip_safe=False, 26 | platforms='any', 27 | install_requires=[ 28 | 'Werkzeug>=0.7', 29 | 'Jinja2>=2.4', 30 | 'itsdangerous>=0.21', 31 | 'click>=2.0', 32 | ], 33 | classifiers=[ 34 | 'Development Status :: 4 - Beta', 35 | 'Environment :: Web Environment', 36 | 'Intended Audience :: Developers', 37 | 'License :: OSI Approved :: BSD License', 38 | 'Operating System :: OS Independent', 39 | 'Programming Language :: Python', 40 | 'Programming Language :: Python :: 2', 41 | 'Programming Language :: Python :: 2.6', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 3', 44 | 'Programming Language :: Python :: 3.3', 45 | 'Programming Language :: Python :: 3.4', 46 | 'Programming Language :: Python :: 3.5', 47 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 48 | 'Topic :: Software Development :: Libraries :: Python Modules' 49 | ], 50 | entry_points=''' 51 | [console_scripts] 52 | flask=flask.cli:main 53 | ''' 54 | ) 55 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/hello/hello.c: -------------------------------------------------------------------------------- 1 | #include 2 | static PyObject* 3 | hello(PyObject* self) 4 | { 5 | return Py_BuildValue("s", "Hello, Python extensions world!"); 6 | } 7 | static char hello_docs[] = 8 | "hello(): return a popular greeting phrase\n"; 9 | static PyMethodDef hello_funcs[] = { 10 | {"helloworld", (PyCFunction)hello, METH_NOARGS, hello_docs}, 11 | {NULL} 12 | }; 13 | static struct PyModuleDef hello_module = { 14 | PyModuleDef_HEAD_INIT, 15 | "hello", 16 | hello_docs, 17 | -1, 18 | hello_funcs 19 | }; 20 | 21 | PyMODINIT_FUNC 22 | PyInit_hello(void) 23 | { 24 | return PyModule_Create(&hello_module); 25 | } 26 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/hello/hello_demo.py: -------------------------------------------------------------------------------- 1 | import hello 2 | 3 | print(hello.helloworld()) 4 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/hello/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | setup(name='hello', 3 | ext_modules=[Extension('hello',sources=['hello.c'])]) 4 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/intpair/intpair_demo.py: -------------------------------------------------------------------------------- 1 | import intpair 2 | 3 | x = intpair.intpair(1.2, 3.4) 4 | print(x) 5 | print(x.first, x.second) 6 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/intpair/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | setup(name='intpair', 3 | ext_modules=[Extension('intpair',sources=['intpair.c'])]) 4 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/merge/merge.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static PyObject* 4 | merge(PyObject* self, PyObject* args, PyObject* kwds) 5 | { 6 | static char* argnames[] = {"x","y","override",NULL}; 7 | PyObject *x, *y; 8 | int override = 0; 9 | if(!PyArg_ParseTupleAndKeywords(args, kwds, "O!O|i", argnames, 10 | &PyDict_Type, &x, &y, &override)) 11 | return NULL; 12 | if(-1 == PyDict_Merge(x, y, override)) { 13 | if(!PyErr_ExceptionMatches(PyExc_AttributeError)) 14 | return NULL; 15 | PyErr_Clear(); 16 | if(-1 == PyDict_MergeFromSeq2(x, y, override)) 17 | return NULL; 18 | } 19 | return Py_BuildValue(""); 20 | } 21 | static char merge_docs[] = "\ 22 | merge(x,y,override=False): merge into dict x the items of dict y (or\n\ 23 | the pairs that are the items of y, if y is a sequence), with\n\ 24 | optional override. Alters dict x directly, returns None.\n\ 25 | "; 26 | static PyObject* 27 | mergenew(PyObject* self, PyObject* args, PyObject* kwds) 28 | { 29 | static char* argnames[] = {"x","y","override",NULL}; 30 | PyObject *x, *y, *result; 31 | int override = 0; 32 | if(!PyArg_ParseTupleAndKeywords(args, kwds, "O!O|i", argnames, 33 | &PyDict_Type, &x, &y, &override)) 34 | return NULL; 35 | result = PyObject_CallMethod(x, "copy", ""); 36 | if(! result) 37 | return NULL; 38 | if(-1 == PyDict_Merge(result, y, override)) { 39 | if(!PyErr_ExceptionMatches(PyExc_AttributeError)) 40 | return NULL; 41 | PyErr_Clear(); 42 | if(-1 == PyDict_MergeFromSeq2(result, y, override)) 43 | return NULL; 44 | } 45 | return result; 46 | } 47 | static char mergenew_docs[] = "\ 48 | mergenew(x,y,override=False): merge into dict x the items of dict y\n\ 49 | (or the pairs that are the items of y, if y is a sequence), with\n\ 50 | optional override. Does NOT alter x, but rather returns the\n\ 51 | modified copy as the function's result.\n\ 52 | "; 53 | static PyMethodDef merge_funcs[] = { 54 | {"merge", (PyCFunction)merge, METH_VARARGS | METH_KEYWORDS, merge_docs}, 55 | {"mergenew", (PyCFunction)mergenew, METH_VARARGS | METH_KEYWORDS, mergenew_docs}, 56 | {NULL} 57 | }; 58 | static char merge_module_docs[] = "Example extension module"; 59 | static struct PyModuleDef merge_module = { 60 | PyModuleDef_HEAD_INIT, 61 | "merge", 62 | merge_module_docs, 63 | -1, 64 | merge_funcs 65 | }; 66 | 67 | PyMODINIT_FUNC 68 | PyInit_merge(void) 69 | { 70 | 71 | return PyModule_Create(&merge_module); 72 | } 73 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/merge/merge_demo.py: -------------------------------------------------------------------------------- 1 | import merge 2 | x = {'a':1,'b':2 } 3 | merge.merge(x,[['b',3],['c',4]]) 4 | print(x) # prints: {'a':1, 'b':2, 'c':4 } 5 | print(merge.mergenew(x,{'a':5,'d':6},override=1)) # prints: {'a':5, 'b':2, 'c':4, 'd':6 } 6 | print(x) # prints: {'a':1, 'b':2, 'c':4 } 7 | -------------------------------------------------------------------------------- /25_Extending_and_Embedding_Classic_Python/merge/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, Extension 2 | setup(name='merge', 3 | ext_modules=[Extension('merge',sources=['merge.c'])]) 4 | -------------------------------------------------------------------------------- /chapters/24 Packaging Programs and Extensions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynutshell/pynut4/8692766c98aecf564bad3ad886daa803ab23d0e9/chapters/24 Packaging Programs and Extensions.pdf -------------------------------------------------------------------------------- /chapters/25 Extending and Embedding Classic Python.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynutshell/pynut4/8692766c98aecf564bad3ad886daa803ab23d0e9/chapters/25 Extending and Embedding Classic Python.pdf -------------------------------------------------------------------------------- /static/Pian_cover2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pynutshell/pynut4/8692766c98aecf564bad3ad886daa803ab23d0e9/static/Pian_cover2.jpg -------------------------------------------------------------------------------- /test/check_snippets.py: -------------------------------------------------------------------------------- 1 | # 2 | # check_snippets.py 3 | # 4 | 5 | from pathlib import Path 6 | import sys 7 | print(f"Python version: {sys.version}\n") 8 | 9 | test_dir = Path(__file__).parent 10 | project_dir = test_dir.parent 11 | snippet_files = project_dir.rglob("*.py") 12 | 13 | # do they compile? 14 | total_files = 0 15 | fail_snip_files = [] 16 | for snip_file in snippet_files: 17 | log_snip = str(snip_file.relative_to(project_dir)) 18 | total_files += 1 19 | try: 20 | compile(snip_file.read_text(encoding="UTF-8"), str(snip_file), "exec") 21 | except Exception as exc: 22 | print(f"Failed to compile {log_snip}") 23 | print(exc) 24 | fail_snip_files.append((snip_file, str(exc))) 25 | else: 26 | print(f"Successfully compiled {log_snip}") 27 | 28 | print() 29 | if fail_snip_files: 30 | print(f"{len(fail_snip_files)} of {total_files} files failed to compile:") 31 | for snip_file, exc_msg in fail_snip_files: 32 | print(f"- {snip_file}\n {exc_msg}\n") 33 | else: 34 | print(f"All {total_files} .py files compiled") 35 | 36 | sys.exit(0 if not fail_snip_files else 1) 37 | --------------------------------------------------------------------------------