├── .gitignore ├── .travis.yml ├── CH_01_getting_started ├── README.rst ├── T_00_poetry │ ├── poetry.lock │ └── pyproject.toml └── __init__.py ├── CH_02_interactive_python ├── README.rst ├── T_00_the_python_interpreter.rst ├── T_01_modifying_the_interpreter.rst ├── T_02_modifying_autocompletion.py ├── T_02_modifying_autocompletion.rst ├── T_03_bpython_rewind.rst ├── __init__.py ├── bpython_reload.py ├── conftest.py ├── reload.txt └── session_filename.ipy ├── CH_03_pythonic_syntax ├── README.rst ├── T_00_format_strings.rst ├── T_01_pep20.rst ├── T_02_beautiful_is_better_than_ugly.rst ├── T_03_explicit_is_better_than_implicit.rst ├── T_04_simple_is_better_than_complex.rst ├── T_05_flat_is_better_than_nested.rst ├── T_06_sparse_is_better_than_dense.rst ├── T_07_readability_counts.rst ├── T_08_practicality_beats_purity.rst ├── T_09_silent_errors.rst ├── T_10_ambiguity.rst ├── T_11_now_or_never.rst ├── T_12_namespaces.rst ├── T_13_duck_typing.rst ├── T_14_value_versus_identity.rst ├── T_15_loops.rst ├── T_16_mccabe.py ├── T_17_mypy.py ├── T_18_flake8.py ├── T_19_match_statement.rst ├── T_20_global_variables.rst ├── T_21_mutable_variables.rst ├── T_22_class_properties.rst ├── T_23_prettyprint_builtin.py ├── T_24_overwriting_built-ins.rst ├── T_25_modify_while_iterating.rst ├── T_26_catching_exceptions.rst ├── T_27_late_binding.rst ├── T_28_circular_imports_a.py ├── T_28_circular_imports_b.py ├── T_29_circular_imports_a.py ├── T_29_circular_imports_b.py ├── T_29_circular_imports_c.py ├── T_30_dynamic_imports.rst ├── __init__.py └── conftest.py ├── CH_04_design_patterns ├── README.rst ├── T_00_time_complexity_big_o.rst ├── T_01_list.rst ├── T_02_dict.rst ├── T_03_set.rst ├── T_04_tuple.rst ├── T_05_dataclasses.rst ├── T_06_chainmap.rst ├── T_07_defaultdict.rst ├── T_08_enum.rst ├── T_09_heapq.rst ├── T_10_bisect.rst ├── T_11_borg_and_singleton.rst ├── T_12_getters_and_setters.rst ├── T_13_dict_union.rst └── __init__.py ├── CH_05_functional_programming ├── README.rst ├── T_00_intro.rst ├── T_01_list_comprehensions.rst ├── T_02_dict_comprehensions.rst ├── T_03_set_comprehensions.rst ├── T_04_lambda_functions.rst ├── T_05_y_combinator.rst ├── T_06_partial.rst ├── T_07_factorial.rst ├── T_08_trees.rst ├── T_09_accumulate.rst ├── T_10_chain.rst ├── T_11_compress.rst ├── T_12_dropwhile_takewhile.rst ├── T_13_count.rst ├── T_14_groupby.rst └── __init__.py ├── CH_06_decorators ├── README.rst ├── T_00_decorating_functions.rst ├── T_01_generic_decorators.rst ├── T_02_functools_wraps.rst ├── T_03_chaining_decorators.rst ├── T_04_registering_decorators.rst ├── T_05_memoization.rst ├── T_06_optional_arguments.rst ├── T_07_decorators_using_classes.rst ├── T_08_decorating_class_functions.rst ├── T_09_classmethod_and_staticmethod.rst ├── T_10_properties.rst ├── T_11_singletons.rst ├── T_12_total_ordering.rst ├── T_13_single_dispatch.rst ├── T_14_contextmanager.rst ├── T_15_validation.rst ├── T_16_useless_warnings.rst ├── __init__.py └── tmp.py ├── CH_07_generators_and_coroutines ├── README.rst ├── T_00_simple_generator.rst ├── T_01_creating_infinite_generators.rst ├── T_02_generators_wrapping_iterables.rst ├── T_03_generator_comprehensions.rst ├── T_04_class_based_generators.rst ├── T_05_chunking_generator.rst ├── T_06_advantages_and_disadvantages.rst ├── T_07_islice.rst ├── T_08_chain.rst ├── T_09_tee.rst ├── T_11_context_managers.rst ├── T_12_coroutines.rst ├── T_13_priming_coroutine.rst ├── T_14_closing_and_exceptions.rst ├── T_15_bidirectional_pipelines.rst ├── T_16_coroutine_state.rst ├── __init__.py ├── conftest.py └── coroutine_decorator.py ├── CH_08_metaclasses ├── README.rst ├── T_00_dynamically_creating_classes.rst ├── T_01_basic_metaclass.rst ├── T_02_arguments_to_metaclasses.rst ├── T_03_accessing_metaclass_attributes.rst ├── T_04_abstract_base_classes.rst ├── T_05_custom_type_checks.rst ├── T_06_automatic_plugin_system.rst ├── T_07_plugins_on_demand.out ├── T_07_plugins_on_demand.py ├── T_08_plugins_through_configuration.out ├── T_08_plugins_through_configuration.py ├── T_09_plugins_through_filesystem.out ├── T_09_plugins_through_filesystem.py ├── T_10_dataclasses.py ├── T_10_dataclasses.rst ├── T_11_order_of_operations.rst ├── T_12_storing_attribute_definition_order.rst ├── __init__.py ├── conftest.py └── plugins │ ├── __init__.py │ ├── a.py │ ├── b.py │ └── base.py ├── CH_09_documentation ├── README.rst ├── T_00_type_hinting.rst ├── T_01_type_hinting.py ├── T_02_restructuredtext_syntax.rst ├── T_03_headers.rst ├── T_04_lists.rst ├── T_05_links.rst ├── T_06_images.rst ├── T_07_substitutions.rst ├── T_08_blocks.rst ├── T_09_toctree.rst ├── T_10_autodoc.rst ├── T_11_roles.rst ├── T_12_sphinx_style.py ├── T_13_google_style.py ├── T_14_numpy_style.py ├── __init__.py ├── apidoc_example │ ├── __init__.py │ ├── a.py │ └── b.py ├── docs │ ├── Makefile │ ├── apidoc_example.rst │ ├── conf.py │ ├── index.rst │ ├── make.bat │ └── modules.rst └── python.png ├── CH_10_testing_and_logging ├── README.rst ├── T_00_simple_doctest.py ├── T_01_simple_doctest.py ├── T_02_testing_with_documentation │ ├── Makefile │ ├── conf.py │ ├── index.rst │ ├── make.bat │ └── square.rst ├── T_03_doctest_true_for_1_flag.py ├── T_04_doctest_normalize_whitespace_flag.py ├── T_05_doctest_ellipsis_flag.py ├── T_06_testing_dictionaries.rst ├── T_07_testing_floating_point_numbers.rst ├── T_08_testing_times_and_durations.rst ├── T_09_test_cube.py ├── T_10_simplifying_assertions.py ├── T_11_representing_assertions.py ├── T_12_assert_representation.py ├── T_13_parameterizing_tests.py ├── T_14_automatic_arguments_using_fixtures.py ├── T_15_print_statements_and_logging.py ├── T_16_test_cube_root.py ├── T_17_test_cube_root_subzero.py ├── T_18_bad_code.py ├── T_19_pytest.ini ├── T_20_unittest_mock.py ├── T_21_pytest_monkeypatch.py ├── T_22_tox │ ├── pytest.ini │ ├── test.py │ └── tox.ini ├── T_23_logging_basic.py ├── T_24_logging_basic_formatted.py ├── T_25_logging_dictconfig.py ├── T_26_logging_json_config.json ├── T_26_logging_json_config.py ├── T_27_logging_ini_config.ini ├── T_27_logging_ini_config.py ├── T_28_logging_network_config.ini ├── T_28_logging_network_config.py ├── T_29_named_logger.py ├── T_30_formatting.py ├── T_31_exception.py ├── T_32_str_format.py ├── T_33_logging_format.py ├── T_34_logging_pitfalls.py ├── T_35_logging_pitfalls.py ├── T_36_logger_debugging.py ├── __init__.py ├── _coveragerc ├── conftest.py ├── cube.py ├── cube_root.py ├── index.rst └── square.py ├── CH_11_debugging ├── README.rst ├── T_00_debugging_code_printer.py ├── T_00_debugging_generator.rst ├── T_01_trace.py ├── T_02_selective_trace.out ├── T_02_selective_trace.py ├── T_03_filename_trace.out ├── T_03_filename_trace.py ├── T_04_logging.out ├── T_04_logging.py ├── T_05_logging_config.out ├── T_05_logging_config.py ├── T_06_stack.py ├── T_07_faulthandler.py ├── T_08_faulthandler_try_catch.py ├── T_09_faulthandler_enabled.py ├── T_10_console.py ├── T_11_pdb.py ├── T_12_pdb_loop.py ├── T_13_pdb_catching_exceptions.py ├── T_14_pdb_commands.py ├── T_15_ipython.py └── __init__.py ├── CH_12_performance ├── README.rst ├── T_00_timeit.py ├── T_01_timeit_main.py ├── T_02_custom_timeit.py ├── T_03_profile_fibonacci.py ├── T_04_profiler_calibration.py ├── T_05_profiler_large_bias.py ├── T_06_selective_profiling.py ├── T_07_profile_statistics.py ├── T_08_line_profiler.py ├── T_09_lists_versus_generators.rst ├── T_10_slots_performance.py ├── T_11_tracemalloc.py ├── T_12_memory_profiler.py ├── T_13_memory_leaks.py ├── T_14_garbage_collection.py ├── T_15_garbage_collector_viewing.py ├── T_16_weak_references.py ├── T_17_weakref_pitfalls.rst ├── T_18_freeing_memory.py ├── T_19_slots.py └── __init__.py ├── CH_13_async_io ├── README.rst ├── T_000X_benchmark.py ├── T_00_async_await.py ├── T_01_single_threaded_parallel_processing.rst ├── T_02_select_event_loop.rst ├── T_03_uvloop_policy.py ├── T_04_event_loop_usage.rst ├── T_05_executors.rst ├── T_06_process_executors.py ├── T_07_processes.rst ├── T_08_echo_server_and_client.rst ├── T_09_aiofiles.rst ├── T_10_async_for.rst ├── T_11_async_with.rst ├── T_12_constructors_and_destructors.rst ├── T_13_forgot_await.py ├── T_14_slow_blocking_code.py ├── T_15_forgotten_exception.py ├── T_16_early_exit.py ├── T_17_wait_for_exit.py ├── T_18_wait_for_all_tasks.py └── __init__.py ├── CH_14_multithreading_and_multiprocessing ├── README.rst ├── T_00_concurrent_futures.out ├── T_00_concurrent_futures.py ├── T_01_threading.out ├── T_01_threading.py ├── T_02_threading_class.out ├── T_02_threading_class.py ├── T_03_multiprocessing.out ├── T_03_multiprocessing.py ├── T_04_multiprocessing_class.out ├── T_04_multiprocessing_class.py ├── T_05_exiting_threads.py ├── T_06_exiting_processes.py ├── T_07_thread_batch_processing.py ├── T_08_process_batch_processing.py ├── T_09_multiprocessing_pool.py ├── T_10_sharing_memory.py ├── T_11_multiprocessing_variable_sharing_benchmark.py ├── T_12_thread_safety.py ├── T_13_thread_safety.py ├── T_14_deadlocks.py ├── T_15_thread_local.py ├── T_16_hyper_threading.py ├── T_17_remote_multiprocessing │ ├── client.py │ ├── constants.py │ ├── functions.py │ ├── server.py │ └── submitter.py ├── T_18_dask.py ├── T_19_dask_single.py ├── T_20_dask_distributed.py └── __init__.py ├── CH_15_scientific_python ├── T_00_numpy.rst ├── T_01_numba.rst ├── T_02_scipy.rst ├── T_03_pandas.rst ├── T_04_statsmodels.rst ├── T_05_xarray.rst ├── T_06_stumpy.rst ├── T_07_gmpy2.rst ├── T_08_sagemath.sage ├── T_09_mpmath.rst ├── T_10_sympy.rst ├── T_11_patsy.rst ├── T_12_matplotlib.ipynb ├── T_13_seaborn.ipynb ├── T_14_yellowbrick.ipynb ├── T_15_plotly.ipynb ├── T_16_bokeh.ipynb ├── T_17_datashader.ipynb ├── Untitled.ipynb ├── iris_splom.html └── python_versions.tsv ├── CH_16_machine_learning ├── .gitignore ├── T_00_scikit-image.ipynb ├── T_01_opencv.ipynb ├── T_02_nltk.rst ├── T_03_spacy_extract.rst ├── T_04_pytorch.ipynb ├── T_05_keras_tensorflow.py.ipynb ├── T_06_pygad.ipynb ├── T_07_scikit_lego.ipynb ├── T_08_tpot.ipynb ├── amsterdam-street.jpg ├── coco.names ├── python.png ├── requirements.txt ├── sklearn.ipynb ├── street.jpg └── yolov3.cfg ├── CH_17_c_and_cpp_extensions ├── README.rst ├── T_00_platform_specific_libraries.rst ├── T_01_complex_data_structures.rst ├── T_02_arrays.rst ├── T_03_memory_management.rst ├── T_04_cffi.rst ├── T_05_cffi_open_library.rst ├── T_06_cffi_complex_datastructures.rst ├── T_07_cffi_arrays.rst ├── T_08_cffi_api.rst ├── T_09_native │ ├── __init__.py │ ├── conftest.py │ ├── plain_c.c │ ├── setup.py │ ├── sum_of_squares.c │ ├── sum_of_squares_py.py │ └── test.py ├── T_10_size_matters │ ├── __init__.py │ ├── plain_c.c │ ├── setup.py │ ├── sum_of_large_squares.c │ ├── sum_of_squares_py.py │ └── test.py ├── T_11_parsing_arguments.c ├── T_12_silent_or_lethal_errors.c ├── T_13_calling_from_c.c ├── __init__.py ├── _libc.py └── conftest.py ├── CH_18_packaging ├── README.rst ├── T_00_basic_pyproject │ ├── README.rst │ ├── __init__.py │ ├── conftest.py │ ├── poetry.lock │ ├── pyproject.toml │ ├── some_directory │ │ └── __init__.py │ ├── t_00_basic_pyproject │ │ └── __init__.py │ └── tests │ │ ├── __init__.py │ │ └── test_t_00_basic_pyproject.py ├── T_01_pyproject_extensions │ ├── README.rst │ ├── __init__.py │ ├── build_extension.py │ ├── conftest.py │ ├── pyproject.toml │ ├── sum_of_squares.c │ ├── t_01_pyproject_extensions │ │ └── __init__.py │ └── tests │ │ ├── __init__.py │ │ └── test_t_01_pyproject_extensions.py ├── T_02_basic_setup_py │ ├── MANIFEST.in │ ├── README.rst │ ├── T_02_basic_setup │ │ └── __init__.py │ ├── __init__.py │ ├── entry_points.rst │ ├── pyproject.toml │ └── setup.py ├── T_03_basic_setup_cfg │ ├── __init__.py │ ├── setup.cfg │ └── setup.py ├── T_04_C_extensions │ ├── __init__.py │ ├── setup.py │ └── sum_of_squares.c ├── T_05_cython │ ├── T_05_cython │ │ ├── .gitignore │ │ ├── __init__.py │ │ └── sum_of_squares.pyx │ ├── __init__.py │ ├── conftest.py │ ├── pyximport_example.rst │ └── setup.py └── __init__.py ├── LICENSE ├── README.rst ├── conftest.py ├── doctest_to_python.py ├── libraries ├── T_08_counter.rst ├── T_09_deque.rst ├── T_11_combinations.rst ├── T_11_namedtuple.rst ├── T_12_permutations.rst └── T_13_ordereddict.rst ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── requirements.txt ├── test_output.py └── tox.ini /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | sudo: false 4 | language: python 5 | 6 | env: 7 | global: 8 | - PIP_WHEEL_DIR=$HOME/.wheels 9 | - PIP_FIND_LINKS=file://$PIP_WHEEL_DIR 10 | 11 | matrix: 12 | include: 13 | - python: '3.8' 14 | env: TOXENV=py38 15 | 16 | cache: 17 | directories: 18 | - $HOME/.wheels 19 | 20 | # command to install dependencies, e.g. pip install -r requirements.txt 21 | install: 22 | - mkdir -p $PIP_WHEEL_DIR 23 | - pip wheel -r requirements.txt 24 | - pip install tox 25 | 26 | script: 27 | - tox 28 | 29 | notifications: 30 | email: 31 | on_success: never 32 | on_failure: change 33 | 34 | -------------------------------------------------------------------------------- /CH_01_getting_started/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 1, Getting started 2 | ############################################################################## 3 | 4 | | One Environment per Project introduces virtual Python environments using 5 | | virtualenv or venv to isolate the packages in your Python projects. 6 | -------------------------------------------------------------------------------- /CH_01_getting_started/T_00_poetry/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "t_00_poetry" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Rick van Hattem "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | progressbar2 = "^3.1.0" 10 | 11 | [tool.poetry.dev-dependencies] 12 | 13 | [build-system] 14 | requires = ["poetry-core>=1.0.0"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /CH_01_getting_started/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_01_getting_started/__init__.py -------------------------------------------------------------------------------- /CH_02_interactive_python/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 2, Interactive Python Shells 2 | ############################################################################## 3 | 4 | -------------------------------------------------------------------------------- /CH_02_interactive_python/T_00_the_python_interpreter.rst: -------------------------------------------------------------------------------- 1 | >>> 'Hello world!' 2 | 'Hello world!' 3 | -------------------------------------------------------------------------------- /CH_02_interactive_python/T_01_modifying_the_interpreter.rst: -------------------------------------------------------------------------------- 1 | # Adding default imports 2 | 3 | >>> from pprint import pprint as pp 4 | >>> from pprint import pformat as pf 5 | 6 | >>> pp(dict(spam=0xA, eggs=0xB)) 7 | {'eggs': 11, 'spam': 10} 8 | >>> pf(dict(spam=0xA, eggs=0xB)) 9 | "{'eggs': 11, 'spam': 10}" 10 | 11 | 12 | # Modifying prompt 13 | 14 | >>> if True: 15 | ... print('Hello!') 16 | Hello! 17 | 18 | >>> import sys 19 | 20 | >>> sys.ps1 = '> ' 21 | >>> sys.ps2 = '. ' 22 | 23 | # With modified prompt 24 | 25 | > if True: 26 | . print('Hello!') 27 | Hello! 28 | -------------------------------------------------------------------------------- /CH_02_interactive_python/T_02_modifying_autocompletion.rst: -------------------------------------------------------------------------------- 1 | >>> sandwich = dict(spam=2, eggs=1, sausage=1) 2 | >>> sandwich. # doctest: +SKIP 3 | sandwich.clear( sandwich.fromkeys( sandwich.items( sandwich.pop( 4 | sandwich.setdefault( sandwich.values( sandwich.copy( sandwich.get( 5 | sandwich.keys( sandwich.popitem( sandwich.update( 6 | >>> sandwich[ # doctest: +SKIP 7 | 8 | 9 | >>> sandwich = dict(spam=2, eggs=1, sausage=1) 10 | >>> sandwich[' # doctest: +SKIP 11 | sandwich['eggs'] sandwich['sausage'] sandwich['spam'] 12 | 13 | >>> import T_02_modifying_autocompletion 14 | 15 | >>> autocompletion = T_02_modifying_autocompletion 16 | -------------------------------------------------------------------------------- /CH_02_interactive_python/T_03_bpython_rewind.rst: -------------------------------------------------------------------------------- 1 | Note: missing_ok was added in Python 3.8 2 | 3 | >>> import pathlib 4 | 5 | >>> pathlib.Path('bpython.txt').unlink(missing_ok=True) 6 | 7 | >>> with open('bpython.txt', 'a') as fh: 8 | ... fh.write('x') 9 | ... 10 | 1 11 | >>> with open('bpython.txt') as fh: 12 | ... print(fh.read()) 13 | ... 14 | x 15 | >>> sandwich = dict(spam=2, eggs=1, sausage=1) 16 | 17 | >>> with open('bpython.txt', 'a') as fh: 18 | ... fh.write('x') 19 | ... 20 | 1 21 | >>> with open('bpython.txt') as fh: 22 | ... print(fh.read()) 23 | ... 24 | xx 25 | >>> 26 | -------------------------------------------------------------------------------- /CH_02_interactive_python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_02_interactive_python/__init__.py -------------------------------------------------------------------------------- /CH_02_interactive_python/bpython_reload.py: -------------------------------------------------------------------------------- 1 | with open('reload.txt', 'a+') as fh: 2 | fh.write('x') 3 | fh.seek(0) 4 | print(fh.read()) 5 | -------------------------------------------------------------------------------- /CH_02_interactive_python/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_02_interactive_python/reload.txt: -------------------------------------------------------------------------------- 1 | xxxxx -------------------------------------------------------------------------------- /CH_02_interactive_python/session_filename.ipy: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | sandwich = dict(spam=2, eggs=1, sausage=1) 4 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 2, Pythonic Syntax 2 | ############################################################################## 3 | 4 | | Common Pitfalls and Style Guide explains what Pythonic code is and how to write code that is Pythonic and adheres to the Python philosophy. 5 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_01_pep20.rst: -------------------------------------------------------------------------------- 1 | >>> import this 2 | The Zen of Python, by Tim Peters 3 | 4 | Beautiful is better than ugly. 5 | Explicit is better than implicit. 6 | Simple is better than complex. 7 | Complex is better than complicated. 8 | Flat is better than nested. 9 | Sparse is better than dense. 10 | Readability counts. 11 | Special cases aren't special enough to break the rules. 12 | Although practicality beats purity. 13 | Errors should never pass silently. 14 | Unless explicitly silenced. 15 | In the face of ambiguity, refuse the temptation to guess. 16 | There should be one-- and preferably only one --obvious way to do it. 17 | Although that way may not be obvious at first unless you're Dutch. 18 | Now is better than never. 19 | Although never is often better than *right* now. 20 | If the implementation is hard to explain, it's a bad idea. 21 | If the implementation is easy to explain, it may be a good idea. 22 | Namespaces are one honking great idea -- let's do more of those! 23 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_02_beautiful_is_better_than_ugly.rst: -------------------------------------------------------------------------------- 1 | >>> filter_modulo = lambda i, m: (i[j] for j in \ 2 | ... range(len(i)) if i[j] % m) 3 | >>> list(filter_modulo(range(10), 2)) 4 | [1, 3, 5, 7, 9] 5 | 6 | 7 | >>> def filter_modulo(items, modulo): 8 | ... for item in items: 9 | ... if item % modulo: 10 | ... yield item 11 | ... 12 | 13 | >>> list(filter_modulo(range(10), 2)) 14 | [1, 3, 5, 7, 9] 15 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_03_explicit_is_better_than_implicit.rst: -------------------------------------------------------------------------------- 1 | >>> from os import * 2 | >>> from asyncio import * 3 | 4 | >>> assert wait 5 | 6 | 7 | 8 | >>> from os import path 9 | >>> from asyncio import wait 10 | 11 | >>> assert wait 12 | 13 | 14 | 15 | >>> import os 16 | >>> import asyncio 17 | 18 | >>> assert asyncio.wait 19 | >>> assert os.path 20 | 21 | 22 | >>> import concurrent.futures 23 | 24 | >>> assert concurrent.futures.wait 25 | 26 | 27 | 28 | >>> def spam(eggs, *args, **kwargs): 29 | ... for arg in args: 30 | ... eggs += arg 31 | ... for extra_egg in kwargs.get('extra_eggs', []): 32 | ... eggs += extra_egg 33 | ... return eggs 34 | 35 | >>> spam(1, 2, 3, extra_eggs=[4, 5]) 36 | 15 37 | 38 | 39 | >>> def sum_ints(*args): 40 | ... total = 0 41 | ... for arg in args: 42 | ... total += arg 43 | ... return total 44 | 45 | >>> sum_ints(1, 2, 3, 4, 5) 46 | 15 47 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_05_flat_is_better_than_nested.rst: -------------------------------------------------------------------------------- 1 | >>> def between_and_modulo(value, a, b, modulo): 2 | ... if value >= a: 3 | ... if value <= b: 4 | ... if value % modulo: 5 | ... return True 6 | ... return False 7 | 8 | >>> for i in range(10): 9 | ... if between_and_modulo(i, 2, 9, 2): 10 | ... print(i, end=' ') 11 | 3 5 7 9 12 | 13 | 14 | >>> def between_and_modulo(value, a, b, modulo): 15 | ... if value < a: 16 | ... return False 17 | ... elif value > b: 18 | ... return False 19 | ... elif not value % modulo: 20 | ... return False 21 | ... else: 22 | ... return True 23 | 24 | >>> for i in range(10): 25 | ... if between_and_modulo(i, 2, 9, 2): 26 | ... print(i, end=' ') 27 | 3 5 7 9 28 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_06_sparse_is_better_than_dense.rst: -------------------------------------------------------------------------------- 1 | >>> f=lambda x:0**x or x*f(x-1) 2 | >>> f(40) 3 | 815915283247897734345611269596115894272000000000 4 | 5 | >>> def factorial(x): 6 | ... if 0 ** x: 7 | ... return 1 8 | ... else: 9 | ... return x * factorial(x - 1) 10 | >>> factorial(40) 11 | 815915283247897734345611269596115894272000000000 12 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_07_readability_counts.rst: -------------------------------------------------------------------------------- 1 | >>> from functools import reduce 2 | 3 | >>> fib=lambda n:n if n<2 else fib(n-1)+fib(n-2) 4 | >>> fib(10) 5 | 55 6 | 7 | >>> fib=lambda n:reduce(lambda x,y:(x[0]+x[1],x[0]),[(1,1)]*(n-1))[0] 8 | >>> fib(10) 9 | 55 10 | 11 | 12 | >>> def fib(n): 13 | ... if n < 2: 14 | ... return n 15 | ... else: 16 | ... return fib(n - 1) + fib(n - 2) 17 | 18 | >>> fib(10) 19 | 55 20 | 21 | 22 | >>> def fib(n): 23 | ... a = 0 24 | ... b = 1 25 | ... for _ in range(n): 26 | ... a, b = b, a + b 27 | ... 28 | ... return a 29 | 30 | >>> fib(10) 31 | 55 32 | 33 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_08_practicality_beats_purity.rst: -------------------------------------------------------------------------------- 1 | >>> from concurrent.futures import ProcessPoolExecutor, \ 2 | ... CancelledError, TimeoutError 3 | 4 | >>> from concurrent.futures import ( 5 | ... ProcessPoolExecutor, CancelledError, TimeoutError) 6 | 7 | >>> from concurrent import futures 8 | 9 | >>> from concurrent.futures.process import ( 10 | ... ProcessPoolExecutor 11 | ... ) 12 | 13 | >>> from concurrent.futures import ( 14 | ... ProcessPoolExecutor, 15 | ... CancelledError, 16 | ... TimeoutError, 17 | ... ) 18 | 19 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_10_ambiguity.rst: -------------------------------------------------------------------------------- 1 | >>> fh_a = open('spam', 'w', -1, None, None, '\n') 2 | >>> fh_b = open(file='spam', mode='w', buffering=-1, newline='\n') 3 | 4 | >>> filename = 'spam' 5 | >>> mode = 'w' 6 | >>> buffers = -1 7 | >>> fh_b = open(filename, mode, buffers, newline='\n') 8 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_11_now_or_never.rst: -------------------------------------------------------------------------------- 1 | >>> import warnings 2 | 3 | >>> warnings.warn('Something deprecated', DeprecationWarning) 4 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_12_namespaces.rst: -------------------------------------------------------------------------------- 1 | >>> from json import loads 2 | 3 | >>> loads('{}') 4 | {} 5 | 6 | >>> import json 7 | 8 | >>> json.loads('{}') 9 | {} 10 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_13_duck_typing.rst: -------------------------------------------------------------------------------- 1 | >>> timestamp = 12345 2 | >>> filename = f'{timestamp}.csv' 3 | 4 | >>> import datetime 5 | 6 | >>> timestamp = 12345 7 | 8 | >>> if isinstance(timestamp, datetime.datetime): 9 | ... filename = f'{timestamp}.csv' 10 | ... else: 11 | ... raise TypeError(f'{timestamp} is not a valid datetime') 12 | Traceback (most recent call last): 13 | ... 14 | TypeError: 12345 is not a valid datetime 15 | 16 | 17 | 18 | >>> import datetime 19 | 20 | >>> timestamp = datetime.date(2000, 10, 5) 21 | >>> filename = f'{timestamp}.csv' 22 | >>> print(f'Filename from date: {filename}') 23 | Filename from date: 2000-10-05.csv 24 | 25 | >>> timestamp = '2000-10-05' 26 | >>> filename = f'{timestamp}.csv' 27 | >>> print(f'Filename from str: {filename}') 28 | Filename from str: 2000-10-05.csv 29 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_14_value_versus_identity.rst: -------------------------------------------------------------------------------- 1 | >>> a = 1 2 | >>> a == True 3 | True 4 | >>> a is True 5 | False 6 | 7 | >>> b = 0 8 | >>> b == False 9 | True 10 | >>> b is False 11 | False 12 | 13 | >>> def some_unsafe_function(arg=None): 14 | ... if not arg: 15 | ... arg = 123 16 | ... 17 | ... return arg 18 | 19 | >>> some_unsafe_function(0) 20 | 123 21 | >>> some_unsafe_function(None) 22 | 123 23 | 24 | 25 | >>> def some_safe_function(arg=None): 26 | ... if arg is None: 27 | ... arg = 123 28 | ... 29 | ... return arg 30 | 31 | >>> some_safe_function(0) 32 | 0 33 | >>> some_safe_function(None) 34 | 123 35 | 36 | 37 | >>> a = 200 + 56 38 | >>> b = 256 39 | >>> c = 200 + 57 40 | >>> d = 257 41 | 42 | >>> a == b 43 | True 44 | >>> a is b 45 | True 46 | >>> c == d 47 | True 48 | >>> c is d 49 | False 50 | 51 | 52 | >>> spam = list(range(1000000)) 53 | >>> eggs = list(range(1000000)) 54 | 55 | >>> spam == eggs 56 | True 57 | >>> spam is eggs 58 | False 59 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_15_loops.rst: -------------------------------------------------------------------------------- 1 | >>> my_range = range(5) 2 | >>> i = 0 3 | >>> while i < len(my_range): 4 | ... item = my_range[i] 5 | ... print(i, item, end=', ') 6 | ... i += 1 7 | 0 0, 1 1, 2 2, 3 3, 4 4, 8 | 9 | 10 | >>> my_range = range(5) 11 | >>> for item in my_range: 12 | ... print(item, end=', ') 13 | 0, 1, 2, 3, 4, 14 | 15 | >>> for i, item in enumerate(my_range): 16 | ... print(i, item, end=', ') 17 | 0 0, 1 1, 2 2, 3 3, 4 4, 18 | 19 | >>> my_range = range(5) 20 | >>> [(i, item) for i, item in enumerate(my_range)] 21 | [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)] 22 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_16_mccabe.py: -------------------------------------------------------------------------------- 1 | def noop(): 2 | pass 3 | 4 | 5 | def yield_cube_points(matrix): 6 | for x in matrix: 7 | for y in x: 8 | for z in y: 9 | yield (x, y, z) 10 | 11 | 12 | def print_cube(matrix): 13 | for x in matrix: 14 | for y in x: 15 | for z in y: 16 | print(z, end='') 17 | print() 18 | print() 19 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_17_mypy.py: -------------------------------------------------------------------------------- 1 | some_number: int 2 | some_number = 'test' 3 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_18_flake8.py: -------------------------------------------------------------------------------- 1 | def spam(a,b,c):print(a,b+c) 2 | def eggs():pass 3 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_20_global_variables.rst: -------------------------------------------------------------------------------- 1 | >>> g = 1 2 | >>> def print_global(): 3 | ... print(f'Value: {g}') 4 | 5 | >>> print_global() 6 | Value: 1 7 | 8 | >>> g = 1 9 | 10 | >>> def print_global(): 11 | ... g += 1 12 | ... print(f'Value: {g}') 13 | 14 | >>> print_global() 15 | Traceback (most recent call last): 16 | ... 17 | UnboundLocalError: local variable 'g' referenced before assignment 18 | 19 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_21_mutable_variables.rst: -------------------------------------------------------------------------------- 1 | >>> import copy 2 | 3 | >>> x = [[1], [2, 3]] 4 | >>> y = x.copy() 5 | >>> z = copy.deepcopy(x) 6 | 7 | >>> x.append('a') 8 | >>> x[0].append(x) 9 | 10 | >>> x 11 | [[1, [...]], [2, 3], 'a'] 12 | >>> y 13 | [[1, [...]], [2, 3]] 14 | >>> z 15 | [[1], [2, 3]] 16 | 17 | 18 | >>> def append(list_=[], value='value'): 19 | ... list_.append(value) 20 | ... return list_ 21 | 22 | >>> append(value='a') 23 | ['a'] 24 | >>> append(value='b') 25 | ['a', 'b'] 26 | 27 | 28 | >>> def append(list_=None, value='value'): 29 | ... if list_ is None: 30 | ... list_ = [] 31 | ... list_.append(value) 32 | ... return list_ 33 | 34 | >>> append(value='a') 35 | ['a'] 36 | >>> append(value='b') 37 | ['b'] 38 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_22_class_properties.rst: -------------------------------------------------------------------------------- 1 | >>> class SomeClass: 2 | ... class_list = [] 3 | ... 4 | ... def __init__(self): 5 | ... self.instance_list = [] 6 | 7 | >>> SomeClass.class_list.append('from class') 8 | >>> instance = SomeClass() 9 | >>> instance.class_list.append('from instance') 10 | >>> instance.instance_list.append('from instance') 11 | 12 | >>> SomeClass.class_list 13 | ['from class', 'from instance'] 14 | >>> SomeClass.instance_list 15 | Traceback (most recent call last): 16 | ... 17 | AttributeError: ... 'SomeClass' has no attribute 'instance_list' 18 | 19 | >>> instance.class_list 20 | ['from class', 'from instance'] 21 | >>> instance.instance_list 22 | ['from instance'] 23 | 24 | 25 | >>> class Parent: 26 | ... pass 27 | 28 | 29 | >>> class Child(Parent): 30 | ... pass 31 | 32 | 33 | >>> Parent.parent_property = 'parent' 34 | >>> Child.parent_property 35 | 'parent' 36 | 37 | >>> Child.parent_property = 'child' 38 | >>> Parent.parent_property 39 | 'parent' 40 | >>> Child.parent_property 41 | 'child' 42 | 43 | >>> Child.child_property = 'child' 44 | >>> Parent.child_property 45 | Traceback (most recent call last): 46 | ... 47 | AttributeError: ... 'Parent' has no attribute 'child_property' 48 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_23_prettyprint_builtin.py: -------------------------------------------------------------------------------- 1 | import builtins 2 | import inspect 3 | import pprint 4 | import re 5 | 6 | 7 | def pp(*args, **kwargs): 8 | '''PrettyPrint function that prints the variable name when 9 | available and pprints the data 10 | 11 | >>> x = 10 12 | >>> pp(x) 13 | # x: 10 14 | ''' 15 | # Fetch the current frame from the stack 16 | frame = inspect.currentframe().f_back 17 | # Prepare the frame info 18 | frame_info = inspect.getframeinfo(frame) 19 | 20 | # Walk through the lines of the function 21 | for line in frame_info[3]: 22 | # Search for the pp() function call with a fancy regexp 23 | m = re.search(r'\bpp\s*\(\s*([^)]*)\s*\)', line) 24 | if m: 25 | print('# %s:' % m.group(1), end=' ') 26 | break 27 | 28 | pprint.pprint(*args, **kwargs) 29 | 30 | 31 | builtins.pf = pprint.pformat 32 | builtins.pp = pp 33 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_24_overwriting_built-ins.rst: -------------------------------------------------------------------------------- 1 | >>> list = list((1, 2, 3)) 2 | >>> list 3 | [1, 2, 3] 4 | 5 | >>> list((4, 5, 6)) 6 | Traceback (most recent call last): 7 | ... 8 | TypeError: 'list' object is not callable 9 | 10 | >>> import = 'Some import' 11 | Traceback (most recent call last): 12 | ... 13 | SyntaxError: invalid syntax 14 | 15 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_25_modify_while_iterating.rst: -------------------------------------------------------------------------------- 1 | >>> dict_ = dict(a=123) 2 | >>> set_ = set((456,)) 3 | 4 | >>> for key in dict_: 5 | ... del dict_[key] 6 | ... 7 | Traceback (most recent call last): 8 | File "", line 1, in 9 | RuntimeError: dictionary changed size during iteration 10 | 11 | >>> for item in set_: 12 | ... set_.remove(item) 13 | ... 14 | Traceback (most recent call last): 15 | File "", line 1, in 16 | RuntimeError: Set changed size during iteration 17 | 18 | 19 | >>> list_ = list(range(10)) 20 | >>> list_ 21 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 22 | 23 | >>> for item in list_: 24 | ... print(list_.pop(0), end=', ') 25 | 0, 1, 2, 3, 4, 26 | 27 | >>> list_ 28 | [5, 6, 7, 8, 9] 29 | 30 | 31 | >>> list_ = list(range(10)) 32 | 33 | >>> for item in list(list_): 34 | ... print(list_.pop(0), end=', ') 35 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 36 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_26_catching_exceptions.rst: -------------------------------------------------------------------------------- 1 | >>> exception = None 2 | 3 | >>> try: 4 | ... 1 / 0 5 | ... except ZeroDivisionError as exception: 6 | ... pass 7 | 8 | >>> exception 9 | Traceback (most recent call last): 10 | ... 11 | NameError: name 'exception' is not defined 12 | 13 | 14 | >>> try: 15 | ... 1 / 0 16 | ... except ZeroDivisionError as exception: 17 | ... new_exception = exception 18 | 19 | >>> new_exception 20 | ZeroDivisionError('division by zero') 21 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_27_late_binding.rst: -------------------------------------------------------------------------------- 1 | >>> functions = [lambda: i for i in range(3)] 2 | 3 | >>> for function in functions: 4 | ... print(function(), end=', ') 5 | 2, 2, 2, 6 | 7 | 8 | >>> from functools import partial 9 | 10 | >>> functions = [partial(lambda x: x, i) for i in range(3)] 11 | 12 | >>> for function in functions: 13 | ... print(function(), end=', ') 14 | 0, 1, 2, 15 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_28_circular_imports_a.py: -------------------------------------------------------------------------------- 1 | import T_28_circular_imports_b 2 | 3 | 4 | class FileA: 5 | pass 6 | 7 | 8 | class FileC(T_28_circular_imports_b.FileB): 9 | pass 10 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_28_circular_imports_b.py: -------------------------------------------------------------------------------- 1 | import T_28_circular_imports_a 2 | 3 | 4 | class FileB(T_28_circular_imports_a.FileA): 5 | pass 6 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_29_circular_imports_a.py: -------------------------------------------------------------------------------- 1 | class FileA: 2 | pass 3 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_29_circular_imports_b.py: -------------------------------------------------------------------------------- 1 | import T_29_circular_imports_a 2 | 3 | 4 | class FileB(T_29_circular_imports_a.FileA): 5 | pass 6 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_29_circular_imports_c.py: -------------------------------------------------------------------------------- 1 | import T_29_circular_imports_b 2 | 3 | 4 | class FileC(T_29_circular_imports_b.FileB): 5 | pass 6 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/T_30_dynamic_imports.rst: -------------------------------------------------------------------------------- 1 | >>> import importlib 2 | 3 | >>> module_name = 'sys' 4 | >>> attribute = 'version_info' 5 | 6 | >>> module = importlib.import_module(module_name) 7 | >>> module 8 | 9 | >>> getattr(module, attribute).major 10 | 3 11 | -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_03_pythonic_syntax/__init__.py -------------------------------------------------------------------------------- /CH_03_pythonic_syntax/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_04_design_patterns/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 3, Containers and Collections 2 | ------------------------------------------------------------------------------ 3 | 4 | | Storing Data the Right Way using the many containers and collections bundled with Python to create code that is fast and readable. 5 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_00_time_complexity_big_o.rst: -------------------------------------------------------------------------------- 1 | >>> n = 1000 2 | >>> a = list(range(n)) 3 | >>> b = dict.fromkeys(range(n)) 4 | >>> for i in range(100): 5 | ... assert i in a # takes n=1000 steps 6 | ... assert i in b # takes 1 step 7 | 8 | 9 | >>> def o_one(items): 10 | ... return 1 # 1 operation so O(1) 11 | 12 | >>> def o_n(items): 13 | ... total = 0 14 | ... # Walks through all items once so O(n) 15 | ... for item in items: 16 | ... total += item 17 | ... return total 18 | 19 | >>> def o_n_squared(items): 20 | ... total = 0 21 | ... # Walks through all items n*n times so O(n**2) 22 | ... for a in items: 23 | ... for b in items: 24 | ... total += a * b 25 | ... return total 26 | 27 | >>> n = 10 28 | >>> items = range(n) 29 | >>> o_one(items) # 1 operation 30 | 1 31 | >>> o_n(items) # n = 10 operations 32 | 45 33 | >>> o_n_squared(items) # n*n = 10*10 = 100 operations 34 | 2025 35 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_02_dict.rst: -------------------------------------------------------------------------------- 1 | >>> def most_significant(value): 2 | ... while value >= 10: 3 | ... value //= 10 4 | ... return value 5 | 6 | >>> most_significant(12345) 7 | 1 8 | >>> most_significant(99) 9 | 9 10 | >>> most_significant(0) 11 | 0 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | >>> def add(collection, key, value): 16 | ... index = most_significant(key) 17 | ... collection[index].append((key, value)) 18 | 19 | >>> def contains(collection, key): 20 | ... index = most_significant(key) 21 | ... for k, v in collection[index]: 22 | ... if k == key: 23 | ... return True 24 | ... return False 25 | 26 | # Create the collection of 10 lists 27 | 28 | >>> collection = [[], [], [], [], [], [], [], [], [], []] 29 | 30 | # Add some items, using key/value pairs 31 | 32 | >>> add(collection, 123, 'a') 33 | >>> add(collection, 456, 'b') 34 | >>> add(collection, 789, 'c') 35 | >>> add(collection, 101, 'c') 36 | 37 | # Look at the collection 38 | 39 | >>> collection 40 | [[], [(123, 'a'), (101, 'c')], [], [], 41 | [(456, 'b')], [], [], [(789, 'c')], [], []] 42 | 43 | # Check if the contains works correctly 44 | 45 | >>> contains(collection, 123) 46 | True 47 | >>> contains(collection, 1) 48 | False 49 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_03_set.rst: -------------------------------------------------------------------------------- 1 | # All output in the table below is generated using this function 2 | 3 | >>> def print_set(expression, set_): 4 | ... 'Print set as a string sorted by letters' 5 | ... print(expression, ''.join(sorted(set_))) 6 | 7 | >>> spam = set('spam') 8 | >>> print_set('spam:', spam) 9 | spam: amps 10 | 11 | >>> eggs = set('eggs') 12 | >>> print_set('eggs:', eggs) 13 | eggs: egs 14 | 15 | ------------------------------------------------------------------------------ 16 | 17 | >>> current_users = set(( 18 | ... 'a', 19 | ... 'b', 20 | ... 'd', 21 | ... )) 22 | 23 | >>> new_users = set(( 24 | ... 'b', 25 | ... 'c', 26 | ... 'd', 27 | ... 'e', 28 | ... )) 29 | 30 | >>> to_insert = new_users - current_users 31 | >>> sorted(to_insert) 32 | ['c', 'e'] 33 | >>> to_delete = current_users - new_users 34 | >>> sorted(to_delete) 35 | ['a'] 36 | >>> unchanged = new_users & current_users 37 | >>> sorted(unchanged) 38 | ['b', 'd'] 39 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_08_enum.rst: -------------------------------------------------------------------------------- 1 | >>> import enum 2 | 3 | 4 | >>> class Color(enum.Enum): 5 | ... red = 1 6 | ... green = 2 7 | ... blue = 3 8 | 9 | >>> Color.red 10 | 11 | >>> Color['red'] 12 | 13 | >>> Color(1) 14 | 15 | >>> Color.red.name 16 | 'red' 17 | >>> Color.red.value 18 | 1 19 | >>> isinstance(Color.red, Color) 20 | True 21 | >>> Color.red is Color['red'] 22 | True 23 | >>> Color.red is Color(1) 24 | True 25 | 26 | ------------------------------------------------------------------------------ 27 | 28 | >>> for color in Color: 29 | ... color 30 | 31 | 32 | 33 | 34 | >>> colors = dict() 35 | >>> colors[Color.green] = 0x00FF00 36 | >>> colors 37 | {: 65280} 38 | 39 | ------------------------------------------------------------------------------ 40 | 41 | >>> import enum 42 | 43 | 44 | >>> class Spam(enum.Enum): 45 | ... EGGS = 'eggs' 46 | 47 | >>> Spam.EGGS == 'eggs' 48 | False 49 | 50 | ------------------------------------------------------------------------------ 51 | 52 | >>> import enum 53 | 54 | 55 | >>> class Spam(str, enum.Enum): 56 | ... EGGS = 'eggs' 57 | 58 | >>> Spam.EGGS == 'eggs' 59 | True 60 | 61 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_09_heapq.rst: -------------------------------------------------------------------------------- 1 | >>> import heapq 2 | 3 | 4 | >>> heap = [1, 3, 5, 7, 2, 4, 3] 5 | >>> heapq.heapify(heap) 6 | >>> heap 7 | [1, 2, 3, 7, 3, 4, 5] 8 | 9 | >>> while heap: 10 | ... heapq.heappop(heap), heap 11 | (1, [2, 3, 3, 7, 5, 4]) 12 | (2, [3, 3, 4, 7, 5]) 13 | (3, [3, 5, 4, 7]) 14 | (3, [4, 5, 7]) 15 | (4, [5, 7]) 16 | (5, [7]) 17 | (7, []) 18 | 19 | 20 | >>> def heapsort(iterable): 21 | ... heap = [] 22 | ... for value in iterable: 23 | ... heapq.heappush(heap, value) 24 | ... 25 | ... while heap: 26 | ... yield heapq.heappop(heap) 27 | >>> list(heapsort([1, 3, 5, 2, 4, 1])) 28 | [1, 1, 2, 3, 4, 5] 29 | 30 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_11_borg_and_singleton.rst: -------------------------------------------------------------------------------- 1 | >>> class Borg: 2 | ... _state = {} 3 | ... def __init__(self): 4 | ... self.__dict__ = self._state 5 | 6 | >>> class SubBorg(Borg): 7 | ... pass 8 | 9 | >>> a = Borg() 10 | >>> b = Borg() 11 | >>> c = Borg() 12 | >>> a.a_property = 123 13 | >>> b.a_property 14 | 123 15 | >>> c.a_property 16 | 123 17 | 18 | 19 | >>> class Singleton: 20 | ... def __new__(cls): 21 | ... if not hasattr(cls, '_instance'): 22 | ... cls._instance = super(Singleton, cls).__new__(cls) 23 | ... 24 | ... return cls._instance 25 | 26 | >>> class SubSingleton(Singleton): 27 | ... pass 28 | 29 | 30 | >>> a = Singleton() 31 | >>> b = Singleton() 32 | >>> c = SubSingleton() 33 | >>> a.a_property = 123 34 | >>> b.a_property 35 | 123 36 | >>> c.a_property 37 | 123 38 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_12_getters_and_setters.rst: -------------------------------------------------------------------------------- 1 | >>> class Sandwich: 2 | ... 3 | ... def __init__(self, spam): 4 | ... self.spam = spam 5 | ... 6 | ... @property 7 | ... def spam(self): 8 | ... return self._spam 9 | ... 10 | ... @spam.setter 11 | ... def spam(self, value): 12 | ... self._spam = value 13 | ... if self._spam >= 5: 14 | ... print('You must be hungry') 15 | ... 16 | ... @spam.deleter 17 | ... def spam(self): 18 | ... self._spam = 0 19 | 20 | >>> sandwich = Sandwich(2) 21 | >>> sandwich.spam += 1 22 | >>> sandwich.spam += 2 23 | You must be hungry 24 | -------------------------------------------------------------------------------- /CH_04_design_patterns/T_13_dict_union.rst: -------------------------------------------------------------------------------- 1 | >>> a = dict(x=1, y=2) 2 | >>> b = dict(y=1, z=2) 3 | 4 | >>> c = a.copy() 5 | >>> c 6 | {'x': 1, 'y': 2} 7 | >>> c.update(b) 8 | 9 | >>> a 10 | {'x': 1, 'y': 2} 11 | >>> b 12 | {'y': 1, 'z': 2} 13 | >>> c 14 | {'x': 1, 'y': 1, 'z': 2} 15 | 16 | ------------------------------------------------------------------------------ 17 | 18 | >>> a = dict(x=1, y=2) 19 | >>> b = dict(y=1, z=2) 20 | 21 | >>> a | b 22 | {'x': 1, 'y': 1, 'z': 2} 23 | -------------------------------------------------------------------------------- /CH_04_design_patterns/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_04_design_patterns/__init__.py -------------------------------------------------------------------------------- /CH_05_functional_programming/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 4, Functional Programming 2 | ############################################################################## 3 | 4 | | Readability versus Brevity covers the functional programming techniques such as list/dict/set comprehensions and lambda statements that are available in Python. Additionally, it illustrates the similarities to the mathematical principles involved. 5 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_00_intro.rst: -------------------------------------------------------------------------------- 1 | >>> def add_value_functional(items, value): 2 | ... return items + [value] 3 | 4 | >>> items = [1, 2, 3] 5 | >>> add_value_functional(items, 5) 6 | [1, 2, 3, 5] 7 | >>> items 8 | [1, 2, 3] 9 | 10 | >>> def add_value_regular(items, value): 11 | ... items.append(value) 12 | ... return items 13 | 14 | >>> add_value_regular(items, 5) 15 | [1, 2, 3, 5] 16 | >>> items 17 | [1, 2, 3, 5] 18 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_02_dict_comprehensions.rst: -------------------------------------------------------------------------------- 1 | >>> {x: x ** 2 for x in range(6)} 2 | {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25} 3 | 4 | >>> {x: x ** 2 for x in range(6) if x % 2} 5 | {1: 1, 3: 9, 5: 25} 6 | 7 | ------------------------------------------------------------------------------ 8 | 9 | >>> {x ** 2: [y for y in range(x)] for x in range(5)} 10 | {0: [], 1: [0], 4: [0, 1], 9: [0, 1, 2], 16: [0, 1, 2, 3]} 11 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_03_set_comprehensions.rst: -------------------------------------------------------------------------------- 1 | >>> [x*y for x in range(3) for y in range(3)] 2 | [0, 0, 0, 0, 1, 2, 0, 2, 4] 3 | 4 | >>> {x*y for x in range(3) for y in range(3)} 5 | {0, 1, 2, 4} 6 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_04_lambda_functions.rst: -------------------------------------------------------------------------------- 1 | >>> import operator 2 | 3 | >>> values = dict(one=1, two=2, three=3) 4 | >>> sorted(values.items()) 5 | [('one', 1), ('three', 3), ('two', 2)] 6 | >>> sorted(values.items(), key=lambda item: item[1]) 7 | [('one', 1), ('two', 2), ('three', 3)] 8 | 9 | >>> get_value = operator.itemgetter(1) 10 | >>> sorted(values.items(), key=get_value) 11 | [('one', 1), ('two', 2), ('three', 3)] 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | >>> key = lambda item: item[1] 16 | 17 | >>> def key(item): 18 | ... return item[1] 19 | 20 | ------------------------------------------------------------------------------ 21 | 22 | >>> def key(spam): return spam.value 23 | 24 | >>> key = lambda spam: spam.value 25 | 26 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_09_accumulate.rst: -------------------------------------------------------------------------------- 1 | >>> import operator 2 | >>> import itertools 3 | 4 | # Sales per month 5 | 6 | >>> months = [10, 8, 5, 7, 12, 10, 5, 8, 15, 3, 4, 2] 7 | >>> list(itertools.accumulate(months, operator.add)) 8 | [10, 18, 23, 30, 42, 52, 57, 65, 80, 83, 87, 89] 9 | 10 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_10_chain.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> a = range(3) 4 | >>> b = range(5) 5 | >>> list(itertools.chain(a, b)) 6 | [0, 1, 2, 0, 1, 2, 3, 4] 7 | 8 | 9 | >>> import itertools 10 | 11 | >>> iterables = [range(3), range(5)] 12 | >>> list(itertools.chain.from_iterable(iterables)) 13 | [0, 1, 2, 0, 1, 2, 3, 4] 14 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_11_compress.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> list(itertools.compress(range(1000), [0, 1, 1, 1, 0, 1])) 4 | [1, 2, 3, 5] 5 | 6 | 7 | >>> primes = [0, 0, 1, 1, 0, 1, 0, 1] 8 | >>> odd = [0, 1, 0, 1, 0, 1, 0, 1] 9 | >>> numbers = ['zero', 'one', 'two', 'three', 'four', 'five'] 10 | 11 | # Primes: 12 | 13 | >>> list(itertools.compress(numbers, primes)) 14 | ['two', 'three', 'five'] 15 | 16 | # Odd numbers 17 | 18 | >>> list(itertools.compress(numbers, odd)) 19 | ['one', 'three', 'five'] 20 | 21 | # Odd primes 22 | 23 | >>> list(itertools.compress(numbers, map(all, zip(odd, primes)))) 24 | ['three', 'five'] 25 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_12_dropwhile_takewhile.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> list(itertools.dropwhile(lambda x: x <= 3, [1, 3, 5, 4, 2])) 4 | [5, 4, 2] 5 | 6 | >>> import itertools 7 | 8 | >>> list(itertools.takewhile(lambda x: x <= 3, [1, 3, 5, 4, 2])) 9 | [1, 3] 10 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_13_count.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> list(itertools.islice(itertools.count(), 10)) 4 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 5 | 6 | >>> list(itertools.islice(itertools.count(), 5, 10, 2)) 7 | [5, 7, 9] 8 | 9 | >>> list(itertools.islice(itertools.count(10, 2.5), 5)) 10 | [10, 12.5, 15.0, 17.5, 20.0] 11 | 12 | -------------------------------------------------------------------------------- /CH_05_functional_programming/T_14_groupby.rst: -------------------------------------------------------------------------------- 1 | >>> import operator 2 | >>> import itertools 3 | 4 | >>> words = ['aa', 'ab', 'ba', 'bb', 'ca', 'cb', 'cc'] 5 | 6 | # Gets the first element from the iterable 7 | 8 | >>> getter = operator.itemgetter(0) 9 | 10 | >>> for group, items in itertools.groupby(words, key=getter): 11 | ... print(f'group: {group}, items: {list(items)}') 12 | group: a, items: ['aa', 'ab'] 13 | group: b, items: ['ba', 'bb'] 14 | group: c, items: ['ca', 'cb', 'cc'] 15 | 16 | ------------------------------------------------------------ 17 | 18 | >>> import operator 19 | >>> import itertools 20 | 21 | >>> words = ['aa', 'bb', 'ca', 'ab', 'ba', 'cb', 'cc'] 22 | 23 | # Gets the first element from the iterable 24 | 25 | >>> getter = operator.itemgetter(0) 26 | 27 | >>> for group, items in itertools.groupby(words, key=getter): 28 | ... print(f'group: {group}, items: {list(items)}') 29 | group: a, items: ['aa'] 30 | group: b, items: ['bb'] 31 | group: c, items: ['ca'] 32 | group: a, items: ['ab'] 33 | group: b, items: ['ba'] 34 | group: c, items: ['cb', 'cc'] 35 | -------------------------------------------------------------------------------- /CH_05_functional_programming/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_05_functional_programming/__init__.py -------------------------------------------------------------------------------- /CH_06_decorators/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 5, Decorators 2 | ############################################################################## 3 | 4 | | Enabling Code Reuse by Decorating explains not only how to create your own function/class decorators but also how internal decorators such as property, staticmethod and classmethod function. 5 | -------------------------------------------------------------------------------- /CH_06_decorators/T_00_decorating_functions.rst: -------------------------------------------------------------------------------- 1 | >>> def decorator(function): 2 | ... return function 3 | 4 | >>> def add(a, b): 5 | ... return a + b 6 | 7 | >>> add = decorator(add) 8 | 9 | 10 | >>> @decorator 11 | ... def add(a, b): 12 | ... return a + b 13 | 14 | 15 | >>> import functools 16 | 17 | >>> def decorator(function): 18 | ... # This decorator makes sure we mimic the wrapped function 19 | ... @functools.wraps(function) 20 | ... def _decorator(a, b): 21 | ... # Pass the modified arguments to the function 22 | ... result = function(a, b + 5) 23 | ... 24 | ... # Logging the function call 25 | ... name = function.__name__ 26 | ... print(f'{name}(a={a}, b={b}): {result}') 27 | ... 28 | ... # Return a modified result 29 | ... return result + 4 30 | ... 31 | ... return _decorator 32 | 33 | >>> @decorator 34 | ... def func(a, b): 35 | ... return a + b 36 | 37 | >>> func(1, 2) 38 | func(a=1, b=2): 8 39 | 12 40 | -------------------------------------------------------------------------------- /CH_06_decorators/T_02_functools_wraps.rst: -------------------------------------------------------------------------------- 1 | >>> def decorator(function): 2 | ... def _decorator(*args, **kwargs): 3 | ... return function(*args, **kwargs) 4 | ... return _decorator 5 | 6 | >>> @decorator 7 | ... def add(a, b): 8 | ... '''Add a and b''' 9 | ... return a + b 10 | 11 | >>> help(add) 12 | Help on function _decorator in module ...: 13 | 14 | _decorator(*args, **kwargs) 15 | 16 | 17 | >>> add.__name__ 18 | '_decorator' 19 | 20 | ------------------------------------------------------------------------------ 21 | 22 | >>> import functools 23 | 24 | >>> def decorator(function): 25 | ... @functools.wraps(function) 26 | ... def _decorator(*args, **kwargs): 27 | ... return function(*args, **kwargs) 28 | ... return _decorator 29 | 30 | >>> @decorator 31 | ... def add(a, b): 32 | ... '''Add a and b''' 33 | ... return a + b 34 | 35 | >>> help(add) 36 | Help on function add in module ...: 37 | 38 | add(a, b) 39 | Add a and b 40 | 41 | 42 | >>> add.__name__ 43 | 'add' 44 | -------------------------------------------------------------------------------- /CH_06_decorators/T_03_chaining_decorators.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | >>> def track(function=None, label=None): 4 | ... # Trick to add an optional argument to our decorator 5 | ... if label and not function: 6 | ... return functools.partial(track, label=label) 7 | ... 8 | ... print(f'initializing {label}') 9 | ... 10 | ... @functools.wraps(function) 11 | ... def _track(*args, **kwargs): 12 | ... print(f'calling {label}') 13 | ... function(*args, **kwargs) 14 | ... print(f'called {label}') 15 | ... 16 | ... return _track 17 | 18 | >>> @track(label='outer') 19 | ... @track(label='inner') 20 | ... def func(): 21 | ... print('func') 22 | initializing inner 23 | initializing outer 24 | 25 | >>> func() 26 | calling outer 27 | calling inner 28 | func 29 | called inner 30 | called outer 31 | -------------------------------------------------------------------------------- /CH_06_decorators/T_04_registering_decorators.rst: -------------------------------------------------------------------------------- 1 | >>> import collections 2 | 3 | 4 | >>> class EventRegistry: 5 | ... def __init__(self): 6 | ... self.registry = collections.defaultdict(list) 7 | ... 8 | ... def on(self, *events): 9 | ... def _on(function): 10 | ... for event in events: 11 | ... self.registry[event].append(function) 12 | ... return function 13 | ... 14 | ... return _on 15 | ... 16 | ... def fire(self, event, *args, **kwargs): 17 | ... for function in self.registry[event]: 18 | ... function(*args, **kwargs) 19 | 20 | >>> events = EventRegistry() 21 | 22 | >>> @events.on('success', 'error') 23 | ... def teardown(value): 24 | ... print(f'Tearing down got: {value}') 25 | 26 | >>> @events.on('success') 27 | ... def success(value): 28 | ... print(f'Successfully executed: {value}') 29 | 30 | >>> events.fire('non-existing', 'nothing to see here') 31 | >>> events.fire('error', 'Oops, some error here') 32 | Tearing down got: Oops, some error here 33 | >>> events.fire('success', 'Everything is fine') 34 | Tearing down got: Everything is fine 35 | Successfully executed: Everything is fine 36 | -------------------------------------------------------------------------------- /CH_06_decorators/T_06_optional_arguments.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | >>> def add(function=None, add_n=0): 4 | ... # function is not callable so it's probably `add_n` 5 | ... if not callable(function): 6 | ... # Test to make sure we don't pass `None` as `add_n` 7 | ... if function is not None: 8 | ... add_n = function 9 | ... return functools.partial(add, add_n=add_n) 10 | ... 11 | ... @functools.wraps(function) 12 | ... def _add(n): 13 | ... return function(n) + add_n 14 | ... 15 | ... return _add 16 | 17 | >>> @add 18 | ... def add_zero(n): 19 | ... return n 20 | 21 | >>> @add(1) 22 | ... def add_one(n): 23 | ... return n 24 | 25 | >>> @add(add_n=2) 26 | ... def add_two(n): 27 | ... return n 28 | 29 | >>> add_zero(5) 30 | 5 31 | 32 | >>> add_one(5) 33 | 6 34 | 35 | >>> add_two(5) 36 | 7 37 | -------------------------------------------------------------------------------- /CH_06_decorators/T_07_decorators_using_classes.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | >>> class Debug(object): 4 | ... 5 | ... def __init__(self, function): 6 | ... self.function = function 7 | ... # functools.wraps for classes 8 | ... functools.update_wrapper(self, function) 9 | ... 10 | ... def __call__(self, *args, **kwargs): 11 | ... output = self.function(*args, **kwargs) 12 | ... name = self.function.__name__ 13 | ... print(f'{name}({args!r}, {kwargs!r}): {output!r}') 14 | ... return output 15 | 16 | 17 | >>> @Debug 18 | ... def add(a, b=0): 19 | ... return a + b 20 | ... 21 | >>> output = add(3) 22 | add((3,), {}): 3 23 | 24 | >>> output = add(a=4, b=2) 25 | add((), {'a': 4, 'b': 2}): 6 26 | -------------------------------------------------------------------------------- /CH_06_decorators/T_08_decorating_class_functions.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | 4 | >>> def plus_one(function): 5 | ... @functools.wraps(function) 6 | ... def _plus_one(self, n, *args): 7 | ... return function(self, n + 1, *args) 8 | ... return _plus_one 9 | 10 | 11 | >>> class Adder(object): 12 | ... @plus_one 13 | ... def add(self, a, b=0): 14 | ... return a + b 15 | 16 | 17 | >>> adder = Adder() 18 | >>> adder.add(0) 19 | 1 20 | >>> adder.add(3, 4) 21 | 8 22 | -------------------------------------------------------------------------------- /CH_06_decorators/T_11_singletons.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | >>> def singleton(cls): 4 | ... instances = dict() 5 | ... @functools.wraps(cls) 6 | ... def _singleton(*args, **kwargs): 7 | ... if cls not in instances: 8 | ... instances[cls] = cls(*args, **kwargs) 9 | ... return instances[cls] 10 | ... return _singleton 11 | 12 | >>> @singleton 13 | ... class SomeSingleton(object): 14 | ... def __init__(self): 15 | ... print('Executing init') 16 | 17 | >>> a = SomeSingleton() 18 | Executing init 19 | >>> b = SomeSingleton() 20 | 21 | >>> a is b 22 | True 23 | 24 | >>> a.x = 123 25 | >>> b.x 26 | 123 27 | -------------------------------------------------------------------------------- /CH_06_decorators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_06_decorators/__init__.py -------------------------------------------------------------------------------- /CH_06_decorators/tmp.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_06_decorators/tmp.py -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 6, Generators and Coroutines 2 | ############################################################################## 3 | 4 | | Infinity One Step at a Time shows how generators and coroutines can be used to lazily evaluate structures of infinite size. 5 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_01_creating_infinite_generators.rst: -------------------------------------------------------------------------------- 1 | >>> def count(start=0, step=1, stop=None): 2 | ... n = start 3 | ... while stop is not None and n < stop: 4 | ... yield n 5 | ... n += step 6 | 7 | >>> list(count(10, 2.5, 20)) 8 | [10, 12.5, 15.0, 17.5] 9 | 10 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_03_generator_comprehensions.rst: -------------------------------------------------------------------------------- 1 | >>> squares = (x ** 2 for x in range(4)) 2 | 3 | >>> squares 4 | at 0x...> 5 | 6 | >>> list(squares) 7 | [0, 1, 4, 9] 8 | 9 | ------------------------------------------------------------------ 10 | 11 | >>> import itertools 12 | 13 | >>> result = itertools.count() 14 | >>> odd = (x for x in result if x % 2) 15 | >>> sliced_odd = itertools.islice(odd, 5) 16 | >>> list(sliced_odd) 17 | [1, 3, 5, 7, 9] 18 | 19 | ------------------------------------------------------------------ 20 | 21 | >>> result = itertools.count() 22 | >>> sliced_result = itertools.islice(result, 5) 23 | >>> odd = (x for x in sliced_result if x % 2) 24 | >>> list(odd) 25 | [1, 3] 26 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_06_advantages_and_disadvantages.rst: -------------------------------------------------------------------------------- 1 | >>> def generator(): 2 | ... print('Before 1') 3 | ... yield 1 4 | ... print('After 1') 5 | ... print('Before 2') 6 | ... yield 2 7 | ... print('After 2') 8 | ... print('Before 3') 9 | ... yield 3 10 | ... print('After 3') 11 | 12 | >>> g = generator() 13 | >>> print('Got %d' % next(g)) 14 | Before 1 15 | Got 1 16 | 17 | >>> print('Got %d' % next(g)) 18 | After 1 19 | Before 2 20 | Got 2 21 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_08_chain.rst: -------------------------------------------------------------------------------- 1 | >>> def chain(*iterables): 2 | ... for iterable in iterables: 3 | ... yield from iterable 4 | 5 | >>> a = 1, 2, 3 6 | >>> b = [4, 5, 6] 7 | >>> c = 'abc' 8 | >>> list(chain(a, b, c)) 9 | [1, 2, 3, 4, 5, 6, 'a', 'b', 'c'] 10 | 11 | >>> a + b + c 12 | Traceback (most recent call last): 13 | ... 14 | TypeError: can only concatenate tuple (not "list") to tuple 15 | 16 | ------------------------------------------------------------------ 17 | 18 | >>> def chain(*iterables): 19 | ... for iterable in iterables: 20 | ... for i in iterable: 21 | ... yield i 22 | 23 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_09_tee.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> def spam_and_eggs(): 4 | ... yield 'spam' 5 | ... yield 'eggs' 6 | 7 | >>> a, b = itertools.tee(spam_and_eggs()) 8 | >>> next(a) 9 | 'spam' 10 | >>> next(a) 11 | 'eggs' 12 | >>> next(b) 13 | 'spam' 14 | >>> next(b) 15 | 'eggs' 16 | >>> next(b) 17 | Traceback (most recent call last): 18 | ... 19 | StopIteration 20 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_12_coroutines.rst: -------------------------------------------------------------------------------- 1 | >>> def generator(): 2 | ... value = yield 'value from generator' 3 | ... print('Generator received:', value) 4 | ... yield f'Previous value: {value!r}' 5 | 6 | >>> g = generator() 7 | >>> print('Result from generator:', next(g)) 8 | Result from generator: value from generator 9 | 10 | >>> print(g.send('value from caller')) 11 | Generator received: value from caller 12 | Previous value: 'value from caller' 13 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/T_13_priming_coroutine.rst: -------------------------------------------------------------------------------- 1 | >>> import functools 2 | 3 | 4 | >>> def coroutine(function): 5 | ... # Copy the `function` description with `functools.wraps` 6 | ... @functools.wraps(function) 7 | ... def _coroutine(*args, **kwargs): 8 | ... active_coroutine = function(*args, **kwargs) 9 | ... # Prime the coroutine and make sure we get no values 10 | ... assert not next(active_coroutine) 11 | ... return active_coroutine 12 | ... return _coroutine 13 | 14 | 15 | >>> @coroutine 16 | ... def our_coroutine(): 17 | ... while True: 18 | ... print('Waiting for yield...') 19 | ... value = yield 20 | ... print('our coroutine received:', value) 21 | 22 | >>> generator = our_coroutine() 23 | Waiting for yield... 24 | 25 | >>> generator.send('a') 26 | our coroutine received: a 27 | Waiting for yield... 28 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_07_generators_and_coroutines/__init__.py -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_07_generators_and_coroutines/coroutine_decorator.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | 4 | def coroutine(function): 5 | @functools.wraps(function) 6 | def _coroutine(*args, **kwargs): 7 | active_coroutine = function(*args, **kwargs) 8 | assert not next(active_coroutine) 9 | return active_coroutine 10 | return _coroutine 11 | -------------------------------------------------------------------------------- /CH_08_metaclasses/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 8, Metaclasses 2 | ############################################################################## 3 | 4 | | Making Classes (not instances) Smarter goes deeper into the creation of classes and how class behavior can be completely modified. 5 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_00_dynamically_creating_classes.rst: -------------------------------------------------------------------------------- 1 | >>> class Spam(object): 2 | ... eggs = 'my eggs' 3 | 4 | >>> Spam = type('Spam', (object,), dict(eggs='my eggs')) 5 | 6 | ------------------------------------------------------------------------------ 7 | 8 | >>> class Spam(object): 9 | ... eggs = 'my eggs' 10 | 11 | >>> spam = Spam() 12 | >>> spam.eggs 13 | 'my eggs' 14 | >>> type(spam) 15 | 16 | >>> type(Spam) 17 | 18 | 19 | >>> Spam = type('Spam', (object,), dict(eggs='my eggs')) 20 | 21 | >>> spam = Spam() 22 | >>> spam.eggs 23 | 'my eggs' 24 | >>> type(spam) 25 | 26 | >>> type(Spam) 27 | 28 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_01_basic_metaclass.rst: -------------------------------------------------------------------------------- 1 | # The metaclass definition, note the inheritance of type instead 2 | of object 3 | 4 | >>> class MetaSandwich(type): 5 | ... 6 | ... # Notice how the __new__ method has the same arguments 7 | ... # as the type function we used earlier? 8 | ... def __new__(metaclass, name, bases, namespace): 9 | ... name = 'SandwichCreatedByMeta' 10 | ... bases = (int,) + bases 11 | ... namespace['lettuce'] = 1 12 | ... return type.__new__(metaclass, name, bases, namespace) 13 | 14 | 15 | # First, the regular Sandwich: 16 | 17 | >>> class Sandwich(object): 18 | ... pass 19 | 20 | >>> Sandwich.__name__ 21 | 'Sandwich' 22 | >>> issubclass(Sandwich, int) 23 | False 24 | >>> Sandwich.lettuce 25 | Traceback (most recent call last): 26 | ... 27 | AttributeError: type object 'Sandwich' has no attribute 'lettuce' 28 | 29 | 30 | # Now the meta-Sandwich 31 | 32 | >>> class Sandwich(object, metaclass=MetaSandwich): 33 | ... pass 34 | 35 | >>> Sandwich.__name__ 36 | 'SandwichCreatedByMeta' 37 | >>> issubclass(Sandwich, int) 38 | True 39 | >>> Sandwich.lettuce 40 | 1 41 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_03_accessing_metaclass_attributes.rst: -------------------------------------------------------------------------------- 1 | >>> class Meta(type): 2 | ... 3 | ... @property 4 | ... def some_property(cls): 5 | ... return 'property of %r' % cls 6 | ... 7 | ... def some_method(self): 8 | ... return 'method of %r' % self 9 | 10 | 11 | >>> class SomeClass(metaclass=Meta): 12 | ... pass 13 | 14 | # Accessing through the class definition 15 | 16 | >>> SomeClass.some_property 17 | "property of " 18 | >>> SomeClass.some_method 19 | > 20 | >>> SomeClass.some_method() 21 | "method of " 22 | 23 | # Accessing through an instance 24 | 25 | >>> some_class = SomeClass() 26 | >>> some_class.some_property 27 | Traceback (most recent call last): 28 | ... 29 | AttributeError: 'SomeClass' object has no attribute 'some_property' 30 | >>> some_class.some_method 31 | Traceback (most recent call last): 32 | ... 33 | AttributeError: 'SomeClass' object has no attribute 'some_method' 34 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_06_automatic_plugin_system.rst: -------------------------------------------------------------------------------- 1 | >>> import abc 2 | 3 | 4 | >>> class Plugins(abc.ABCMeta): 5 | ... plugins = dict() 6 | ... 7 | ... def __new__(metaclass, name, bases, namespace): 8 | ... cls = abc.ABCMeta.__new__(metaclass, name, bases, 9 | ... namespace) 10 | ... if isinstance(cls.name, str): 11 | ... metaclass.plugins[cls.name] = cls 12 | ... return cls 13 | ... 14 | ... @classmethod 15 | ... def get(cls, name): 16 | ... return cls.plugins[name] 17 | 18 | 19 | >>> class PluginBase(metaclass=Plugins): 20 | ... @property 21 | ... @abc.abstractmethod 22 | ... def name(self): 23 | ... raise NotImplemented() 24 | 25 | 26 | >>> class PluginA(PluginBase): 27 | ... name = 'a' 28 | 29 | 30 | >>> class PluginB(PluginBase): 31 | ... name = 'b' 32 | 33 | 34 | >>> Plugins.get('a') 35 | 36 | 37 | >>> Plugins.plugins 38 | {'a': , 39 | 'b': } 40 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_07_plugins_on_demand.out: -------------------------------------------------------------------------------- 1 | Loading plugins from plugins.a 2 | 3 | 4 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_07_plugins_on_demand.py: -------------------------------------------------------------------------------- 1 | import plugins 2 | 3 | print(plugins.PluginsOnDemand.get('a')) 4 | print(plugins.PluginsOnDemand.get('a')) 5 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_08_plugins_through_configuration.out: -------------------------------------------------------------------------------- 1 | Loading plugins from plugins.a 2 | Loading plugins from plugins.b 3 | After load 4 | 5 | 6 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_08_plugins_through_configuration.py: -------------------------------------------------------------------------------- 1 | import plugins 2 | 3 | plugins.PluginsThroughConfiguration.load( 4 | 'a', 5 | 'b', 6 | ) 7 | 8 | print('After load') 9 | print(plugins.PluginsThroughConfiguration.get('a')) 10 | print(plugins.PluginsThroughConfiguration.get('a')) 11 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_09_plugins_through_filesystem.out: -------------------------------------------------------------------------------- 1 | Loading plugins from plugins.a 2 | Loading plugins from plugins.b 3 | After load 4 | {'a': , 5 | 'b': , 6 | 'plugin': } 7 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_09_plugins_through_filesystem.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | import plugins 3 | 4 | plugins.PluginsThroughFilesystem.autoload() 5 | 6 | print('After load') 7 | pprint.pprint(plugins.PluginsThroughFilesystem.plugins) 8 | -------------------------------------------------------------------------------- /CH_08_metaclasses/T_10_dataclasses.rst: -------------------------------------------------------------------------------- 1 | >>> from T_10_dataclasses import Dataclass 2 | 3 | >>> class Sandwich(metaclass=Dataclass): 4 | ... spam: int 5 | ... eggs: int = 3 6 | 7 | >>> Sandwich(1, 2) 8 | Sandwich(spam=1, eggs=2) 9 | 10 | >>> sandwich = Sandwich(4) 11 | >>> sandwich 12 | Sandwich(spam=4, eggs=3) 13 | >>> sandwich.eggs 14 | 3 15 | 16 | >>> help(Sandwich.__init__) 17 | Help on function __init__ in ... 18 | 19 | __init__(spam: int, eggs: int = 3) 20 | 21 | 22 | >>> Sandwich('a') 23 | Traceback (most recent call last): 24 | ... 25 | ValueError: invalid literal for int() with base 10: 'a' 26 | 27 | >>> Sandwich('1234', 56.78) 28 | Sandwich(spam=1234, eggs=56) 29 | -------------------------------------------------------------------------------- /CH_08_metaclasses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_08_metaclasses/__init__.py -------------------------------------------------------------------------------- /CH_08_metaclasses/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_08_metaclasses/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Plugin 2 | from .base import Plugins 3 | from .base import PluginsOnDemand 4 | from .base import PluginsThroughConfiguration 5 | from .base import PluginsThroughFilesystem 6 | 7 | __all__ = [ 8 | 'Plugin', 'Plugins', 'PluginsOnDemand', 9 | 'PluginsThroughConfiguration', 'PluginsThroughFilesystem'] 10 | 11 | -------------------------------------------------------------------------------- /CH_08_metaclasses/plugins/a.py: -------------------------------------------------------------------------------- 1 | from . import base 2 | 3 | 4 | class A(base.Plugin): 5 | pass 6 | 7 | -------------------------------------------------------------------------------- /CH_08_metaclasses/plugins/b.py: -------------------------------------------------------------------------------- 1 | from . import base 2 | 3 | 4 | class B(base.Plugin): 5 | pass 6 | 7 | -------------------------------------------------------------------------------- /CH_09_documentation/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 9, Documentation 2 | ############################################################################## 3 | 4 | | reStructuredText, Napoleon and How to Use Sphinx shows how you can make Sphinx automatically document your code with very little effort. Additionally, it shows how the Napoleon syntax can be used to document function arguments in a way that is legible both in the code and the documentation. 5 | -------------------------------------------------------------------------------- /CH_09_documentation/T_01_type_hinting.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | def pow(base: int, exponent: int) -> int: 5 | return base ** exponent 6 | 7 | 8 | pow(2.5, 10) 9 | 10 | ################################################################ 11 | 12 | Username = typing.NewType('Username', str) 13 | 14 | rick = Username('Rick') 15 | 16 | 17 | def print_username(username: Username): 18 | print(f'Username: {username}') 19 | 20 | 21 | print_username(rick) 22 | print_username(str(rick)) 23 | 24 | ################################################################ 25 | 26 | T = typing.TypeVar('T') 27 | 28 | 29 | def to_string(value: T) -> T: 30 | return str(value) 31 | 32 | 33 | to_string(1) 34 | -------------------------------------------------------------------------------- /CH_09_documentation/T_02_restructuredtext_syntax.rst: -------------------------------------------------------------------------------- 1 | Documentation, how to use Sphinx and reStructuredText 2 | ################################################################## 3 | 4 | Documenting code can be both fun and useful! ... 5 | 6 | Additionally, adding ... 7 | 8 | ... So that typing `Spam.eggs.` will automatically ... 9 | 10 | Topics covered in this chapter are as follows: 11 | 12 | - The reStructuredText syntax 13 | - Setting up documentation using Sphinx 14 | - Sphinx style docstrings 15 | - Google style docstrings 16 | - NumPy style docstrings 17 | 18 | The reStructuredText syntax 19 | ****************************************************************** 20 | 21 | The reStructuredText format (also known as ... 22 | -------------------------------------------------------------------------------- /CH_09_documentation/T_04_lists.rst: -------------------------------------------------------------------------------- 1 | 1. With 2 | 2. Numbers 3 | 4 | a. With 5 | #. letters 6 | 7 | i. Roman 8 | #. numerals 9 | 10 | (1) With 11 | (2) Parenthesis 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | - dashes 16 | - and more dashes 17 | 18 | * asterisk 19 | * stars 20 | 21 | + plus 22 | + and plus 23 | 24 | ------------------------------------------------------------------------------ 25 | 26 | -s, --spam This is the spam option 27 | --eggs This is the eggs option 28 | 29 | ------------------------------------------------------------------------------ 30 | 31 | spam 32 | Spam is a canned pork meat product 33 | eggs 34 | Is, similar to spam, also food 35 | 36 | ------------------------------------------------------------------------------ 37 | 38 | 1. With 39 | 2. Numbers 40 | 41 | (food) food 42 | 43 | spam 44 | Spam is a canned pork meat product 45 | eggs 46 | Is, similar to spam, also food 47 | 48 | (other) non-food stuff 49 | -------------------------------------------------------------------------------- /CH_09_documentation/T_05_links.rst: -------------------------------------------------------------------------------- 1 | The switch to reStructuredText and Sphinx was made with the 2 | `Python 2.6 `_ 3 | release. 4 | 5 | ------------------------------------------------------------------------------ 6 | 7 | The switch to reStructuredText and Sphinx was made with the 8 | `python 2.6`_ release. 9 | 10 | .. _`Python 2.6`: https://docs.python.org/whatsnew/2.6.html 11 | 12 | ------------------------------------------------------------------------------ 13 | 14 | The introduction section 15 | ================================================================ 16 | 17 | This section contains: 18 | 19 | - `chapter 1`_ 20 | - :ref:`chapter2` 21 | 22 | 1. my_label_ 23 | 24 | 2. `And a label link with a custom title `_ 25 | 26 | Chapter 1 27 | ---------------------------------------------------------------- 28 | 29 | Jumping back to the beginning of `chapter 1`_ is also possible. 30 | Or jumping to :ref:`Chapter 2 ` 31 | 32 | .. _chapter2: 33 | 34 | Chapter 2 With a longer title 35 | ---------------------------------------------------------------- 36 | 37 | The next chapter. 38 | 39 | .. _my_label: 40 | 41 | The label points here. 42 | 43 | Back to `the introduction section`_ 44 | -------------------------------------------------------------------------------- /CH_09_documentation/T_06_images.rst: -------------------------------------------------------------------------------- 1 | .. image:: python.png 2 | :width: 150 3 | :height: 100 4 | 5 | .. image:: python.png 6 | :scale: 10 7 | 8 | ------------------------------------------------------------------------------ 9 | 10 | .. figure:: python.png 11 | :scale: 10 12 | 13 | The Python logo 14 | -------------------------------------------------------------------------------- /CH_09_documentation/T_07_substitutions.rst: -------------------------------------------------------------------------------- 1 | .. |python| image:: python.png 2 | :scale: 2 3 | 4 | The Python programming language uses the logo: |python| 5 | 6 | ------------------------------------------------------------------------------ 7 | 8 | .. |author| replace:: Rick van Hattem 9 | 10 | This book was written by |author| 11 | -------------------------------------------------------------------------------- /CH_09_documentation/T_08_blocks.rst: -------------------------------------------------------------------------------- 1 | .. code:: python 2 | 3 | def spam(*args): 4 | print('spam got args', args) 5 | 6 | ------------------------------------------------------------------------------ 7 | 8 | .. math:: 9 | 10 | \int_a^b f(x)\,dx = F(b) - F(a) 11 | 12 | ------------------------------------------------------------------------------ 13 | 14 | Before comments 15 | 16 | .. Everything here will be commented 17 | 18 | And this as well 19 | 20 | .. code:: python 21 | 22 | def even_this_code_sample(): 23 | pass # Will be commented 24 | 25 | After comments 26 | 27 | ------------------------------------------------------------------------------ 28 | 29 | Normal text 30 | 31 | Quoted text 32 | -------------------------------------------------------------------------------- /CH_09_documentation/T_09_toctree.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | 4 | ------------------------------------------------------------------------------ 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | module.a 10 | module.b 11 | module.c 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | :glob: 18 | 19 | module.* 20 | 21 | ------------------------------------------------------------------------------ 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | 26 | The A module 27 | 28 | -------------------------------------------------------------------------------- /CH_09_documentation/T_10_autodoc.rst: -------------------------------------------------------------------------------- 1 | eggs module 2 | =========== 3 | 4 | .. automodule:: eggs 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | ------------------------------------------------------------------------------ 10 | 11 | eggs module 12 | =========== 13 | 14 | .. automodule:: eggs 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | :inherited-members: 19 | 20 | ------------------------------------------------------------------------------ 21 | 22 | eggs module 23 | =========== 24 | 25 | .. automodule:: eggs 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | :inherited-members: 30 | :private-members: 31 | 32 | ------------------------------------------------------------------------------ 33 | 34 | eggs module 35 | =========== 36 | 37 | .. automodule:: eggs 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | .. class:: NonExistingClass 43 | This class doesn't actually exist, but it's in the documentation now. 44 | 45 | .. method:: non_existing_function() 46 | 47 | And this function does not exist either. 48 | -------------------------------------------------------------------------------- /CH_09_documentation/T_11_roles.rst: -------------------------------------------------------------------------------- 1 | Spam: :class:`spam.Spam` 2 | 3 | ------------------------------------------------------------------------------ 4 | 5 | Link to the intersphinx module: :mod:`sphinx.ext.intersphinx` 6 | -------------------------------------------------------------------------------- /CH_09_documentation/T_12_sphinx_style.py: -------------------------------------------------------------------------------- 1 | class Eggs: 2 | pass 3 | 4 | 5 | class Spam(object): 6 | 7 | ''' 8 | The Spam object contains lots of spam 9 | 10 | :param arg: The arg is used for ... 11 | :type arg: str 12 | :param `*args`: The variable arguments are used for ... 13 | :param `**kwargs`: The keyword arguments are used for ... 14 | :ivar arg: This is where we store arg 15 | :vartype arg: str 16 | ''' 17 | 18 | def __init__(self, arg: str, *args, **kwargs): 19 | self.arg: str = arg 20 | 21 | def eggs(self, number: int, cooked: bool) -> Eggs: 22 | '''We can't have spam without eggs, so here are the eggs 23 | 24 | :param number: The number of eggs to return 25 | :type number: int 26 | :param bool cooked: Should the eggs be cooked? 27 | :raises: :class:`RuntimeError`: Out of eggs 28 | 29 | :returns: A bunch of eggs 30 | :rtype: Eggs 31 | ''' 32 | pass 33 | 34 | -------------------------------------------------------------------------------- /CH_09_documentation/T_13_google_style.py: -------------------------------------------------------------------------------- 1 | class Eggs: 2 | pass 3 | 4 | 5 | class Spam(object): 6 | 7 | r''' 8 | The Spam object contains lots of spam 9 | 10 | Args: 11 | arg: The arg is used for ... 12 | \*args: The variable arguments are used for ... 13 | \*\*kwargs: The keyword arguments are used for ... 14 | 15 | Attributes: 16 | arg: This is where we store arg, 17 | ''' 18 | 19 | def __init__(self, arg: str, *args, **kwargs): 20 | self.arg: str = arg 21 | 22 | def eggs(self, number: int, cooked: bool) -> Eggs: 23 | '''We can't have spam without eggs, so here are the eggs 24 | 25 | Args: 26 | number: The number of eggs to return 27 | cooked: Should the eggs be cooked? 28 | 29 | Raises: 30 | RuntimeError: Out of eggs 31 | 32 | Returns: 33 | Eggs: A bunch of eggs 34 | ''' 35 | pass 36 | 37 | -------------------------------------------------------------------------------- /CH_09_documentation/T_14_numpy_style.py: -------------------------------------------------------------------------------- 1 | class Eggs: 2 | pass 3 | 4 | 5 | class Spam(object): 6 | 7 | r''' 8 | The Spam object contains lots of spam 9 | 10 | Parameters 11 | ---------- 12 | arg : str 13 | The arg is used for ... 14 | \*args 15 | The variable arguments are used for ... 16 | \*\*kwargs 17 | The keyword arguments are used for ... 18 | 19 | Attributes 20 | ---------- 21 | arg : str 22 | This is where we store arg, 23 | ''' 24 | 25 | def __init__(self, arg, *args, **kwargs): 26 | self.arg = arg 27 | 28 | def eggs(self, number, cooked): 29 | '''We can't have spam without eggs, so here are the eggs 30 | 31 | Parameters 32 | ---------- 33 | number : int 34 | The number of eggs to return 35 | cooked : bool 36 | Should the eggs be cooked? 37 | 38 | Raises 39 | ------ 40 | RuntimeError 41 | Out of eggs 42 | 43 | Returns 44 | ------- 45 | Eggs 46 | A bunch of eggs 47 | ''' 48 | pass 49 | 50 | -------------------------------------------------------------------------------- /CH_09_documentation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_09_documentation/__init__.py -------------------------------------------------------------------------------- /CH_09_documentation/apidoc_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_09_documentation/apidoc_example/__init__.py -------------------------------------------------------------------------------- /CH_09_documentation/apidoc_example/a.py: -------------------------------------------------------------------------------- 1 | class A(object): 2 | 3 | def __init__(self, arg, *args, **kwargs): 4 | pass 5 | 6 | def regular_method(self, arg): 7 | pass 8 | 9 | @classmethod 10 | def decorated_method(self, arg): 11 | pass 12 | 13 | def _hidden_method(self): 14 | pass 15 | 16 | -------------------------------------------------------------------------------- /CH_09_documentation/apidoc_example/b.py: -------------------------------------------------------------------------------- 1 | from . import a 2 | 3 | 4 | class B(a.A): 5 | 6 | def regular_method(self): 7 | '''This regular method overrides 8 | :meth:`a.A.regular_method` 9 | ''' 10 | pass 11 | 12 | -------------------------------------------------------------------------------- /CH_09_documentation/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /CH_09_documentation/docs/apidoc_example.rst: -------------------------------------------------------------------------------- 1 | apidoc\_example package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | apidoc\_example.a module 8 | ------------------------ 9 | 10 | .. automodule:: apidoc_example.a 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | :private-members: 15 | :special-members: 16 | :inherited-members: 17 | 18 | apidoc\_example.b module 19 | ------------------------ 20 | 21 | .. automodule:: apidoc_example.b 22 | :members: 23 | :undoc-members: 24 | :show-inheritance: 25 | :private-members: 26 | :special-members: 27 | :inherited-members: 28 | 29 | Module contents 30 | --------------- 31 | 32 | .. automodule:: apidoc_example 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | :private-members: 37 | :special-members: 38 | :inherited-members: 39 | -------------------------------------------------------------------------------- /CH_09_documentation/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Mastering Python documentation master file, created by 2 | sphinx-quickstart on Mon Sep 7 00:03:29 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Mastering Python's documentation! 7 | ============================================ 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | modules 14 | 15 | 16 | .. automodule:: 13_sphinx_style 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | 22 | .. automodule:: 14_google_style 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | 28 | .. automodule:: 15_numpy_style 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | -------------------------------------------------------------------------------- /CH_09_documentation/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /CH_09_documentation/docs/modules.rst: -------------------------------------------------------------------------------- 1 | apidoc_example 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | apidoc_example 8 | -------------------------------------------------------------------------------- /CH_09_documentation/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_09_documentation/python.png -------------------------------------------------------------------------------- /CH_10_testing_and_logging/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 10, Testing and Logging 2 | ############################################################################## 3 | 4 | | Preparing for Bugs explains how code can be tested and how logging can be added to enable easy debugging in the case of bugs at a later time. 5 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_00_simple_doctest.py: -------------------------------------------------------------------------------- 1 | def square(n: int) -> int: 2 | ''' 3 | Returns the input number, squared 4 | 5 | >>> square(0) 6 | 0 7 | >>> square(1) 8 | 1 9 | >>> square(2) 10 | 4 11 | >>> square(3) 12 | 9 13 | >>> square() 14 | Traceback (most recent call last): 15 | ... 16 | TypeError: square() missing 1 required positional argument: 'n' 17 | >>> square('x') 18 | Traceback (most recent call last): 19 | ... 20 | TypeError: can't multiply sequence by non-int of type 'str' 21 | 22 | Args: 23 | n (int): The number to square 24 | 25 | Returns: 26 | int: The squared result 27 | ''' 28 | return n * n 29 | 30 | 31 | if __name__ == '__main__': 32 | import doctest 33 | doctest.testmod() 34 | 35 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_01_simple_doctest.py: -------------------------------------------------------------------------------- 1 | def square(n: int) -> int: 2 | ''' 3 | >>> square('x') 4 | Traceback (most recent call last): 5 | ... 6 | TypeError: unsupported operand type(s) for ** or pow(): ... 7 | ''' 8 | return n ** 2 9 | 10 | 11 | if __name__ == '__main__': 12 | import doctest 13 | doctest.testmod(optionflags=doctest.ELLIPSIS) 14 | 15 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_02_testing_with_documentation/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_02_testing_with_documentation/index.rst: -------------------------------------------------------------------------------- 1 | .. doctest support documentation master file, created by 2 | sphinx-quickstart on Sun Oct 4 17:57:12 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to doctest support's documentation! 7 | =========================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | square 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_02_testing_with_documentation/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_02_testing_with_documentation/square.rst: -------------------------------------------------------------------------------- 1 | square module 2 | ============= 3 | 4 | .. automodule:: square 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Examples: 10 | 11 | .. testsetup:: 12 | 13 | from square import square 14 | 15 | .. doctest:: 16 | 17 | # pytest does not recognize testsetup 18 | >>> from square import square 19 | >>> square(100) 20 | 10000 21 | >>> square(0) 22 | 0 23 | >>> square(1) 24 | 1 25 | >>> square(3) 26 | 9 27 | >>> square() 28 | Traceback (most recent call last): 29 | ... 30 | TypeError: square() missing 1 required positional argument: 'n' 31 | >>> square('x') 32 | Traceback (most recent call last): 33 | ... 34 | TypeError: can't multiply sequence by non-int of type 'str' 35 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_03_doctest_true_for_1_flag.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> False 3 | 0 4 | >>> True 5 | 1 6 | ''' 7 | 8 | if __name__ == '__main__': 9 | import doctest 10 | doctest.testmod() 11 | doctest.testmod(optionflags=doctest.DONT_ACCEPT_TRUE_FOR_1) 12 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_04_doctest_normalize_whitespace_flag.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> [list(range(5)) for i in range(3)] 3 | [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]] 4 | 5 | >>> # doctest: +NORMALIZE_WHITESPACE 6 | ... [list(range(5)) for i in range(3)] 7 | [[0, 1, 2, 3, 4], 8 | [0, 1, 2, 3, 4], 9 | [0, 1, 2, 3, 4]] 10 | ''' 11 | 12 | if __name__ == '__main__': 13 | import doctest 14 | doctest.testmod() 15 | 16 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_05_doctest_ellipsis_flag.py: -------------------------------------------------------------------------------- 1 | ''' 2 | >>> {10: 'a', 20: 'b'} # doctest: +ELLIPSIS 3 | {...} 4 | >>> [True, 1, 'a'] # doctest: +ELLIPSIS 5 | [...] 6 | >>> True, # doctest: +ELLIPSIS 7 | (...) 8 | >>> [1, 2, 3, 4] # doctest: +ELLIPSIS 9 | [1, ..., 4] 10 | >>> [1, 0, 0, 0, 0, 0, 4] # doctest: +ELLIPSIS 11 | [1, ..., 4] 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | >>> class Spam(object): 16 | ... pass 17 | >>> Spam() # doctest: +ELLIPSIS 18 | <...Spam object at 0x...> 19 | ''' 20 | 21 | if __name__ == '__main__': 22 | import doctest 23 | doctest.testmod() 24 | 25 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_06_testing_dictionaries.rst: -------------------------------------------------------------------------------- 1 | >>> import pprint 2 | 3 | >>> data = dict.fromkeys('spam') 4 | >>> pprint.pprint(data) 5 | {'a': None, 'm': None, 'p': None, 's': None} 6 | 7 | ------------------------------------------------------------------------------ 8 | 9 | >>> data = dict.fromkeys('spam') 10 | >>> sorted(data.items()) 11 | [('a', None), ('m', None), ('p', None), ('s', None)] 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | >>> data = dict.fromkeys('spam') 16 | >>> data == {'a': None, 'm': None, 'p': None, 's': None} 17 | True 18 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_07_testing_floating_point_numbers.rst: -------------------------------------------------------------------------------- 1 | >>> 1/3 # doctest: +ELLIPSIS 2 | 0.333... 3 | >>> f'{1/3:.3f}' 4 | '0.333' 5 | >>> '{:.3f}'.format(1/3) 6 | '0.333' 7 | >>> round(1/3, 3) 8 | 0.333 9 | >>> 0.333 < 1/3 < 0.334 10 | True 11 | 12 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_08_testing_times_and_durations.rst: -------------------------------------------------------------------------------- 1 | >>> import time 2 | 3 | >>> a = time.time() 4 | >>> b = time.time() 5 | >>> (b - a) < 0.01 6 | True 7 | 8 | ------------------------------------------------------------------------------ 9 | 10 | >>> import datetime 11 | 12 | >>> a = datetime.datetime.now() 13 | >>> b = datetime.datetime.now() 14 | >>> str(b - a) # doctest: +ELLIPSIS 15 | '0:00:00.000... 16 | 17 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_09_test_cube.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import cube 3 | 4 | 5 | class TestCube(unittest.TestCase): 6 | 7 | def test_0(self): 8 | self.assertEqual(cube.cube(0), 0) 9 | 10 | def test_1(self): 11 | self.assertEqual(cube.cube(1), 1) 12 | 13 | def test_2(self): 14 | self.assertEqual(cube.cube(2), 8) 15 | 16 | def test_3(self): 17 | self.assertEqual(cube.cube(3), 27) 18 | 19 | def test_no_arguments(self): 20 | with self.assertRaises(TypeError): 21 | cube.cube() 22 | 23 | def test_exception_str(self): 24 | with self.assertRaises(TypeError): 25 | cube.cube('x') 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_10_simplifying_assertions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import cube 3 | 4 | n = 2 5 | expected = 8 6 | 7 | 8 | # Regular unit test 9 | class TestCube(unittest.TestCase): 10 | 11 | def test_2(self): 12 | self.assertEqual(cube.cube(n), expected) 13 | 14 | def test_no_arguments(self): 15 | with self.assertRaises(TypeError): 16 | cube.cube() 17 | 18 | 19 | # py.test class 20 | class TestPyCube: 21 | 22 | def test_2(self): 23 | assert cube.cube(n) == expected 24 | 25 | 26 | # py.test functions 27 | def test_2(): 28 | assert cube.cube(n) == expected 29 | 30 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_11_representing_assertions.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def __eq__(self, other): 6 | return self.name == other.name 7 | 8 | 9 | def test_user_equal(): 10 | a = User('Rick') 11 | b = User('Guido') 12 | 13 | assert a == b 14 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_12_assert_representation.py: -------------------------------------------------------------------------------- 1 | class User: 2 | def __init__(self, name): 3 | self.name = name 4 | 5 | def __eq__(self, other): 6 | return self.name == other.name 7 | 8 | 9 | def test_user_equal(): 10 | a = User('Rick') 11 | b = User('Guido') 12 | 13 | assert a == b 14 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_13_parameterizing_tests.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import cube 3 | 4 | 5 | cubes = ( 6 | (0, 0), 7 | (1, 1), 8 | (2, 8), 9 | (3, 27), 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize('n,expected', cubes) 14 | def test_cube(n, expected): 15 | assert cube.cube(n) == expected 16 | 17 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_15_print_statements_and_logging.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import logging 4 | 5 | 6 | def test_print(): 7 | print('Printing to stdout') 8 | print('Printing to stderr', file=sys.stderr) 9 | logging.debug('Printing to debug') 10 | logging.info('Printing to info') 11 | logging.warning('Printing to warning') 12 | logging.error('Printing to error') 13 | # We don't want to display os.environ so hack around it 14 | fail = 'FAIL' in os.environ 15 | assert not fail 16 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_16_test_cube_root.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import cube_root 3 | 4 | 5 | cubes = ( 6 | (0, 0), 7 | (1, 1), 8 | (8, 2), 9 | (27, 3), 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize('n,expected', cubes) 14 | def test_cube_root(n, expected): 15 | assert cube_root.cube_root(n) == expected 16 | 17 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_17_test_cube_root_subzero.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import cube_root 3 | 4 | 5 | cubes = ( 6 | (0, 0), 7 | (1, 1), 8 | (8, 2), 9 | (27, 3), 10 | ) 11 | 12 | 13 | @pytest.mark.parametrize('n,expected', cubes) 14 | def test_cube_root(n, expected): 15 | assert cube_root.cube_root(n) == expected 16 | 17 | 18 | def test_cube_root_below_zero(): 19 | with pytest.raises(ValueError): 20 | cube_root.cube_root(-1) 21 | 22 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_18_bad_code.py: -------------------------------------------------------------------------------- 1 | import os 2 | def test(a,b): 3 | return c 4 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_19_pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = 3 | your_project_source/*.py 4 | tests/*.py 5 | 6 | addopts = 7 | --doctest-modules 8 | --cov your_project_source 9 | --cov-report term-missing 10 | --cov-report html 11 | --flake8 12 | --mypy 13 | 14 | # W391 is the error about blank lines at the end of a file 15 | flake8-ignore = 16 | *.py W391 17 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_20_unittest_mock.py: -------------------------------------------------------------------------------- 1 | from unittest import mock 2 | import random 3 | 4 | 5 | @mock.patch('random.random') 6 | def test_random(mock_random): 7 | # Specify our mock return value 8 | mock_random.return_value = 0.1 9 | # Test for the mock return value 10 | assert random.random() == 0.1 11 | assert mock_random.call_count == 1 12 | 13 | 14 | def test_random_with(): 15 | with mock.patch('random.random') as mock_random: 16 | mock_random.return_value = 0.1 17 | assert random.random() == 0.1 18 | 19 | ############################################################################## 20 | 21 | 22 | import os 23 | from unittest import mock 24 | 25 | 26 | def delete_file(filename): 27 | while os.path.exists(filename): 28 | os.unlink(filename) 29 | 30 | 31 | @mock.patch('os.path.exists', side_effect=(True, False, False)) 32 | @mock.patch('os.unlink') 33 | def test_delete_file(mock_exists, mock_unlink): 34 | # First try: 35 | delete_file('some non-existing file') 36 | 37 | # Second try: 38 | delete_file('some non-existing file') 39 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_21_pytest_monkeypatch.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def test_chdir_monkeypatch(monkeypatch): 5 | monkeypatch.chdir('/') 6 | assert os.getcwd() == '/' 7 | 8 | 9 | def test_chdir(): 10 | original_directory = os.getcwd() 11 | try: 12 | os.chdir('/') 13 | assert os.getcwd() == '/' 14 | finally: 15 | os.chdir(original_directory) 16 | 17 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_22_tox/pytest.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_10_testing_and_logging/T_22_tox/pytest.ini -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_22_tox/test.py: -------------------------------------------------------------------------------- 1 | def test_dict_merge(): 2 | a = dict(a=123) 3 | b = dict(b=456) 4 | assert a | b 5 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_22_tox/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3{8,9} 3 | skipsdist = True 4 | 5 | [testenv] 6 | deps = 7 | pytest 8 | commands = 9 | pytest test.py 10 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_23_logging_basic.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.debug('debug') 4 | logging.info('info') 5 | logging.warning('warning') 6 | logging.error('error') 7 | logging.critical('critical') 8 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_24_logging_basic_formatted.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log_format = ( 4 | '%(levelname)-8s %(name)-12s %(message)s') 5 | 6 | logging.basicConfig( 7 | filename='debug.log', 8 | format=log_format, 9 | level=logging.DEBUG, 10 | ) 11 | 12 | formatter = logging.Formatter(log_format) 13 | handler = logging.StreamHandler() 14 | handler.setLevel(logging.WARNING) 15 | handler.setFormatter(formatter) 16 | logging.getLogger().addHandler(handler) 17 | 18 | 19 | logging.debug('debug') 20 | logging.info('info') 21 | some_logger = logging.getLogger('some') 22 | some_logger.warning('warning') 23 | some_logger.error('error') 24 | other_logger = some_logger.getChild('other') 25 | other_logger.critical('critical') 26 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_25_logging_dictconfig.py: -------------------------------------------------------------------------------- 1 | from logging import config 2 | 3 | config.dictConfig({ 4 | 'version': 1, 5 | 'formatters': { 6 | 'standard': { 7 | 'format': '%(levelname)-8s %(name)-12s %(message)s', 8 | }, 9 | }, 10 | 'handlers': { 11 | 'file': { 12 | 'filename': 'debug.log', 13 | 'level': 'DEBUG', 14 | 'class': 'logging.FileHandler', 15 | 'formatter': 'standard', 16 | }, 17 | 'stream': { 18 | 'level': 'WARNING', 19 | 'class': 'logging.StreamHandler', 20 | 'formatter': 'standard', 21 | }, 22 | }, 23 | 'loggers': { 24 | '': { 25 | 'handlers': ['file', 'stream'], 26 | 'level': 'DEBUG', 27 | }, 28 | }, 29 | }) 30 | 31 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_26_logging_json_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "formatters": { 4 | "standard": { 5 | "format": "%(levelname)-8s %(name)-12s %(message)s" 6 | } 7 | }, 8 | "handlers": { 9 | "file": { 10 | "filename": "debug.log", 11 | "level": "DEBUG", 12 | "class": "logging.FileHandler", 13 | "formatter": "standard" 14 | }, 15 | "stream": { 16 | "level": "WARNING", 17 | "class": "logging.StreamHandler", 18 | "formatter": "standard" 19 | } 20 | }, 21 | "loggers": { 22 | "": { 23 | "handlers": ["file", "stream"], 24 | "level": "DEBUG" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_26_logging_json_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from logging import config 4 | 5 | 6 | name = os.path.splitext(__file__)[0] 7 | json_filename = os.path.join(os.path.dirname(__file__), 8 | f'{name}.json') 9 | with open(json_filename) as fh: 10 | config.dictConfig(json.load(fh)) 11 | 12 | 13 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_27_logging_ini_config.ini: -------------------------------------------------------------------------------- 1 | [formatters] 2 | keys=standard 3 | 4 | [handlers] 5 | keys=file,stream 6 | 7 | [loggers] 8 | keys=root 9 | 10 | [formatter_standard] 11 | format=%(levelname)-8s %(name)-12s %(message)s 12 | 13 | [handler_file] 14 | level=DEBUG 15 | class=FileHandler 16 | formatter=standard 17 | args=('debug.log',) 18 | 19 | [handler_stream] 20 | level=WARNING 21 | class=StreamHandler 22 | formatter=standard 23 | args=(sys.stderr,) 24 | 25 | [logger_root] 26 | handlers=file,stream 27 | level=DEBUG 28 | 29 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_27_logging_ini_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from logging import config 3 | 4 | name = os.path.splitext(__file__)[0] 5 | 6 | config.fileConfig(os.path.join(os.path.dirname(__file__), 7 | f'{name}.ini')) 8 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_28_logging_network_config.ini: -------------------------------------------------------------------------------- 1 | [formatters] 2 | keys=standard 3 | 4 | [handlers] 5 | keys=file,stream 6 | 7 | [loggers] 8 | keys=root,some 9 | 10 | [formatter_standard] 11 | format=%(levelname)-8s %(name)-12s %(message)s 12 | 13 | [handler_file] 14 | level=DEBUG 15 | class=FileHandler 16 | formatter=standard 17 | args=('debug.log',) 18 | 19 | [handler_stream] 20 | level=WARNING 21 | class=StreamHandler 22 | formatter=standard 23 | args=(sys.stderr,) 24 | 25 | [logger_root] 26 | handlers=file,stream 27 | level=DEBUG 28 | 29 | [logger_some] 30 | level=DEBUG 31 | qualname=some 32 | handlers= 33 | 34 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_29_named_logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class MyClass(object): 8 | 9 | def __init__(self, count): 10 | self.logger = logger.getChild(self.__class__.__name__) 11 | 12 | ################################################################## 13 | 14 | 15 | import logging 16 | 17 | logger = logging.getLogger('main_module.sub_module') 18 | logger.addHandler(logging.FileHandler('sub_module.log')) 19 | 20 | ################################################################## 21 | 22 | 23 | import logging 24 | 25 | logger = logging.getLogger('main_module.sub_module') 26 | logger.setLevel(logging.DEBUG) 27 | 28 | ################################################################## 29 | 30 | 31 | import logging 32 | 33 | logger = logging.getLogger() 34 | exception = 'Oops...' 35 | logger.error('Some horrible error: %r', exception) 36 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_30_formatting.py: -------------------------------------------------------------------------------- 1 | 2 | ################################################################## 3 | 4 | 5 | import logging 6 | 7 | logger = logging.getLogger() 8 | logger.error('simple error', extra=dict(some_variable='my value')) 9 | 10 | ################################################################## 11 | 12 | 13 | import logging 14 | 15 | logging.basicConfig(format='%(some_variable)s: %(message)s') 16 | logger = logging.getLogger() 17 | logger.error('the message', extra=dict(some_variable='my value')) 18 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_31_exception.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.basicConfig() 4 | logger = logging.getLogger() 5 | 6 | try: 7 | raise RuntimeError('some runtime error') 8 | except Exception as exception: 9 | logger.exception('Got an exception: %s', exception) 10 | 11 | logger.error('And an error') 12 | 13 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_32_str_format.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | formatter = logging.Formatter('{levelname} {message}', style='{') 5 | handler = logging.StreamHandler() 6 | handler.setFormatter(formatter) 7 | 8 | logging.error('formatted message?') 9 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_33_logging_format.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class FormattingMessage: 5 | 6 | def __init__(self, message, kwargs): 7 | self.message = message 8 | self.kwargs = kwargs 9 | 10 | def __str__(self): 11 | return self.message.format(**self.kwargs) 12 | 13 | 14 | class FormattingAdapter(logging.LoggerAdapter): 15 | 16 | def process(self, msg, kwargs): 17 | msg, kwargs = super().process(msg, kwargs) 18 | return FormattingMessage(msg, kwargs), dict() 19 | 20 | 21 | logger = FormattingAdapter(logging.root, dict()) 22 | logger.error('Hi {name}', name='Rick') 23 | 24 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_34_logging_pitfalls.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | a = logging.getLogger('a') 5 | ab = logging.getLogger('a.b') 6 | 7 | ab.error('before setting level') 8 | a.setLevel(logging.CRITICAL) 9 | ab.error('after setting level') 10 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/T_35_logging_pitfalls.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | ab = logging.getLogger('a.b') 5 | ab.setLevel(logging.ERROR) 6 | ab.propagate = False 7 | ab.addHandler(logging.StreamHandler()) 8 | 9 | a = logging.getLogger('a') 10 | 11 | ab.error('before setting level') 12 | a.setLevel(logging.CRITICAL) 13 | ab.error('after setting level') 14 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_10_testing_and_logging/__init__.py -------------------------------------------------------------------------------- /CH_10_testing_and_logging/_coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | # The test coverage you require, keeping to 100% is not easily 3 | # possible for all projects but it’s a good default for new projects. 4 | fail_under = 100 5 | 6 | # These functions are generally only needed for debugging and/or 7 | # extra safety so we want to ignore them from the coverage 8 | # requirements 9 | exclude_lines = 10 | # Make it possible to ignore blocks of code 11 | pragma: no cover 12 | 13 | # Generally only debug code uses this 14 | def __repr__ 15 | 16 | # If a debug setting is set, skip testing 17 | if self\.debug: 18 | if settings.DEBUG 19 | 20 | # Don’t worry about safety checks and expected errors 21 | raise AssertionError 22 | raise NotImplementedError 23 | 24 | # Do not complain about code that will never run 25 | if 0: 26 | if __name__ == .__main__.: 27 | @abc.abstractmethod 28 | 29 | [run] 30 | # Make sure we require that all branches of the code is covered. 31 | # So both the if and the else 32 | branch = True 33 | 34 | # No need to require coverage of testing code 35 | omit = 36 | test_*.py 37 | 38 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | 9 | 10 | from T_12_assert_representation import User 11 | 12 | 13 | def is_user(value): 14 | return isinstance(value, User) 15 | 16 | 17 | def pytest_assertrepr_compare(config, op, left, right): 18 | if is_user(left) and is_user(right) and op == '==': 19 | return [ 20 | 'Comparing User instances:', 21 | f' name: {left.name} != {right.name}', 22 | ] 23 | 24 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/cube.py: -------------------------------------------------------------------------------- 1 | def cube(n: int) -> int: 2 | ''' 3 | Returns the input number, cubed 4 | 5 | Args: 6 | n (int): The number to cube 7 | 8 | Returns: 9 | int: The cubed result 10 | ''' 11 | return n ** 3 12 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/cube_root.py: -------------------------------------------------------------------------------- 1 | def cube_root(n: int) -> int: 2 | ''' 3 | Returns the cube root of the input number 4 | 5 | Args: 6 | n (int): The number to cube root 7 | 8 | Returns: 9 | int: The cube root result 10 | ''' 11 | if n >= 0: 12 | return n ** (1 / 3) 13 | else: 14 | raise ValueError('A number larger than 0 was expected') 15 | 16 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Mastering Python's documentation! 2 | ============================================ 3 | 4 | Contents: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | README 10 | 11 | Indices and tables 12 | ================== 13 | 14 | * :ref:`genindex` 15 | * :ref:`modindex` 16 | * :ref:`search` 17 | 18 | -------------------------------------------------------------------------------- /CH_10_testing_and_logging/square.py: -------------------------------------------------------------------------------- 1 | def square(n: int) -> int: 2 | ''' 3 | Returns the input number, squared 4 | 5 | >>> square(2) 6 | 4 7 | 8 | Args: 9 | n (int): The number to square 10 | 11 | Returns: 12 | int: The squared result 13 | ''' 14 | return n * n 15 | 16 | 17 | if __name__ == '__main__': 18 | import doctest 19 | doctest.testmod() 20 | 21 | -------------------------------------------------------------------------------- /CH_11_debugging/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 11, Debugging 2 | ############################################################################## 3 | 4 | | Solving bugs demonstrates several methods of hunting down bugs using tracing, logging and interactive debugging. 5 | -------------------------------------------------------------------------------- /CH_11_debugging/T_00_debugging_code_printer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import inspect 3 | import linecache 4 | 5 | 6 | def print_code(): 7 | while True: 8 | info = inspect.stack()[1] 9 | lineno = info.lineno 10 | function = info.function 11 | # Strip the path from the filename 12 | filename = os.path.split(info.filename)[-1] 13 | # Fetch the next line of code 14 | code = linecache.getline(info.filename, lineno + 1) 15 | print(f'{filename}:{lineno}:{function}: {code.strip()}') 16 | yield 17 | 18 | 19 | # Always prime the generator 20 | print_code = print_code() 21 | 22 | 23 | def some_test_function(a, b): 24 | next(print_code) 25 | c = a + b 26 | next(print_code) 27 | return c 28 | 29 | 30 | some_test_function('a', 'b') 31 | -------------------------------------------------------------------------------- /CH_11_debugging/T_01_trace.py: -------------------------------------------------------------------------------- 1 | def some_test_function(a, b): 2 | c = a + b 3 | return c 4 | 5 | 6 | print(some_test_function('a', 'b')) 7 | -------------------------------------------------------------------------------- /CH_11_debugging/T_02_selective_trace.out: -------------------------------------------------------------------------------- 1 | --- modulename: T_02_selective_trace, funcname: some_test_function 2 | 0.00 T_02_selective_trace.py(19): c = a + b 3 | 0.00 T_02_selective_trace.py(20): return c 4 | ab 5 | --- modulename: contextlib, funcname: __exit__ 6 | 0.00 contextlib.py(...): if typ... is None: 7 | 0.00 contextlib.py(...): try: 8 | 0.00 contextlib.py(...): next(self.gen) 9 | --- modulename: T_02_selective_trace, funcname: trace 10 | 0.00 T_02_selective_trace.py(12): sys.settrace(None) 11 | -------------------------------------------------------------------------------- /CH_11_debugging/T_02_selective_trace.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import trace as trace_module 3 | import contextlib 4 | 5 | 6 | @contextlib.contextmanager 7 | def trace(count=False, trace=True, timing=True): 8 | tracer = trace_module.Trace( 9 | count=count, trace=trace, timing=timing) 10 | sys.settrace(tracer.globaltrace) 11 | yield tracer 12 | sys.settrace(None) 13 | 14 | result = tracer.results() 15 | result.write_results(show_missing=False, summary=True) 16 | 17 | 18 | def some_test_function(a, b): 19 | c = a + b 20 | return c 21 | 22 | 23 | with trace(): 24 | print(some_test_function('a', 'b')) 25 | -------------------------------------------------------------------------------- /CH_11_debugging/T_03_filename_trace.out: -------------------------------------------------------------------------------- 1 | --- modulename: T_03_filename_trace, funcname: some_test_function 2 | T_03_filename_trace.py(27): c = a + b 3 | T_03_filename_trace.py(28): return c 4 | ab 5 | --- modulename: T_03_filename_trace, funcname: trace 6 | T_03_filename_trace.py(20): sys.settrace(None) 7 | lines cov% module (path) 8 | 3 100% T_03_filename_trace (/CH_11_debugging/T_03_filename_trace.py) 9 | -------------------------------------------------------------------------------- /CH_11_debugging/T_03_filename_trace.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import trace as trace_module 3 | import contextlib 4 | 5 | 6 | @contextlib.contextmanager 7 | def trace(filename): 8 | tracer = trace_module.Trace() 9 | 10 | def custom_trace(frame, event, arg): 11 | # Only trace for the given filename 12 | if filename != frame.f_code.co_filename: 13 | return custom_trace 14 | 15 | # Let globaltrace handle the rest 16 | return tracer.globaltrace(frame, event, arg) 17 | 18 | sys.settrace(custom_trace) 19 | yield tracer 20 | sys.settrace(None) 21 | 22 | result = tracer.results() 23 | result.write_results(show_missing=False, summary=True) 24 | 25 | 26 | def some_test_function(a, b): 27 | c = a + b 28 | return c 29 | 30 | 31 | # Pass our current filename as `__file__` 32 | with trace(filename=__file__): 33 | print(some_test_function('a', 'b')) 34 | -------------------------------------------------------------------------------- /CH_11_debugging/T_04_logging.out: -------------------------------------------------------------------------------- 1 | DEBUG:root:add(a=1, b=123): 124 2 | DEBUG:root:add(a=1, b=456): 457 3 | DEBUG:root:add(a=456, b=1): 457 4 | -------------------------------------------------------------------------------- /CH_11_debugging/T_04_logging.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | import inspect 3 | import logging 4 | import functools 5 | 6 | 7 | def debug(function): 8 | @functools.wraps(function) 9 | def _debug(*args, **kwargs): 10 | # Make sure `result` is always defined 11 | result = None 12 | try: 13 | result = function(*args, **kwargs) 14 | return result 15 | finally: 16 | # Extract the signature from the function 17 | signature = inspect.signature(function) 18 | # Fill the arguments 19 | arguments = signature.bind(*args, **kwargs) 20 | # NOTE: This only works for Python 3.5 and up! 21 | arguments.apply_defaults() 22 | 23 | logging.debug('%s(%s): %s' % ( 24 | function.__qualname__, 25 | ', '.join('%s=%r' % (k, v) for k, v in 26 | arguments.arguments.items()), 27 | pprint.pformat(result), 28 | )) 29 | 30 | return _debug 31 | 32 | 33 | @debug 34 | def add(a, b=123): 35 | return a + b 36 | 37 | 38 | if __name__ == '__main__': 39 | logging.basicConfig(level=logging.DEBUG) 40 | 41 | add(1) 42 | add(1, 456) 43 | add(b=1, a=456) 44 | -------------------------------------------------------------------------------- /CH_11_debugging/T_05_logging_config.out: -------------------------------------------------------------------------------- 1 | [DEBUG] T_05_logging_config.py:20:_debug: add(a=1, b=123): 124 2 | [DEBUG] T_05_logging_config.py:20:_debug: add(a=1, b=456): 457 3 | [DEBUG] T_05_logging_config.py:20:_debug: add(a=456, b=1): 457 4 | -------------------------------------------------------------------------------- /CH_11_debugging/T_06_stack.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | 3 | 4 | class ShowMyStack: 5 | 6 | def run(self, limit=None): 7 | print('Before stack print') 8 | traceback.print_stack(limit=limit) 9 | print('After stack print') 10 | 11 | 12 | class InheritShowMyStack(ShowMyStack): 13 | pass 14 | 15 | 16 | if __name__ == '__main__': 17 | show_stack = InheritShowMyStack() 18 | 19 | print('Stack without limit') 20 | show_stack.run() 21 | print() 22 | 23 | print('Stack with limit 1') 24 | show_stack.run(1) 25 | -------------------------------------------------------------------------------- /CH_11_debugging/T_07_faulthandler.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | # Get memory address 0, your kernel shouldn't allow this: 4 | ctypes.string_at(0) 5 | -------------------------------------------------------------------------------- /CH_11_debugging/T_08_faulthandler_try_catch.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | 3 | try: 4 | # Get memory address 0, your kernel shouldn't allow this: 5 | ctypes.string_at(0) 6 | except Exception as e: 7 | print('Got exception:', e) 8 | -------------------------------------------------------------------------------- /CH_11_debugging/T_09_faulthandler_enabled.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import faulthandler 3 | 4 | faulthandler.enable() 5 | 6 | # Get memory address 0, your kernel shouldn't allow this: 7 | ctypes.string_at(0) 8 | -------------------------------------------------------------------------------- /CH_11_debugging/T_10_console.py: -------------------------------------------------------------------------------- 1 | import code 2 | 3 | 4 | def start_console(): 5 | some_variable = 123 6 | print(f'Launching console, some_variable: {some_variable}') 7 | code.interact(banner='console:', local=locals()) 8 | print(f'Exited console, some_variable: {some_variable}') 9 | 10 | 11 | if __name__ == '__main__': 12 | start_console() 13 | -------------------------------------------------------------------------------- /CH_11_debugging/T_11_pdb.py: -------------------------------------------------------------------------------- 1 | import pdb 2 | 3 | 4 | def go_to_debugger(): 5 | some_variable = 123 6 | print('Starting pdb trace') 7 | pdb.set_trace() 8 | print(f'Finished pdb, some_variable: {some_variable}') 9 | 10 | 11 | if __name__ == '__main__': 12 | go_to_debugger() 13 | -------------------------------------------------------------------------------- /CH_11_debugging/T_12_pdb_loop.py: -------------------------------------------------------------------------------- 1 | import pdb 2 | 3 | 4 | def print_value(value): 5 | print('value:', value) 6 | 7 | 8 | if __name__ == '__main__': 9 | pdb.set_trace() 10 | for i in range(5): 11 | print_value(i) 12 | -------------------------------------------------------------------------------- /CH_11_debugging/T_13_pdb_catching_exceptions.py: -------------------------------------------------------------------------------- 1 | print('This still works') 2 | 1 / 0 3 | print('We will never reach this') 4 | -------------------------------------------------------------------------------- /CH_11_debugging/T_14_pdb_commands.py: -------------------------------------------------------------------------------- 1 | def do_nothing(i): 2 | pass 3 | 4 | 5 | for i in range(10): 6 | do_nothing(i) 7 | -------------------------------------------------------------------------------- /CH_11_debugging/T_15_ipython.py: -------------------------------------------------------------------------------- 1 | def print_value(value): 2 | print('value:', value) 3 | 4 | 5 | if __name__ == '__main__': 6 | for i in range(5): 7 | print_value(i) 8 | -------------------------------------------------------------------------------- /CH_11_debugging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_11_debugging/__init__.py -------------------------------------------------------------------------------- /CH_12_performance/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 12, Performance 2 | ############################################################################## 3 | 4 | | Tracking and Reducing your Memory and CPU Usage shows several methods of measuring and improving CPU and memory usage. 5 | -------------------------------------------------------------------------------- /CH_12_performance/T_01_timeit_main.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | timeit.main(args=['[x for x in range(1000000)]']) 4 | -------------------------------------------------------------------------------- /CH_12_performance/T_03_profile_fibonacci.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import functools 3 | 4 | 5 | @functools.lru_cache() 6 | def fibonacci_cached(n): 7 | if n < 2: 8 | return n 9 | else: 10 | return fibonacci_cached(n - 1) + fibonacci_cached(n - 2) 11 | 12 | 13 | def fibonacci(n): 14 | if n < 2: 15 | return n 16 | else: 17 | return fibonacci(n - 1) + fibonacci(n - 2) 18 | 19 | 20 | if __name__ == '__main__': 21 | n = 30 22 | if sys.argv[-1] == 'cache': 23 | fibonacci_cached(n) 24 | else: 25 | fibonacci(n) 26 | 27 | -------------------------------------------------------------------------------- /CH_12_performance/T_04_profiler_calibration.py: -------------------------------------------------------------------------------- 1 | import profile 2 | 3 | if __name__ == '__main__': 4 | profiler = profile.Profile() 5 | for i in range(10): 6 | print(profiler.calibrate(100000)) 7 | 8 | ############################################################################## 9 | 10 | import profile 11 | 12 | 13 | # The number here is bias calculated earlier 14 | profile.Profile.bias = 9.809351906482531e-07 15 | 16 | ############################################################################## 17 | 18 | import profile 19 | 20 | 21 | profiler = profile.Profile(bias=9.809351906482531e-07) 22 | -------------------------------------------------------------------------------- /CH_12_performance/T_05_profiler_large_bias.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pstats 3 | import profile 4 | import functools 5 | 6 | 7 | @functools.lru_cache() 8 | def fibonacci_cached(n): 9 | if n < 2: 10 | return n 11 | else: 12 | return fibonacci_cached(n - 1) + fibonacci_cached(n - 2) 13 | 14 | 15 | def fibonacci(n): 16 | if n < 2: 17 | return n 18 | else: 19 | return fibonacci(n - 1) + fibonacci(n - 2) 20 | 21 | 22 | if __name__ == '__main__': 23 | profiler = profile.Profile(bias=9.809351906482531e-07) 24 | n = 30 25 | 26 | if sys.argv[-1] == 'cache': 27 | profiler.runcall(fibonacci_cached, n) 28 | else: 29 | profiler.runcall(fibonacci, n) 30 | 31 | stats = pstats.Stats(profiler).sort_stats('calls') 32 | stats.print_stats() 33 | 34 | ############################################################################## 35 | 36 | import profile 37 | 38 | 39 | if __name__ == '__main__': 40 | profiler = profile.Profile() 41 | profiler.bias = profiler.calibrate(100000) 42 | -------------------------------------------------------------------------------- /CH_12_performance/T_06_selective_profiling.py: -------------------------------------------------------------------------------- 1 | import cProfile 2 | import datetime 3 | import functools 4 | 5 | 6 | def timer(function): 7 | @functools.wraps(function) 8 | def _timer(*args, **kwargs): 9 | start = datetime.datetime.now() 10 | try: 11 | return function(*args, **kwargs) 12 | finally: 13 | end = datetime.datetime.now() 14 | print(f'{function.__name__}: {end - start}') 15 | return _timer 16 | 17 | 18 | def profiler(function): 19 | @functools.wraps(function) 20 | def _profiler(*args, **kwargs): 21 | profiler = cProfile.Profile() 22 | try: 23 | profiler.enable() 24 | return function(*args, **kwargs) 25 | finally: 26 | profiler.disable() 27 | profiler.print_stats() 28 | return _profiler 29 | 30 | 31 | @profiler 32 | def profiled_fibonacci(n): 33 | return fibonacci(n) 34 | 35 | 36 | @timer 37 | def timed_fibonacci(n): 38 | return fibonacci(n) 39 | 40 | 41 | def fibonacci(n): 42 | if n < 2: 43 | return n 44 | else: 45 | return fibonacci(n - 1) + fibonacci(n - 2) 46 | 47 | 48 | if __name__ == '__main__': 49 | timed_fibonacci(32) 50 | profiled_fibonacci(32) 51 | 52 | -------------------------------------------------------------------------------- /CH_12_performance/T_07_profile_statistics.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | import pstats 4 | import cProfile 5 | 6 | import pyperformance 7 | 8 | # pyperformance doesn't expose the benchmarks anymore so we need 9 | # to manually add the path 10 | pyperformance_path = pathlib.Path(pyperformance.__file__).parent 11 | sys.path.append(str(pyperformance_path / 'data-files')) 12 | 13 | # Now we can import the benchmark 14 | from benchmarks.bm_float import run_benchmark as bm_float # noqa 15 | 16 | 17 | def benchmark(): 18 | for i in range(10): 19 | bm_float.benchmark(bm_float.POINTS) 20 | 21 | 22 | if __name__ == '__main__': 23 | profiler = cProfile.Profile() 24 | profiler.runcall(benchmark) 25 | profiler.dump_stats('bm_float.profile') 26 | 27 | stats = pstats.Stats('bm_float.profile') 28 | stats.strip_dirs() 29 | stats.sort_stats('calls', 'cumtime') 30 | stats.print_stats(10) 31 | -------------------------------------------------------------------------------- /CH_12_performance/T_08_line_profiler.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | 3 | 4 | @profile 5 | def primes(): 6 | n = 2 7 | primes = set() 8 | while True: 9 | for p in primes: 10 | if n % p == 0: 11 | break 12 | else: 13 | primes.add(n) 14 | yield n 15 | n += 1 16 | 17 | 18 | if __name__ == '__main__': 19 | total = 0 20 | n = 2000 21 | for prime in itertools.islice(primes(), n): 22 | total += prime 23 | 24 | print('The sum of the first %d primes is %d' % (n, total)) 25 | 26 | -------------------------------------------------------------------------------- /CH_12_performance/T_09_lists_versus_generators.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | # Without itertools.tee: 4 | 5 | >>> generator = itertools.count() 6 | >>> list(itertools.islice(generator, 5)) 7 | [0, 1, 2, 3, 4] 8 | >>> list(itertools.islice(generator, 5)) 9 | [5, 6, 7, 8, 9] 10 | 11 | >>> generator_a, generator_b = itertools.tee(itertools.count()) 12 | >>> list(itertools.islice(generator_a, 5)) 13 | [0, 1, 2, 3, 4] 14 | >>> list(itertools.islice(generator_b, 5)) 15 | [0, 1, 2, 3, 4] 16 | -------------------------------------------------------------------------------- /CH_12_performance/T_10_slots_performance.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | import pytest 3 | import functools 4 | 5 | 6 | class WithSlots: 7 | __slots__ = 'eggs', 8 | 9 | 10 | class WithoutSlots: 11 | pass 12 | 13 | 14 | with_slots = WithSlots() 15 | no_slots = WithoutSlots() 16 | 17 | 18 | @pytest.mark.skip() 19 | def test_set(obj): 20 | obj.eggs = 5 21 | 22 | 23 | @pytest.mark.skip() 24 | def test_get(obj): 25 | return obj.eggs 26 | 27 | 28 | timer = functools.partial( 29 | timeit.timeit, 30 | number=20000000, 31 | setup='\n'.join(( 32 | f'from {__name__} import with_slots, no_slots', 33 | f'from {__name__} import test_get, test_set', 34 | )), 35 | ) 36 | for function in 'test_set', 'test_get': 37 | print(function) 38 | print('with slots', timer(f'{function}(with_slots)')) 39 | print('with slots', timer(f'{function}(no_slots)')) 40 | -------------------------------------------------------------------------------- /CH_12_performance/T_11_tracemalloc.py: -------------------------------------------------------------------------------- 1 | import tracemalloc 2 | 3 | 4 | if __name__ == '__main__': 5 | tracemalloc.start() 6 | 7 | # Reserve some memory 8 | x = list(range(1000000)) 9 | 10 | # Import some modules 11 | import os 12 | import sys 13 | import asyncio 14 | 15 | assert os 16 | assert sys 17 | assert asyncio 18 | 19 | # Take a snapshot to calculate the memory usage 20 | snapshot = tracemalloc.take_snapshot() 21 | for statistic in snapshot.statistics('lineno')[:10]: 22 | print(statistic) 23 | 24 | -------------------------------------------------------------------------------- /CH_12_performance/T_12_memory_profiler.py: -------------------------------------------------------------------------------- 1 | try: 2 | import memory_profiler 3 | except ImportError: 4 | print('Please install the memory profiler to run this example') 5 | print('pip install -U memory-profiler') 6 | else: 7 | @memory_profiler.profile 8 | def main(): 9 | n = 100000 10 | a = [i for i in range(n)] 11 | b = [i for i in range(n)] 12 | c = list(range(n)) 13 | d = list(range(n)) 14 | e = dict.fromkeys(a, b) 15 | f = dict.fromkeys(c, d) 16 | assert e 17 | assert f 18 | 19 | if __name__ == '__main__': 20 | main() 21 | 22 | -------------------------------------------------------------------------------- /CH_12_performance/T_13_memory_leaks.py: -------------------------------------------------------------------------------- 1 | import tracemalloc 2 | 3 | 4 | class SomeClass: 5 | pass 6 | 7 | 8 | if __name__ == '__main__': 9 | # Initialize some variables to ignore them from the leak 10 | # detection 11 | n = 100000 12 | 13 | tracemalloc.start() 14 | # Your application should initialize here 15 | 16 | snapshot_a = tracemalloc.take_snapshot() 17 | instances = [] 18 | 19 | # This code should be the memory leaking part 20 | for i in range(n): 21 | a = SomeClass() 22 | b = SomeClass() 23 | # Circular reference. a references b, b references a 24 | a.b = b 25 | b.a = a 26 | # Force Python to keep the object in memory for now 27 | instances.append(a) 28 | 29 | # Clear the list of items again. Now all memory should be 30 | # released, right? 31 | del instances 32 | snapshot_b = tracemalloc.take_snapshot() 33 | 34 | statistics = snapshot_b.compare_to(snapshot_a, 'lineno') 35 | for statistic in statistics[:10]: 36 | print(statistic) 37 | -------------------------------------------------------------------------------- /CH_12_performance/T_14_garbage_collection.py: -------------------------------------------------------------------------------- 1 | import gc 2 | 3 | 4 | class SomeClass(object): 5 | 6 | def __init__(self, name): 7 | self.name = name 8 | 9 | def __repr__(self): 10 | return f'<{self.__class__.__name__}: {self.name}' 11 | 12 | 13 | # Create the objects 14 | a = SomeClass('a') 15 | b = SomeClass('b') 16 | 17 | # Add some circular references 18 | a.b = a 19 | b.a = b 20 | 21 | # Remove the objects 22 | del a 23 | del b 24 | 25 | # See if the objects are still there 26 | print('Before manual collection:') 27 | for object_ in gc.get_objects(): 28 | if isinstance(object_, SomeClass): 29 | print('\t', object_, gc.get_referents(object_)) 30 | 31 | print('After manual collection:') 32 | gc.collect() 33 | for object_ in gc.get_objects(): 34 | if isinstance(object_, SomeClass): 35 | print('\t', object_, gc.get_referents(object_)) 36 | 37 | print('Thresholds:', gc.get_threshold()) 38 | -------------------------------------------------------------------------------- /CH_12_performance/T_15_garbage_collector_viewing.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import collections 3 | 4 | 5 | if __name__ == '__main__': 6 | objects = collections.Counter() 7 | for object_ in gc.get_objects(): 8 | objects[type(object_)] += 1 9 | 10 | print(f'Different object count: {len(objects)}') 11 | for object_, count in objects.most_common(10): 12 | print(f'{count}: {object_}') 13 | -------------------------------------------------------------------------------- /CH_12_performance/T_16_weak_references.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import weakref 3 | 4 | 5 | class SomeClass(object): 6 | 7 | def __init__(self, name): 8 | self.name = name 9 | 10 | def __repr__(self): 11 | return '<%s: %s>' % (self.__class__.__name__, self.name) 12 | 13 | 14 | def print_mem(message): 15 | print(message) 16 | for object_ in gc.get_objects(): 17 | if isinstance(object_, SomeClass): 18 | print('\t', object_, gc.get_referents(object_)) 19 | 20 | 21 | # Create the objects 22 | a = SomeClass('a') 23 | b = SomeClass('b') 24 | 25 | # Add some weak circular references 26 | a.b = weakref.ref(a) 27 | b.a = weakref.ref(b) 28 | 29 | print_mem('Objects in memory before del:') 30 | 31 | # Remove the objects 32 | del a 33 | del b 34 | 35 | # See if the objects are still there 36 | print_mem('Objects in memory after del:') 37 | -------------------------------------------------------------------------------- /CH_12_performance/T_18_freeing_memory.py: -------------------------------------------------------------------------------- 1 | import os 2 | import psutil 3 | 4 | 5 | def print_usage(message): 6 | process = psutil.Process(os.getpid()) 7 | usage = process.memory_info().rss / (1 << 20) 8 | print(f'Memory usage {message}: {usage:.1f} MiB') 9 | 10 | 11 | def allocate_and_release(): 12 | # Allocate large block of memory 13 | large_list = list(range(1000000)) 14 | print_usage('after allocation') 15 | 16 | del large_list 17 | print_usage('after releasing') 18 | 19 | 20 | print_usage('initial') 21 | allocate_and_release() 22 | allocate_and_release() 23 | -------------------------------------------------------------------------------- /CH_12_performance/T_19_slots.py: -------------------------------------------------------------------------------- 1 | 2 | class Slots(object): 3 | __slots__ = 'index', 'name', 'description' 4 | 5 | def __init__(self, index): 6 | self.index = index 7 | self.name = 'slot %d' % index 8 | self.description = 'some slot with index %d' % index 9 | 10 | 11 | class NoSlots(object): 12 | 13 | def __init__(self, index): 14 | self.index = index 15 | self.name = 'slot %d' % index 16 | self.description = 'some slot with index %d' % index 17 | 18 | 19 | try: 20 | import memory_profiler 21 | except ImportError: 22 | print('Please install the memory profiler to run this example') 23 | print('pip install -U memory-profiler') 24 | else: 25 | @memory_profiler.profile 26 | def main(): 27 | slots = [Slots(i) for i in range(25000)] 28 | no_slots = [NoSlots(i) for i in range(25000)] 29 | return slots, no_slots 30 | 31 | if __name__ == '__main__': 32 | main() 33 | 34 | -------------------------------------------------------------------------------- /CH_12_performance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_12_performance/__init__.py -------------------------------------------------------------------------------- /CH_13_async_io/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 7, Async IO 2 | ############################################################################## 3 | 4 | | Multithreading without Threads demonstrates the usage of asynchronous functions using async def and await so external resources no longer stall your Python processes. 5 | -------------------------------------------------------------------------------- /CH_13_async_io/T_000X_benchmark.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | 5 | async def slow_blocking_function(sleep): 6 | # Note: you should never use time.sleep() in an async function 7 | # Always use asyncio.sleep instead 8 | time.sleep(sleep) 9 | 10 | 11 | print('Slow:') 12 | asyncio.run(slow_blocking_function(0.5), debug=True) 13 | print('Fast:') 14 | asyncio.run(slow_blocking_function(0.05), debug=True) 15 | -------------------------------------------------------------------------------- /CH_13_async_io/T_01_single_threaded_parallel_processing.rst: -------------------------------------------------------------------------------- 1 | >>> import time 2 | >>> import asyncio 3 | 4 | >>> def normal_sleep(): 5 | ... print('before sleep') 6 | ... time.sleep(1) 7 | ... print('after sleep') 8 | 9 | 10 | >>> def normal_sleeps(n): 11 | ... for _ in range(n): 12 | ... normal_sleep() 13 | 14 | 15 | # Normal execution 16 | 17 | >>> start = time.time() 18 | >>> normal_sleeps(2) 19 | before sleep 20 | after sleep 21 | before sleep 22 | after sleep 23 | >>> print(f'duration: {time.time() - start:.0f}') 24 | duration: 2 25 | 26 | ################################################################## 27 | 28 | >>> async def asyncio_sleep(): 29 | ... print('before sleep') 30 | ... await asyncio.sleep(1) 31 | ... print('after sleep') 32 | 33 | 34 | >>> async def asyncio_sleeps(n): 35 | ... coroutines = [] 36 | ... for _ in range(n): 37 | ... coroutines.append(asyncio_sleep()) 38 | ... 39 | ... await asyncio.gather(*coroutines) 40 | 41 | >>> start = time.time() 42 | >>> asyncio.run(asyncio_sleeps(2)) 43 | before sleep 44 | before sleep 45 | after sleep 46 | after sleep 47 | >>> print(f'duration: {time.time() - start:.0f}') 48 | duration: 1 49 | 50 | -------------------------------------------------------------------------------- /CH_13_async_io/T_02_select_event_loop.rst: -------------------------------------------------------------------------------- 1 | >>> import asyncio 2 | >>> import selectors 3 | 4 | 5 | >>> selector = selectors.SelectSelector() 6 | >>> loop = asyncio.SelectorEventLoop(selector) 7 | >>> asyncio.set_event_loop(loop) 8 | -------------------------------------------------------------------------------- /CH_13_async_io/T_03_uvloop_policy.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | class UvLoopPolicy(asyncio.DefaultEventLoopPolicy): 5 | def new_event_loop(self): 6 | try: 7 | from uvloop import Loop 8 | return Loop() 9 | except ImportError: 10 | return super().new_event_loop() 11 | 12 | 13 | asyncio.set_event_loop_policy(UvLoopPolicy()) 14 | -------------------------------------------------------------------------------- /CH_13_async_io/T_04_event_loop_usage.rst: -------------------------------------------------------------------------------- 1 | >>> import time 2 | >>> import asyncio 3 | 4 | 5 | >>> def printer(name): 6 | ... print(f'Started {name} at {loop.time() - offset:.1f}') 7 | ... time.sleep(0.2) 8 | ... print(f'Finished {name} at {loop.time() - offset:.1f}') 9 | 10 | >>> loop = asyncio.new_event_loop() 11 | >>> _ = loop.call_at(loop.time() + .2, printer, 'call_at') 12 | >>> _ = loop.call_later(.1, printer, 'call_later') 13 | >>> _ = loop.call_soon(printer, 'call_soon') 14 | >>> _ = loop.call_soon_threadsafe(printer, 'call_soon_threadsafe') 15 | 16 | # Make sure we stop after a second 17 | 18 | >>> _ = loop.call_later(1, loop.stop) 19 | 20 | # Store the offset because the loop requires time to start 21 | 22 | >>> offset = loop.time() 23 | 24 | >>> loop.run_until_complete(asyncio.sleep(0)) 25 | Started call_soon at 0.0 26 | Finished call_soon at 0.2 27 | Started call_soon_threadsafe at 0.2 28 | Finished call_soon_threadsafe at 0.4 29 | Started call_later at 0.4 30 | Finished call_later at 0.6 31 | Started call_at at 0.6 32 | Finished call_at at 0.8 33 | -------------------------------------------------------------------------------- /CH_13_async_io/T_05_executors.rst: -------------------------------------------------------------------------------- 1 | >>> import time 2 | >>> import asyncio 3 | 4 | 5 | >>> def executor_sleep(): 6 | ... print('before sleep') 7 | ... time.sleep(1) 8 | ... print('after sleep') 9 | 10 | 11 | >>> async def executor_sleeps(n): 12 | ... loop = asyncio.get_running_loop() 13 | ... futures = [] 14 | ... for _ in range(n): 15 | ... future = loop.run_in_executor(None, executor_sleep) 16 | ... futures.append(future) 17 | ... 18 | ... await asyncio.gather(*futures) 19 | 20 | >>> start = time.time() 21 | >>> asyncio.run(executor_sleeps(2)) 22 | before sleep 23 | before sleep 24 | after sleep 25 | after sleep 26 | >>> print(f'duration: {time.time() - start:.0f}') 27 | duration: 1 28 | -------------------------------------------------------------------------------- /CH_13_async_io/T_06_process_executors.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | import concurrent.futures 4 | 5 | 6 | def executor_sleep(): 7 | print('before sleep') 8 | time.sleep(1) 9 | print('after sleep') 10 | 11 | 12 | async def executor_sleeps(n): 13 | loop = asyncio.get_running_loop() 14 | futures = [] 15 | with concurrent.futures.ProcessPoolExecutor() as pool: 16 | for _ in range(n): 17 | future = loop.run_in_executor(pool, executor_sleep) 18 | futures.append(future) 19 | 20 | await asyncio.gather(*futures) 21 | 22 | 23 | if __name__ == '__main__': 24 | start = time.time() 25 | asyncio.run(executor_sleeps(2)) 26 | print(f'duration: {time.time() - start:.0f}') 27 | -------------------------------------------------------------------------------- /CH_13_async_io/T_09_aiofiles.rst: -------------------------------------------------------------------------------- 1 | >>> import asyncio 2 | >>> import aiofiles 3 | 4 | >>> async def main(): 5 | ... async with aiofiles.open('aiofiles.txt', 'w') as fh: 6 | ... await fh.write('Writing to file') 7 | ... 8 | ... async with aiofiles.open('aiofiles.txt', 'r') as fh: 9 | ... async for line in fh: 10 | ... print(line) 11 | 12 | 13 | >>> asyncio.run(main()) 14 | Writing to file 15 | -------------------------------------------------------------------------------- /CH_13_async_io/T_10_async_for.rst: -------------------------------------------------------------------------------- 1 | >>> import asyncio 2 | 3 | 4 | >>> class AsyncGenerator: 5 | ... def __init__(self, iterable): 6 | ... self.iterable = iterable 7 | ... 8 | ... async def __aiter__(self): 9 | ... for item in self.iterable: 10 | ... yield item 11 | 12 | 13 | >>> async def main(): 14 | ... async_generator = AsyncGenerator([4, 2]) 15 | ... 16 | ... async for item in async_generator: 17 | ... print(f'Got item: {item}') 18 | 19 | >>> asyncio.run(main()) 20 | Got item: 4 21 | Got item: 2 22 | -------------------------------------------------------------------------------- /CH_13_async_io/T_11_async_with.rst: -------------------------------------------------------------------------------- 1 | >>> import asyncio 2 | 3 | 4 | >>> class AsyncContextManager: 5 | ... async def __aenter__(self): 6 | ... print('Hi :)') 7 | ... 8 | ... async def __aexit__(self, exc_type, exc_value, traceback): 9 | ... print('Bye :(') 10 | 11 | 12 | >>> async def main(): 13 | ... async_context_manager = AsyncContextManager() 14 | ... 15 | ... print('Before with') 16 | ... async with async_context_manager: 17 | ... print('During with') 18 | ... print('After with') 19 | 20 | >>> asyncio.run(main()) 21 | Before with 22 | Hi :) 23 | During with 24 | Bye :( 25 | After with 26 | -------------------------------------------------------------------------------- /CH_13_async_io/T_12_constructors_and_destructors.rst: -------------------------------------------------------------------------------- 1 | >>> import asyncio 2 | 3 | 4 | >>> class SomeClass: 5 | ... def __init__(self, *args, **kwargs): 6 | ... print('Sync init') 7 | ... 8 | ... async def init(self, *args, **kwargs): 9 | ... print('Async init') 10 | ... 11 | ... @classmethod 12 | ... async def create(cls, *args, **kwargs): 13 | ... # Create an instance of `SomeClass` which calls the 14 | ... # sync init: `SomeClass.__init__(*args, **kwargs)` 15 | ... self = cls(*args, **kwargs) 16 | ... # Now we can call the async init: 17 | ... await self.init(*args, **kwargs) 18 | ... return self 19 | ... 20 | ... async def close(self): 21 | ... print('Async destructor') 22 | ... 23 | ... def __del__(self): 24 | ... print('Sync destructor') 25 | 26 | 27 | >>> async def main(): 28 | ... # Note that we use `SomeClass.create()` instead of 29 | ... # `SomeClass()` so we also run `SomeClass().init()` 30 | ... some_class = await SomeClass.create() 31 | ... print('Using the class here') 32 | ... await some_class.close() 33 | ... del some_class 34 | 35 | >>> asyncio.run(main()) 36 | Sync init 37 | Async init 38 | Using the class here 39 | Async destructor 40 | Sync destructor 41 | -------------------------------------------------------------------------------- /CH_13_async_io/T_13_forgot_await.py: -------------------------------------------------------------------------------- 1 | async def printer(): 2 | print('This is a coroutine') 3 | 4 | 5 | printer() 6 | -------------------------------------------------------------------------------- /CH_13_async_io/T_14_slow_blocking_code.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | 4 | 5 | async def main(): 6 | # Oh no... a synchronous sleep from asyncio code 7 | time.sleep(0.2) 8 | 9 | 10 | asyncio.run(main(), debug=True) 11 | -------------------------------------------------------------------------------- /CH_13_async_io/T_15_forgotten_exception.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def throw_exception(): 5 | raise RuntimeError() 6 | 7 | 8 | async def main(): 9 | # ignoring an exception from an async def 10 | asyncio.create_task(throw_exception()) 11 | 12 | 13 | asyncio.run(main()) 14 | -------------------------------------------------------------------------------- /CH_13_async_io/T_16_early_exit.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def sub_printer(): 5 | print('Hi from the sub-printer') 6 | 7 | 8 | async def printer(): 9 | print('Before creating the sub-printer task') 10 | asyncio.create_task(sub_printer()) 11 | print('After creating the sub-printer task') 12 | 13 | 14 | async def main(): 15 | asyncio.create_task(printer()) 16 | 17 | asyncio.run(main()) 18 | -------------------------------------------------------------------------------- /CH_13_async_io/T_17_wait_for_exit.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def sub_printer(): 5 | print('Hi from the sub-printer') 6 | 7 | 8 | async def printer(): 9 | print('Before creating the sub-printer task') 10 | asyncio.create_task(sub_printer()) 11 | print('After creating the sub-printer task') 12 | 13 | 14 | async def main(): 15 | asyncio.create_task(printer()) 16 | await asyncio.sleep(0.1) 17 | 18 | asyncio.run(main()) 19 | -------------------------------------------------------------------------------- /CH_13_async_io/T_18_wait_for_all_tasks.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def sub_printer(): 5 | print('Hi from the sub-printer') 6 | 7 | 8 | async def printer(): 9 | print('Before creating the sub-printer task') 10 | asyncio.create_task(sub_printer()) 11 | print('After creating the sub-printer task') 12 | 13 | 14 | async def main(): 15 | asyncio.create_task(printer()) 16 | await shutdown() 17 | 18 | 19 | async def shutdown(timeout=5): 20 | tasks = [] 21 | # Collect all tasks from `asyncio` 22 | for task in asyncio.all_tasks(): 23 | # Make sure we skip our current task so we don't loop 24 | if task is not asyncio.current_task(): 25 | tasks.append(task) 26 | 27 | for future in asyncio.as_completed(tasks, timeout=timeout): 28 | await future 29 | 30 | asyncio.run(main()) 31 | -------------------------------------------------------------------------------- /CH_13_async_io/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_13_async_io/__init__.py -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 13, Multiprocessing 2 | ############################################################################## 3 | 4 | | When a Single CPU Core is not Enough the multiprocessing library can be used to execute your code not just on multiple processors but even on multiple machines. 5 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_00_concurrent_futures.out: -------------------------------------------------------------------------------- 1 | a 0 2 | b 0 3 | a 1 4 | b 1 5 | a 2 6 | b 2 7 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_00_concurrent_futures.py: -------------------------------------------------------------------------------- 1 | import time 2 | import concurrent.futures 3 | 4 | 5 | def timer(name, steps, interval=0.1): 6 | '''timer function that sleeps `steps * interval` ''' 7 | for step in range(steps): 8 | print(name, step) 9 | time.sleep(interval) 10 | 11 | 12 | if __name__ == '__main__': 13 | # Replace with concurrent.futures.ProcessPoolExecutor for 14 | # multiple processes instead of threads 15 | with concurrent.futures.ThreadPoolExecutor() as executor: 16 | # Submit the function to the executor with some arguments 17 | executor.submit(timer, steps=3, name='a') 18 | # Sleep a tiny bit to keep the output order consistent 19 | time.sleep(0.05) 20 | executor.submit(timer, steps=3, name='b') 21 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_01_threading.out: -------------------------------------------------------------------------------- 1 | a 0 2 | b 0 3 | a 1 4 | b 1 5 | a 2 6 | b 2 7 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_01_threading.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | 4 | 5 | def timer(name, steps, interval=0.1): 6 | '''timer function that sleeps `steps * interval` ''' 7 | for step in range(steps): 8 | print(name, step) 9 | time.sleep(interval) 10 | 11 | 12 | # Create the threads declaratively 13 | a = threading.Thread(target=timer, kwargs=dict(name='a', steps=3)) 14 | b = threading.Thread(target=timer, kwargs=dict(name='b', steps=3)) 15 | 16 | # Start the threads 17 | a.start() 18 | # Sleep a tiny bit to keep the output order consistent 19 | time.sleep(0.05) 20 | b.start() 21 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_02_threading_class.out: -------------------------------------------------------------------------------- 1 | a 0 2 | b 0 3 | a 1 4 | b 1 5 | a 2 6 | b 2 7 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_02_threading_class.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | 4 | 5 | class Timer(threading.Thread): 6 | 7 | def __init__(self, name, steps, interval=0.1): 8 | self.steps = steps 9 | self.interval = interval 10 | # Small gotcha: threading.Thread has a built-in name 11 | # parameter so be careful not to manually override it 12 | super().__init__(name=name) 13 | 14 | def run(self): 15 | '''timer function that sleeps `steps * interval` ''' 16 | for step in range(self.steps): 17 | print(self.name, step) 18 | time.sleep(self.interval) 19 | 20 | 21 | a = Timer(name='a', steps=3) 22 | b = Timer(name='b', steps=3) 23 | 24 | # Start the threads 25 | a.start() 26 | # Sleep a tiny bit to keep the output order consistent 27 | time.sleep(0.05) 28 | b.start() 29 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_03_multiprocessing.out: -------------------------------------------------------------------------------- 1 | a 0 2 | a 1 3 | a 2 4 | b 0 5 | b 1 6 | b 2 7 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_03_multiprocessing.py: -------------------------------------------------------------------------------- 1 | import time 2 | import multiprocessing 3 | 4 | 5 | def timer(name, steps, interval=0.1): 6 | '''timer function that sleeps `steps * interval` ''' 7 | for step in range(steps): 8 | print(name, step) 9 | time.sleep(interval) 10 | 11 | 12 | if __name__ == '__main__': 13 | # Create the processes declaratively 14 | a = multiprocessing.Process(target=timer, kwargs=dict(name='a', steps=3)) 15 | b = multiprocessing.Process(target=timer, kwargs=dict(name='b', steps=3)) 16 | 17 | # Start the processes 18 | a.start() 19 | # Sleep a tiny bit to keep the output order consistent 20 | time.sleep(0.1) 21 | b.start() 22 | 23 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_04_multiprocessing_class.out: -------------------------------------------------------------------------------- 1 | a 0 2 | a 1 3 | a 2 4 | b 0 5 | b 1 6 | b 2 7 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_04_multiprocessing_class.py: -------------------------------------------------------------------------------- 1 | import time 2 | import multiprocessing 3 | 4 | 5 | class Timer(multiprocessing.Process): 6 | 7 | def __init__(self, name, steps, interval=0.1): 8 | self.steps = steps 9 | self.interval = interval 10 | # Similar to threading.Thread, multiprocessing.Process 11 | # also supports the name parameter but you are not 12 | # required to use it here. 13 | super().__init__(name=name) 14 | 15 | def run(self): 16 | '''timer function that sleeps `steps * interval` ''' 17 | for step in range(self.steps): 18 | print(self.name, step) 19 | time.sleep(self.interval) 20 | 21 | 22 | if __name__ == '__main__': 23 | a = Timer(name='a', steps=3) 24 | b = Timer(name='b', steps=3) 25 | 26 | # Start the processs 27 | a.start() 28 | # Sleep a tiny bit to keep the output order consistent 29 | time.sleep(0.1) 30 | b.start() 31 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_05_exiting_threads.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | 4 | 5 | class Forever(threading.Thread): 6 | 7 | def __init__(self): 8 | self.stop = threading.Event() 9 | super().__init__() 10 | 11 | def run(self): 12 | while not self.stop.is_set(): 13 | # Do whatever you need to do here 14 | time.sleep(0.1) 15 | 16 | 17 | thread = Forever() 18 | thread.start() 19 | # Do whatever you need to do here 20 | thread.stop.set() 21 | thread.join() 22 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_06_exiting_processes.py: -------------------------------------------------------------------------------- 1 | import time 2 | import multiprocessing 3 | 4 | 5 | class Forever(multiprocessing.Process): 6 | 7 | def run(self): 8 | while True: 9 | # Do whatever you need to do here 10 | time.sleep(0.1) 11 | 12 | 13 | if __name__ == '__main__': 14 | process = Forever() 15 | process.start() 16 | 17 | # Kill our "unkillable" process 18 | process.terminate() 19 | 20 | # Wait for 10 seconds to properly exit 21 | process.join(10) 22 | 23 | # If it still didn't exit, kill it 24 | if process.exitcode is None: 25 | process.kill() 26 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_07_thread_batch_processing.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | import socket 3 | import concurrent.futures 4 | 5 | 6 | def getaddrinfo(*args): 7 | # Call getaddrinfo but ignore the given parameter 8 | socket.getaddrinfo('localhost', None) 9 | 10 | 11 | def benchmark(threads, n=1000): 12 | if threads > 1: 13 | # Create the executor 14 | with concurrent.futures.ThreadPoolExecutor(threads) \ 15 | as executor: 16 | executor.map(getaddrinfo, range(n)) 17 | else: 18 | # Make sure to use `list`. Otherwise the generator will 19 | # not execute because it is lazy 20 | list(map(getaddrinfo, range(n))) 21 | 22 | 23 | if __name__ == '__main__': 24 | for threads in (1, 10, 50, 100): 25 | print(f'Testing with {threads} threads and n={10} took: ', 26 | end='') 27 | print('{:.1f}'.format(timeit.timeit( 28 | f'benchmark({threads})', 29 | setup='from __main__ import benchmark', 30 | number=10, 31 | ))) 32 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_08_process_batch_processing.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | import socket 3 | import concurrent.futures 4 | 5 | 6 | def getaddrinfo(*args): 7 | # Call getaddrinfo but ignore the given parameter 8 | socket.getaddrinfo('localhost', None) 9 | 10 | 11 | def benchmark(processes, n=1000): 12 | if processes > 1: 13 | # Create the executor 14 | with concurrent.futures.ProcessPoolExecutor(processes) \ 15 | as executor: 16 | executor.map(getaddrinfo, range(n)) 17 | else: 18 | # Make sure to use `list`. Otherwise the generator will 19 | # not execute because it is lazy 20 | list(map(getaddrinfo, range(n))) 21 | 22 | 23 | if __name__ == '__main__': 24 | for processes in (1, 10, 50, 100): 25 | print(f'Testing with {processes} processes and n={10} ' 26 | 'took: ', end='') 27 | print('{:.1f}'.format(timeit.timeit( 28 | f'benchmark({processes})', 29 | setup='from __main__ import benchmark', 30 | number=10, 31 | ))) 32 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_10_sharing_memory.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | 3 | 4 | some_int = multiprocessing.Value('i', 123) 5 | with some_int.get_lock(): 6 | some_int.value += 10 7 | print(some_int.value) 8 | 9 | some_double_array = multiprocessing.Array('d', [1, 2, 3]) 10 | with some_double_array.get_lock(): 11 | some_double_array[0] += 2.5 12 | print(some_double_array[:]) 13 | 14 | 15 | from multiprocessing import shared_memory 16 | # From process A we could write something 17 | name = 'share_a' 18 | share_a = shared_memory.SharedMemory(name, create=True, size=4) 19 | share_a.buf[0] = 10 20 | 21 | # From a different process we could access the data: 22 | share_a = shared_memory.SharedMemory(name) 23 | print(share_a.buf[0]) 24 | 25 | # Make sure to clean up after. And only once! 26 | share_a.unlink() 27 | 28 | 29 | from multiprocessing import shared_memory 30 | 31 | 32 | shared_list = shared_memory.ShareableList(['Hi', 1, False, None]) 33 | # Changing type from str to bool here 34 | shared_list[0] = True 35 | # Don't forget to unlink() 36 | shared_list.shm.unlink() 37 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_12_thread_safety.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | 3 | 4 | counter = 10 5 | 6 | 7 | def increment(name): 8 | global counter 9 | current_value = counter 10 | print(f'{name} value before increment: {current_value}') 11 | counter = current_value + 1 12 | print(f'{name} value after increment: {counter}') 13 | 14 | 15 | print(f'Before thread start: {counter}') 16 | 17 | with concurrent.futures.ThreadPoolExecutor() as executor: 18 | executor.map(increment, range(5)) 19 | 20 | print(f'After thread finish: {counter}') 21 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_13_thread_safety.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | 3 | 4 | counter = 10 5 | 6 | 7 | def increment(name): 8 | global counter 9 | current_value = counter 10 | print(f'{name} value before increment: {current_value}') 11 | counter = current_value + 1 12 | print(f'{name} value after increment: {counter}') 13 | 14 | 15 | if __name__ == '__main__': 16 | print(f'Before thread start: {counter}') 17 | 18 | with concurrent.futures.ProcessPoolExecutor() as executor: 19 | executor.map(increment, range(5)) 20 | 21 | print(f'After thread finish: {counter}') 22 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_14_deadlocks.py: -------------------------------------------------------------------------------- 1 | import time 2 | import threading 3 | 4 | 5 | a = threading.Lock() 6 | b = threading.Lock() 7 | 8 | 9 | def thread_0(): 10 | print('thread 0 locking a') 11 | with a: 12 | time.sleep(0.1) 13 | print('thread 0 locking b') 14 | with b: 15 | print('thread 0 everything locked') 16 | 17 | 18 | def thread_1(): 19 | print('thread 1 locking b') 20 | with b: 21 | time.sleep(0.1) 22 | print('thread 1 locking a') 23 | with a: 24 | print('thread 1 everything locked') 25 | 26 | 27 | threading.Thread(target=thread_0).start() 28 | threading.Thread(target=thread_1).start() 29 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_15_thread_local.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import concurrent.futures 3 | 4 | 5 | context = threading.local() 6 | 7 | 8 | def init_counter(): 9 | context.counter = 10 10 | 11 | 12 | def increment(name): 13 | current_value = context.counter 14 | print(f'{name} value before increment: {current_value}') 15 | context.counter = current_value + 1 16 | print(f'{name} value after increment: {context.counter}') 17 | 18 | 19 | init_counter() 20 | print(f'Before thread start: {context.counter}') 21 | 22 | with concurrent.futures.ThreadPoolExecutor( 23 | initializer=init_counter) as executor: 24 | executor.map(increment, range(5)) 25 | 26 | print(f'After thread finish: {context.counter}') 27 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_16_hyper_threading.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | import multiprocessing 3 | 4 | 5 | def busy_wait(n): 6 | while n > 0: 7 | n -= 1 8 | 9 | 10 | def benchmark(n, processes, tasks): 11 | with multiprocessing.Pool(processes=processes) as pool: 12 | # Execute the busy_wait function `tasks` times with 13 | # parameter n 14 | pool.map(busy_wait, [n for _ in range(tasks)]) 15 | # Create the executor 16 | 17 | 18 | if __name__ == '__main__': 19 | n = 100000 20 | tasks = 128 21 | for exponent in range(6): 22 | processes = int(2 ** exponent) 23 | statement = f'benchmark({n}, {processes}, {tasks})' 24 | result = timeit.timeit( 25 | statement, 26 | number=5, 27 | setup='from __main__ import benchmark', 28 | ) 29 | print(f'{statement}: {result:.3f}') 30 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_17_remote_multiprocessing/client.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import managers 2 | 3 | import constants 4 | 5 | 6 | manager = managers.BaseManager( 7 | address=(constants.host, constants.port), 8 | authkey=constants.password) 9 | manager.register('queue') 10 | manager.register('primes') 11 | manager.connect() 12 | 13 | queue = manager.queue() 14 | while not queue.empty(): 15 | print(manager.primes(queue.get())) 16 | 17 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_17_remote_multiprocessing/constants.py: -------------------------------------------------------------------------------- 1 | host = 'localhost' 2 | port = 12345 3 | password = b'some secret password' 4 | 5 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_17_remote_multiprocessing/functions.py: -------------------------------------------------------------------------------- 1 | def primes(n): 2 | for i, prime in enumerate(prime_generator()): 3 | if i == n: 4 | return prime 5 | 6 | 7 | def prime_generator(): 8 | n = 2 9 | primes = set() 10 | while True: 11 | for p in primes: 12 | if n % p == 0: 13 | break 14 | else: 15 | primes.add(n) 16 | yield n 17 | n += 1 18 | 19 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_17_remote_multiprocessing/server.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | from multiprocessing import managers 3 | 4 | import constants 5 | import functions 6 | 7 | queue = multiprocessing.Queue() 8 | manager = managers.BaseManager(address=('', constants.port), 9 | authkey=constants.password) 10 | 11 | manager.register('queue', callable=lambda: queue) 12 | manager.register('primes', callable=functions.primes) 13 | 14 | server = manager.get_server() 15 | server.serve_forever() 16 | 17 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_17_remote_multiprocessing/submitter.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import managers 2 | 3 | import constants 4 | 5 | manager = managers.BaseManager( 6 | address=(constants.host, constants.port), 7 | authkey=constants.password) 8 | manager.register('queue') 9 | manager.connect() 10 | 11 | queue = manager.queue() 12 | for i in range(1000): 13 | queue.put(i) 14 | 15 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_18_dask.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import datetime 3 | from dask import distributed 4 | 5 | 6 | def busy_wait(n): 7 | while n > 0: 8 | n -= 1 9 | 10 | 11 | def benchmark_dask(client): 12 | start = datetime.datetime.now() 13 | 14 | # Run up to 1 million 15 | n = 1000000 16 | tasks = int(sys.argv[1]) # get number of tasks from argv 17 | 18 | # Submit the tasks to Dask 19 | futures = client.map(busy_wait, [n] * tasks, pure=False) 20 | # Gather the results, this blocks until the results are ready 21 | client.gather(futures) 22 | 23 | duration = datetime.datetime.now() - start 24 | per_second = int(tasks / duration.total_seconds()) 25 | print(f'{tasks} tasks at {per_second} per ' 26 | f'second, total time: {duration}') 27 | 28 | 29 | if __name__ == '__main__': 30 | benchmark_dask(distributed.Client()) 31 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_19_dask_single.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import datetime 3 | from dask import distributed 4 | 5 | 6 | def busy_wait(n): 7 | while n > 0: 8 | n -= 1 9 | 10 | 11 | def benchmark_dask(client): 12 | start = datetime.datetime.now() 13 | 14 | # Run up to 1 million 15 | n = 1000000 16 | tasks = int(sys.argv[1]) # get number of tasks from argv 17 | 18 | # Submit the tasks to Dask 19 | futures = client.map(busy_wait, [n] * tasks, pure=False) 20 | # Gather the results, this blocks until the results are ready 21 | client.gather(futures) 22 | 23 | duration = datetime.datetime.now() - start 24 | per_second = int(tasks / duration.total_seconds()) 25 | print(f'{tasks} tasks at {per_second} per ' 26 | f'second, total time: {duration}') 27 | 28 | 29 | if __name__ == '__main__': 30 | benchmark_dask(distributed.Client(processes=False)) 31 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/T_20_dask_distributed.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import datetime 3 | from dask import distributed 4 | 5 | 6 | def busy_wait(n): 7 | while n > 0: 8 | n -= 1 9 | 10 | 11 | def benchmark_dask(client): 12 | start = datetime.datetime.now() 13 | 14 | # Run up to 1 million 15 | n = 1000000 16 | tasks = int(sys.argv[1]) # get number of tasks from argv 17 | 18 | # Submit the tasks to Dask 19 | futures = client.map(busy_wait, [n] * tasks, pure=False) 20 | # Gather the results, this blocks until the results are ready 21 | client.gather(futures) 22 | 23 | duration = datetime.datetime.now() - start 24 | per_second = int(tasks / duration.total_seconds()) 25 | print(f'{tasks} tasks at {per_second} per ' 26 | f'second, total time: {duration}') 27 | 28 | 29 | if __name__ == '__main__': 30 | benchmark_dask(distributed.Client('localhost:8786')) 31 | -------------------------------------------------------------------------------- /CH_14_multithreading_and_multiprocessing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_14_multithreading_and_multiprocessing/__init__.py -------------------------------------------------------------------------------- /CH_15_scientific_python/T_01_numba.rst: -------------------------------------------------------------------------------- 1 | >>> import numpy 2 | >>> import numba 3 | 4 | >>> numbers = numpy.arange(500, dtype=numpy.int64) 5 | 6 | >>> @numba.vectorize([numba.int64(numba.int64)]) 7 | ... def add_one(x): 8 | ... return x + 1 9 | 10 | >>> numbers 11 | array([ 0, 1, 2, ..., 498, 499]) 12 | 13 | >>> add_one(numbers) 14 | array([ 1, 2, 3, ..., 499, 500]) 15 | 16 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_02_scipy.rst: -------------------------------------------------------------------------------- 1 | >>> import numpy 2 | 3 | >>> from scipy import sparse 4 | 5 | >>> x = numpy.identity(10000) 6 | >>> y = sparse.identity(10000) 7 | 8 | >>> x.data.nbytes 9 | 800000000 10 | 11 | # Summing the memory usage of scipy.sparse objects require summing 12 | of all internal arrays. We can test for these arrays using the 13 | nbytes attribute. 14 | 15 | >>> arrays = [a for a in vars(y).values() if hasattr(a, 'nbytes')] 16 | 17 | # sum the bytes from all arrays 18 | 19 | >>> sum(a.nbytes for a in arrays) 20 | 80004 21 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_04_statsmodels.rst: -------------------------------------------------------------------------------- 1 | # The common shorthand for statsmodels is sm 2 | 3 | >>> import statsmodels.api as sm 4 | >>> import numpy as np 5 | 6 | >>> Y = np.arange(8) 7 | >>> X = np.ones(8) 8 | 9 | # Create the weighted-least-squares model 10 | 11 | >>> model = sm.WLS(Y, X) 12 | 13 | # Fit the model and generate the regression results 14 | 15 | >>> fit = model.fit() 16 | 17 | # Show the estimated parameters and the t-values: 18 | 19 | >>> fit.params 20 | array([3.5]) 21 | >>> fit.tvalues 22 | array([4.04145188]) 23 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_06_stumpy.rst: -------------------------------------------------------------------------------- 1 | >>> import numpy as np 2 | >>> import stumpy 3 | 4 | >>> temperatures = np.array([22., 21., 22., 21., 22., 23.]) 5 | 6 | >>> window_size = 3 7 | 8 | # Calculate a Euclidean distance matrix between the windows 9 | 10 | >>> stump = stumpy.stump(temperatures, window_size) 11 | 12 | # Show the distance matrix. The row number is the index in the 13 | input array. The first column is the distance, the next columns 14 | are the indices of the nearest match, the left match and the 15 | right match. 16 | 17 | >>> stump 18 | array([[0.0, 2, -1, 2], 19 | [2.449489742783178, 3, -1, 3], 20 | [0.0, 0, 0, -1], 21 | [2.449489742783178, 1, 1, -1]], dtype=object) 22 | 23 | # As we can see in the matrix above, the first window has a 24 | distance of 0 to the window at index 2 meaning that they are 25 | identical. We can easily verify that by showing both windows: 26 | 27 | # The first window: 28 | 29 | >>> temperatures[0:window_size] 30 | array([22., 21., 22.]) 31 | 32 | # The window at index 2: 33 | 34 | >>> temperatures[2:2 + window_size] 35 | array([22., 21., 22.]) 36 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_07_gmpy2.rst: -------------------------------------------------------------------------------- 1 | >>> import gmpy2 2 | 3 | >>> gmpy2.const_pi(1000) 4 | mpfr('3.14159265358979...33936072602491412736',1000) 5 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_08_sagemath.sage: -------------------------------------------------------------------------------- 1 | sage: x, y, z = var('x, y, z') 2 | sage: solve([x + y == 10, x - y == 5, x + y + z == 1], x, y, z) 3 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_09_mpmath.rst: -------------------------------------------------------------------------------- 1 | >>> N = 10 2 | >>> x = 0.1 3 | 4 | # Regular addition 5 | 6 | >>> a = 0.0 7 | >>> for _ in range(N): 8 | ... a += x 9 | 10 | >>> a 11 | 0.9999999999999999 12 | 13 | # Using sum, the same result as addition 14 | 15 | >>> sum(x for _ in range(N)) 16 | 0.9999999999999999 17 | 18 | ################################################################# 19 | 20 | # Sum using Python's optimized fsum: 21 | 22 | >>> import math 23 | 24 | >>> math.fsum(x for _ in range(N)) 25 | 1.0 26 | 27 | ################################################################# 28 | 29 | >>> import mpmath 30 | 31 | # Increase the mpmath precision to 100 decimal places 32 | 33 | >>> mpmath.mp.dps = 100 34 | >>> y = mpmath.mpf('0.1') 35 | 36 | # Using mpmath with addition: 37 | 38 | >>> b = mpmath.mpf('0.0') 39 | >>> for _ in range(N): 40 | ... b += y 41 | 42 | >>> b 43 | mpf('1.00000000000000000000000000...00000000000000000000000014') 44 | 45 | # Or a regular sum with mpmath: 46 | 47 | >>> sum(y for _ in range(N)) 48 | mpf('1.00000000000000000000000000...00000000000000000000000014') 49 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_10_sympy.rst: -------------------------------------------------------------------------------- 1 | >>> from sympy import * 2 | 3 | >>> init_printing(use_unicode=True) 4 | >>> x, y, z = symbols('x y z') 5 | 6 | >>> integral = Integral(x * cos(x), x) 7 | >>> integral 8 | ⌠ 9 | ⎮ x⋅cos(x) dx 10 | ⌡ 11 | >>> integral.doit() 12 | x⋅sin(x) + cos(x) 13 | -------------------------------------------------------------------------------- /CH_15_scientific_python/T_11_patsy.rst: -------------------------------------------------------------------------------- 1 | >>> import patsy 2 | >>> import numpy as np 3 | 4 | >>> array = np.arange(2, 6) 5 | 6 | >>> data = dict(a=array, b=array, c=array) 7 | >>> patsy.dmatrix('a + np.square(b) + np.power(c, 3)', data) 8 | DesignMatrix with shape (4, 4) 9 | Intercept a np.square(b) np.power(c, 3) 10 | 1 2 4 8 11 | 1 3 9 27 12 | 1 4 16 64 13 | 1 5 25 125 14 | Terms: 15 | 'Intercept' (column 0) 16 | 'a' (column 1) 17 | 'np.square(b)' (column 2) 18 | 'np.power(c, 3)' (column 3) 19 | -------------------------------------------------------------------------------- /CH_15_scientific_python/python_versions.tsv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_15_scientific_python/python_versions.tsv -------------------------------------------------------------------------------- /CH_16_machine_learning/.gitignore: -------------------------------------------------------------------------------- 1 | /yolov3.weights 2 | -------------------------------------------------------------------------------- /CH_16_machine_learning/T_02_nltk.rst: -------------------------------------------------------------------------------- 1 | >>> import nltk 2 | 3 | >>> from nltk import sentiment 4 | 5 | >>> nltk.download('vader_lexicon') 6 | True 7 | 8 | >>> sentences = [ 9 | ... 'Python is a wonderful programming language', 10 | ... 'Weak-typed languages are prone to errors', 11 | ... 'I love programming in Python and I hate YAML', 12 | ... ] 13 | 14 | >>> si = sentiment.SentimentIntensityAnalyzer() 15 | >>> for sentence in sentences: 16 | ... scores = si.polarity_scores(sentence) 17 | ... print(sentence) 18 | ... print('negative: {neg}, positive: {pos}'.format(**scores)) 19 | Python is a wonderful programming language 20 | negative: 0.0, positive: 0.481 21 | Weak-typed languages are prone to errors 22 | negative: 0.324, positive: 0.0 23 | I love programming in Python and I hate YAML 24 | negative: 0.287, positive: 0.326 25 | -------------------------------------------------------------------------------- /CH_16_machine_learning/T_03_spacy_extract.rst: -------------------------------------------------------------------------------- 1 | >>> import spacy 2 | >>> import en_core_web_sm 3 | 4 | >>> nlp = en_core_web_sm.load() 5 | >>> _ = nlp.add_pipe("merge_entities") 6 | 7 | >>> sentence = ('Python was introduced in 1989 by Guido van ' 8 | ... 'Rossum at Stichting Mathematisch Centrum in Amsterdam.') 9 | 10 | >>> for token in nlp(sentence): 11 | ... if token.ent_type_: 12 | ... print(f'{token.ent_type_}: {token.text}') 13 | DATE: 1989 14 | PERSON: Guido van Rossum 15 | ORG: Stichting Mathematisch Centrum 16 | GPE: Amsterdam 17 | -------------------------------------------------------------------------------- /CH_16_machine_learning/amsterdam-street.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_16_machine_learning/amsterdam-street.jpg -------------------------------------------------------------------------------- /CH_16_machine_learning/coco.names: -------------------------------------------------------------------------------- 1 | person 2 | bicycle 3 | car 4 | motorbike 5 | aeroplane 6 | bus 7 | train 8 | truck 9 | boat 10 | traffic light 11 | fire hydrant 12 | stop sign 13 | parking meter 14 | bench 15 | bird 16 | cat 17 | dog 18 | horse 19 | sheep 20 | cow 21 | elephant 22 | bear 23 | zebra 24 | giraffe 25 | backpack 26 | umbrella 27 | handbag 28 | tie 29 | suitcase 30 | frisbee 31 | skis 32 | snowboard 33 | sports ball 34 | kite 35 | baseball bat 36 | baseball glove 37 | skateboard 38 | surfboard 39 | tennis racket 40 | bottle 41 | wine glass 42 | cup 43 | fork 44 | knife 45 | spoon 46 | bowl 47 | banana 48 | apple 49 | sandwich 50 | orange 51 | broccoli 52 | carrot 53 | hot dog 54 | pizza 55 | donut 56 | cake 57 | chair 58 | sofa 59 | pottedplant 60 | bed 61 | diningtable 62 | toilet 63 | tvmonitor 64 | laptop 65 | mouse 66 | remote 67 | keyboard 68 | cell phone 69 | microwave 70 | oven 71 | toaster 72 | sink 73 | refrigerator 74 | book 75 | clock 76 | vase 77 | scissors 78 | teddy bear 79 | hair drier 80 | toothbrush 81 | -------------------------------------------------------------------------------- /CH_16_machine_learning/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_16_machine_learning/python.png -------------------------------------------------------------------------------- /CH_16_machine_learning/requirements.txt: -------------------------------------------------------------------------------- 1 | tensorflow 2 | tensorflow-hub 3 | opencv-contrib-python 4 | pygad 5 | nltk 6 | gensim 7 | torch 8 | torchvision 9 | scikit 10 | scikit-lego 11 | tpot 12 | -------------------------------------------------------------------------------- /CH_16_machine_learning/street.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_16_machine_learning/street.jpg -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/README.rst: -------------------------------------------------------------------------------- 1 | Chapter 14, C/C++ Extensions 2 | ############################################################################## 3 | 4 | | System Calls and C/C++ Libraries covers the calling of C/C++ functions for both interoperability and performance using Ctypes, CFFI and native C/C++. 5 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_01_complex_data_structures.rst: -------------------------------------------------------------------------------- 1 | >>> from _libc import libc 2 | >>> import ctypes 3 | 4 | >>> class ComplexStructure(ctypes.Structure): 5 | ... _fields_ = [ 6 | ... ('some_int', ctypes.c_int), 7 | ... ('some_double', ctypes.c_double), 8 | ... ('some_char', ctypes.c_char), 9 | ... ('some_string', ctypes.c_char_p), 10 | ... ] 11 | ... 12 | >>> structure = ComplexStructure(123, 456.789, b'x', b'abc') 13 | >>> structure.some_int 14 | 123 15 | >>> structure.some_double 16 | 456.789 17 | >>> structure.some_char 18 | b'x' 19 | >>> structure.some_string 20 | b'abc' 21 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_04_cffi.rst: -------------------------------------------------------------------------------- 1 | >>> import cffi 2 | 3 | >>> ffi = cffi.FFI() 4 | >>> ffi.cdef('int printf(const char* format, ...);') 5 | >>> libc = ffi.dlopen(None) 6 | >>> arg = ffi.new('char[]', b'Printing using CFFI\n') 7 | >>> libc.printf(arg) 8 | 20 9 | 10 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_05_cffi_open_library.rst: -------------------------------------------------------------------------------- 1 | >>> from ctypes import util 2 | >>> import cffi 3 | 4 | # Initialize the FFI builder 5 | 6 | >>> ffi = cffi.FFI() 7 | 8 | # Find the libc library on OS X. Look back at the ctypes examples 9 | for other platforms. 10 | 11 | >>> library = util.find_library('libc.dylib') 12 | >>> library 13 | '/usr/lib/libc.dylib' 14 | 15 | # Load the library 16 | 17 | >>> libc = ffi.dlopen(library) 18 | >>> libc 19 | .FFILibrary object at ...> 20 | 21 | # We do have printf available, but CFFI requires a signature 22 | 23 | >>> libc.printf 24 | Traceback (most recent call last): 25 | ... 26 | AttributeError: printf 27 | 28 | # Define the printf signature and call printf 29 | 30 | >>> ffi.cdef('int printf(const char* format, ...);') 31 | >>> libc.printf 32 | 33 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_06_cffi_complex_datastructures.rst: -------------------------------------------------------------------------------- 1 | >>> import cffi 2 | 3 | >>> ffi = cffi.FFI() 4 | 5 | # Create the structures as C structs 6 | 7 | >>> ffi.cdef(''' 8 | ... typedef struct { 9 | ... int x; 10 | ... int y; 11 | ... } point; 12 | ... 13 | ... typedef struct { 14 | ... point a; 15 | ... point b; 16 | ... } vertex; 17 | ... ''') 18 | 19 | # Create a vertex and return the pointer 20 | 21 | >>> v = ffi.new('vertex*') 22 | 23 | # Set the data 24 | 25 | >>> v.a.x, v.a.y, v.b.x, v.b.y = (0, 1, 2, 3) 26 | 27 | # Print before change 28 | 29 | >>> v.a.x, v.a.y, v.b.x, v.b.y 30 | (0, 1, 2, 3) 31 | 32 | >>> v.a, v.b = v.b, v.a 33 | 34 | # Print after change 35 | 36 | >>> v.a.x, v.a.y, v.b.x, v.b.y 37 | (2, 3, 2, 3) 38 | 39 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_07_cffi_arrays.rst: -------------------------------------------------------------------------------- 1 | >>> import cffi 2 | 3 | >>> ffi = cffi.FFI() 4 | 5 | # Create arrays of size 10: 6 | 7 | >>> x = ffi.new('int[10]') 8 | >>> y = ffi.new('int[]', 10) 9 | 10 | >>> x 11 | 12 | >>> y 13 | 14 | 15 | >>> x[0:10] = range(10) 16 | >>> list(x) 17 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 18 | 19 | >>> y[:] = range(10) 20 | Traceback (most recent call last): 21 | ... 22 | IndexError: slice start must be specified 23 | 24 | >>> x[0:100] = range(100) 25 | Traceback (most recent call last): 26 | ... 27 | IndexError: index too large (expected 100 <= 10) 28 | 29 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_08_cffi_api.rst: -------------------------------------------------------------------------------- 1 | >>> import cffi 2 | 3 | >>> ffi = cffi.FFI() 4 | 5 | # In API mode we can in-line the actual C code 6 | 7 | >>> ffi.set_source('_sum', ''' 8 | ... int sum(int* input, int n){ 9 | ... int result = 0; 10 | ... while(n--)result += input[n]; 11 | ... return result; 12 | ... } 13 | ... ''') 14 | 15 | >>> ffi.cdef('int sum(int*, int);') 16 | 17 | >>> library = ffi.compile() 18 | 19 | # Now we can import the library 20 | 21 | >>> import _sum 22 | 23 | # Or use `ffi.dlopen()` with the results from the compile step 24 | 25 | >>> _sum_lib = ffi.dlopen(library) 26 | 27 | # Create an array with 5 items 28 | 29 | >>> N = 5 30 | >>> array = ffi.new('int[]', N) 31 | >>> array[0:N] = range(N) 32 | 33 | # Call our C function from either the import or the dlopen 34 | 35 | >>> _sum.lib.sum(array, N) 36 | 10 37 | 38 | >>> _sum_lib.sum(array, N) 39 | 10 40 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_09_native/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_17_c_and_cpp_extensions/T_09_native/__init__.py -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_09_native/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_17_c_and_cpp_extensions/T_09_native/plain_c.c: -------------------------------------------------------------------------------- 1 | 2 | long sum_of_squares(long n){ 3 | long total = 0; 4 | 5 | /* The actual summing code */ 6 | for(int i=0; i"] 6 | packages = [ 7 | {include="T_00_basic_pyproject"}, 8 | {include="some_directory/**/*.py"}, 9 | ] 10 | 11 | include = ["CHANGELOG.rst"] 12 | exclude = ["T_00_basic_pyproject/local.py"] 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.10" 16 | progressbar2 = "^3.5" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pytest = "^5.2" 20 | 21 | [build-system] 22 | requires = ["poetry-core>=1.0.0"] 23 | build-backend = "poetry.core.masonry.api" 24 | 25 | [tool.poetry.scripts] 26 | our_command = 'T_00_basic_pyproject.main:run' 27 | 28 | [tool.poetry.urls] 29 | docs='https://progressbar-2.readthedocs.io/' 30 | -------------------------------------------------------------------------------- /CH_18_packaging/T_00_basic_pyproject/some_directory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_00_basic_pyproject/some_directory/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_00_basic_pyproject/t_00_basic_pyproject/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /CH_18_packaging/T_00_basic_pyproject/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_00_basic_pyproject/tests/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_00_basic_pyproject/tests/test_t_00_basic_pyproject.py: -------------------------------------------------------------------------------- 1 | from t_00_basic_pyproject import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/README.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_01_pyproject_extensions/README.rst -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_01_pyproject_extensions/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/build_extension.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import setuptools 3 | 4 | # Get the current directory 5 | PROJECT_PATH = pathlib.Path(__file__).parent 6 | 7 | # Create the extension object with the references to the C source 8 | sum_of_squares = setuptools.Extension('sum_of_squares', sources=[ 9 | # Get the relative path to sum_of_squares.c 10 | str(PROJECT_PATH / 'sum_of_squares.c'), 11 | ]) 12 | 13 | 14 | def build(setup_kwargs): 15 | setup_kwargs['ext_modules'] = [sum_of_squares] 16 | -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "T_01_pyproject_extensions" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Rick van Hattem "] 6 | build = "build_extension.py" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.10" 10 | 11 | [tool.poetry.dev-dependencies] 12 | pytest = "^5.2" 13 | 14 | [build-system] 15 | requires = ["poetry-core>=1.0.0"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/t_01_pyproject_extensions/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_01_pyproject_extensions/tests/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_01_pyproject_extensions/tests/test_t_01_pyproject_extensions.py: -------------------------------------------------------------------------------- 1 | from t_01_pyproject_extensions import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == '0.1.0' 6 | -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include all documentation files 2 | include-recursive *.rst 3 | include LICENSE 4 | 5 | # Include docs and tests 6 | graft tests 7 | graft docs 8 | 9 | # Skip compiled python files 10 | global-exclude *.py[co] 11 | 12 | # Remove all build directories 13 | prune docs/_build 14 | prune build 15 | prune dist 16 | -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/README.rst: -------------------------------------------------------------------------------- 1 | Hi 2 | -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/T_02_basic_setup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_02_basic_setup_py/T_02_basic_setup/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_02_basic_setup_py/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/entry_points.rst: -------------------------------------------------------------------------------- 1 | >>> from importlib import metadata 2 | 3 | >>> metadata.entry_points()['our.custom.plugins'] 4 | [EntryPoint(name='some_plugin', value='...some_plugin:run', ...] 5 | -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /CH_18_packaging/T_02_basic_setup_py/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | if __name__ == '__main__': 5 | setuptools.setup( 6 | name='T_02_basic_setup_py', 7 | version='0.1.0', 8 | packages=setuptools.find_packages(), 9 | url='https://wol.ph/', 10 | author='Rick van Hattem', 11 | author_email='wolph@wol.ph', 12 | setup_requires=['pytest-runner'], 13 | install_requires=['portalocker'], 14 | extras_require={ 15 | 'docs': ['sphinx'], 16 | 'tests': ['pytest'], 17 | }, 18 | entry_points={ 19 | 'console_scripts': [ 20 | 'our_command = T_02_basic_setup_py.main:run', 21 | ], 22 | }, 23 | include_package_data=True, 24 | package_data={ 25 | # Include all documentation files 26 | '': ['*.rst'], 27 | 28 | # Include docs and tests 29 | 'tests': ['*'], 30 | 'docs': ['*'], 31 | }, 32 | exclude_package_data={ 33 | '': ['*.pyc', '*.pyo'], 34 | 'dist': ['*'], 35 | 'build': ['*'], 36 | }, 37 | project_urls=dict( 38 | docs='https://progressbar-2.readthedocs.io/', 39 | ), 40 | ) 41 | -------------------------------------------------------------------------------- /CH_18_packaging/T_03_basic_setup_cfg/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_03_basic_setup_cfg/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_03_basic_setup_cfg/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = T_03_basic_setup_cfg 3 | version = 0.1.0 4 | url='https://wol.ph/', 5 | author='Rick van Hattem', 6 | author_email='wolph@wol.ph', 7 | 8 | [build-system] 9 | requires = 10 | setuptools 11 | wheel 12 | 13 | [options] 14 | packages = find: 15 | 16 | install_requires = 17 | portalocker 18 | 19 | include_package_data=True, 20 | 21 | project_urls= 22 | docs=https://progressbar-2.readthedocs.io/ 23 | 24 | [options.extras_require] 25 | docs = sphinx 26 | tests = pytest 27 | 28 | [options.entry_points] 29 | console_scripts = 30 | our_command = T_03_basic_setup_cfg.main:run 31 | 32 | our.custom.plugins = 33 | some_plugin = T_03_basic_setup_cfg.some_plugin:run 34 | 35 | [options.package_data] 36 | # Include all documentation files 37 | * = *.rst 38 | 39 | # Include docs and tests 40 | tests = * 41 | docs = * 42 | 43 | [options.exclude_package_data] 44 | * = *.pyc, *.pyo 45 | dist = * 46 | build = * 47 | -------------------------------------------------------------------------------- /CH_18_packaging/T_03_basic_setup_cfg/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | if __name__ == '__main__': 5 | setuptools.setup() 6 | -------------------------------------------------------------------------------- /CH_18_packaging/T_04_C_extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_04_C_extensions/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_04_C_extensions/setup.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import setuptools 3 | 4 | # Get the current directory 5 | PROJECT_PATH = pathlib.Path(__file__).parent 6 | 7 | 8 | sum_of_squares = setuptools.Extension('sum_of_squares', sources=[ 9 | # Get the relative path to sum_of_squares.c 10 | str(PROJECT_PATH / 'sum_of_squares.c'), 11 | ]) 12 | 13 | 14 | if __name__ == '__main__': 15 | setuptools.setup( 16 | name='T_04_C_extensions', 17 | version='0.1.0', 18 | packages=setuptools.find_packages(), 19 | url='https://wol.ph/', 20 | author='Rick van Hattem', 21 | author_email='wolph@wol.ph', 22 | ext_modules=[sum_of_squares], 23 | ) 24 | -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/T_05_cython/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore C files since they are automatically generated by Cython 2 | *.c 3 | -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/T_05_cython/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_05_cython/T_05_cython/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/T_05_cython/sum_of_squares.pyx: -------------------------------------------------------------------------------- 1 | #cython: language_level=3 2 | 3 | 4 | def sum_of_squares(int n): 5 | cdef int i, total = 0 6 | 7 | for i in range(n): 8 | if i * i < n: 9 | total += i * i 10 | else: 11 | break 12 | 13 | return total 14 | 15 | -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/T_05_cython/__init__.py -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | 4 | # Little hack to add the current directory to sys.path so we can 5 | # find the imports 6 | path = pathlib.Path(__file__).parent 7 | sys.path.append(str(path.resolve())) 8 | -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/pyximport_example.rst: -------------------------------------------------------------------------------- 1 | # python3 2 | 3 | >>> import pyximport 4 | 5 | >>> pyximport.install() 6 | (None, ) 7 | 8 | >>> from T_05_cython import sum_of_squares 9 | 10 | >>> sum_of_squares.sum_of_squares(10) 11 | 14 12 | -------------------------------------------------------------------------------- /CH_18_packaging/T_05_cython/setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | if __name__ == '__main__': 5 | setuptools.setup( 6 | name='T_05_cython', 7 | version='0.1.0', 8 | ext_modules=[ 9 | setuptools.Extension( 10 | 'sum_of_squares', 11 | sources=['T_05_cython/sum_of_squares.pyx'], 12 | ), 13 | ], 14 | setup_requires=['cython'], 15 | ) 16 | -------------------------------------------------------------------------------- /CH_18_packaging/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Mastering-Python-Second-Edition/3223a43fe32e0721cd8e2c487a724ee70cfe180a/CH_18_packaging/__init__.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rick van Hattem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Code samples for Mastering Python Second Edition 2 | ############################################################################## 3 | 4 | Mastering Python Second Edition, published by Packt 5 | 6 | Code samples from Mastering Python Second Edition 7 | (https://www.packtpub.com/application-development/mastering-python-second-edition) 8 | 9 | All of the code in this repository is tested using the bundled tests. To run 10 | the tests yourself simply install the requirements and run the tests: 11 | 12 | pip3 install --upgrade --requirement requirements.txt 13 | py.test 14 | 15 | **NOTE** Python 3.10.0b4 or higher is required to run the tests. 16 | 17 | ------------------------------------------------------------------------------ 18 | 19 | If you are interested in more packages from the author, take a look at his 20 | Github account: https://github.com/WoLpH/ 21 | -------------------------------------------------------------------------------- /libraries/T_09_deque.rst: -------------------------------------------------------------------------------- 1 | >>> import collections 2 | 3 | >>> queue = collections.deque() 4 | >>> queue.append(1) 5 | >>> queue.append(2) 6 | >>> queue 7 | deque([1, 2]) 8 | >>> queue.popleft() 9 | 1 10 | >>> queue.popleft() 11 | 2 12 | >>> queue.popleft() 13 | Traceback (most recent call last): 14 | ... 15 | IndexError: pop from an empty deque 16 | 17 | ------------------------------------------------------------------------------ 18 | 19 | >>> import collections 20 | 21 | >>> queue = collections.deque() 22 | >>> queue.append(1) 23 | >>> queue.append(2) 24 | >>> queue 25 | deque([1, 2]) 26 | >>> queue.pop() 27 | 2 28 | >>> queue.pop() 29 | 1 30 | >>> queue.pop() 31 | Traceback (most recent call last): 32 | ... 33 | IndexError: pop from an empty deque 34 | 35 | ------------------------------------------------------------------------------ 36 | 37 | >>> import collections 38 | 39 | >>> circular = collections.deque(maxlen=2) 40 | >>> for i in range(5): 41 | ... circular.append(i) 42 | ... circular 43 | deque([0], maxlen=2) 44 | deque([0, 1], maxlen=2) 45 | deque([1, 2], maxlen=2) 46 | deque([2, 3], maxlen=2) 47 | deque([3, 4], maxlen=2) 48 | >>> circular 49 | deque([3, 4], maxlen=2) 50 | -------------------------------------------------------------------------------- /libraries/T_11_combinations.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> list(itertools.combinations(range(3), 2)) 4 | [(0, 1), (0, 2), (1, 2)] 5 | 6 | ------------------------------------------------------------------------------ 7 | 8 | >>> import itertools 9 | 10 | >>> list(itertools.combinations_with_replacement(range(3), 2)) 11 | [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (2, 2)] 12 | 13 | ------------------------------------------------------------------------------ 14 | 15 | >>> import itertools 16 | 17 | >>> def powerset(iterable): 18 | ... return itertools.chain.from_iterable( 19 | ... itertools.combinations(iterable, i) 20 | ... for i in range(len(iterable) + 1)) 21 | >>> list(powerset(range(3))) 22 | [(), (0,), (1,), (2,), (0, 1), (0, 2), (1, 2), (0, 1, 2)] 23 | -------------------------------------------------------------------------------- /libraries/T_11_namedtuple.rst: -------------------------------------------------------------------------------- 1 | >>> import collections 2 | 3 | >>> Point = collections.namedtuple('Point', ['x', 'y', 'z']) 4 | >>> point_a = Point(1, 2, 3) 5 | >>> point_a 6 | Point(x=1, y=2, z=3) 7 | 8 | >>> point_b = Point(x=4, z=5, y=6) 9 | >>> point_b 10 | Point(x=4, y=6, z=5) 11 | 12 | ------------------------------------------------------------------------------ 13 | 14 | >>> x, y, z = point_a 15 | >>> print(f'X: {x}, Y: {y}, Z: {z}') 16 | X: 1, Y: 2, Z: 3 17 | >>> print('X: {}, Y: {}, Z: {}'.format(*point_b)) 18 | X: 4, Y: 6, Z: 5 19 | >>> print('X: %d, Y: %d, Z: %d' % point_b) 20 | X: 4, Y: 6, Z: 5 21 | >>> print(f'X: {point_a.x}') 22 | X: 1 23 | -------------------------------------------------------------------------------- /libraries/T_12_permutations.rst: -------------------------------------------------------------------------------- 1 | >>> import itertools 2 | 3 | >>> list(itertools.permutations(range(3), 2)) 4 | [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] 5 | -------------------------------------------------------------------------------- /libraries/T_13_ordereddict.rst: -------------------------------------------------------------------------------- 1 | >>> import collections 2 | 3 | 4 | >>> spam = collections.OrderedDict() 5 | >>> spam['b'] = 2 6 | >>> spam['c'] = 3 7 | >>> spam['a'] = 1 8 | >>> spam 9 | OrderedDict([('b', 2), ('c', 3), ('a', 1)]) 10 | 11 | >>> for key, value in spam.items(): 12 | ... key, value 13 | ('b', 2) 14 | ('c', 3) 15 | ('a', 1) 16 | 17 | >>> eggs = collections.OrderedDict(sorted(spam.items())) 18 | >>> eggs 19 | OrderedDict([('a', 1), ('b', 2), ('c', 3)]) 20 | 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mastering-python-2" 3 | version = "1.0.0" 4 | description = "" 5 | authors = ["Rick van Hattem "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.10" 9 | 10 | [tool.poetry.dev-dependencies] 11 | 12 | [build-system] 13 | requires = ["poetry-core>=1.0.0"] 14 | build-backend = "poetry.core.masonry.api" 15 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | doctest_optionflags = ELLIPSIS NORMALIZE_WHITESPACE 3 | python_files = *.py 4 | 5 | addopts = 6 | --doctest-modules 7 | --doctest-glob=*.rst 8 | --flake8 9 | 10 | flake8-ignore = 11 | *.py W391 E402 12 | conf.py ALL 13 | pystone.py ALL 14 | CH_03_pythonic_syntax/T_17_flake8.py ALL 15 | CH_10_testing_and_logging/*.py F811 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipdb 2 | cffi 3 | pytest 4 | pytest-flake8 5 | pytest-watch 6 | pytest-xprocess<0.17 7 | pytest-mypy 8 | pytest-cov 9 | mccabe 10 | mypy 11 | sphinx 12 | pyperformance 13 | line_profiler 14 | memory-profiler 15 | aiofiles 16 | dask[distributed] 17 | python-utils>=3.1.0 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38 3 | skip_missing_interpreters = True 4 | skipsdist = True 5 | 6 | [testenv] 7 | basepython = 8 | py38: python3.8 9 | 10 | deps = -r{toxinidir}/requirements.txt 11 | 12 | commands = py.test {posargs} 13 | 14 | --------------------------------------------------------------------------------