├── .gitignore ├── .project ├── .pydevproject ├── .readthedocs.yaml ├── CMakeLists.txt ├── HISTORY.rst ├── INSTALL.rst ├── LICENSE.txt ├── MANIFEST.in ├── PythonExtensionPatterns ├── PythonExtensionPatterns.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── PythonExtensionPatterns.xccheckout │ │ └── xcuserdata │ │ │ ├── engun.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── paulross.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── engun.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── paulross.xcuserdatad │ │ └── xcschemes │ │ ├── PythonExtensionPatterns.xcscheme │ │ └── xcschememanagement.plist └── PythonExtensionPatterns │ ├── Python │ ├── PythonExtensionPatterns.1 │ ├── PythonExtensionPatterns.c │ └── main.c ├── README.rst ├── build_all.sh ├── doc ├── presentations │ ├── PyConUK_2015_HereBeDragons_v05.pdf │ ├── PyConUS_2016_HereBeDragons_v11.pdf │ └── ReadMe.md └── sphinx │ ├── Makefile │ └── source │ ├── HISTORY.rst │ ├── _headings.rst │ ├── _index_styles.rst │ ├── canonical_function.rst │ ├── capsules.rst │ ├── code_layout.rst │ ├── compiler_flags.rst │ ├── conf.py │ ├── containers_and_refcounts.rst │ ├── context_manager.rst │ ├── cpp.rst │ ├── cpp │ ├── cpp_and_buffer_protocol.rst │ ├── cpp_and_cpython.rst │ ├── cpp_and_numpy.rst │ ├── cpp_and_placement_new.rst │ └── cpp_and_unicode.rst │ ├── debugging │ ├── debug.rst │ ├── debug_in_ide.rst │ ├── debug_python.rst │ ├── debug_tactics.rst │ ├── debug_tools.rst │ ├── gcov.rst │ ├── leak_newrefs_vg.rst │ ├── pyatexit.rst │ └── valgrind.rst │ ├── exceptions.rst │ ├── files.rst │ ├── further_reading.rst │ ├── images │ └── DebugXcode.png │ ├── index.rst │ ├── install.rst │ ├── introduction.rst │ ├── iterators_generators.rst │ ├── logging.rst │ ├── memory_leaks.rst │ ├── memory_leaks │ ├── introduction.rst │ ├── pymemtrace.rst │ ├── techniques.rst │ └── tools.rst │ ├── miscellaneous.rst │ ├── module_globals.rst │ ├── new_types.rst │ ├── parsing_arguments.rst │ ├── pickle.rst │ ├── refcount.rst │ ├── simple_example.rst │ ├── struct_sequence.rst │ ├── subclassing_and_super_call.rst │ ├── thread_safety.rst │ ├── todo.rst │ └── watchers.rst ├── requirements.txt ├── setup.py ├── src ├── FaultHandlerExample.py ├── cCanonical.c ├── cPyRefs.so.dSYM │ └── Contents │ │ └── Info.plist ├── cpy │ ├── Capsules │ │ ├── custom_capsule.c │ │ ├── custom_capsule.h │ │ ├── custom_use.c │ │ ├── datetimetz.c │ │ ├── spam.c │ │ ├── spam_capsule.c │ │ ├── spam_capsule.h │ │ └── spam_client.c │ ├── Containers │ │ ├── DebugContainers.c │ │ └── DebugContainers.h │ ├── CtxMgr │ │ └── cCtxMgr.c │ ├── Exceptions │ │ └── cExceptions.c │ ├── File │ │ ├── PythonFileWrapper.cpp │ │ ├── PythonFileWrapper.h │ │ └── cFile.cpp │ ├── Iterators │ │ └── cIterator.c │ ├── Logging │ │ └── cLogging.c │ ├── ModuleGlobals │ │ └── cModuleGlobals.c │ ├── Object │ │ ├── cObject.c │ │ └── cSeqObject.c │ ├── ParseArgs │ │ ├── cParseArgs.c │ │ └── cParseArgsHelper.cpp │ ├── Pickle │ │ └── cCustomPickle.c │ ├── RefCount │ │ ├── cPyRefs.c │ │ └── cRefCount.c │ ├── SimpleExample │ │ ├── cFibA.c │ │ ├── cFibA.h │ │ ├── cFibB.c │ │ ├── pFibA.py │ │ ├── pFibB.py │ │ └── timeit_test.py │ ├── StructSequence │ │ └── cStructSequence.c │ ├── SubClass │ │ └── sublist.c │ ├── Threads │ │ ├── cThreadLock.h │ │ ├── cppsublist.cpp │ │ └── csublist.c │ ├── Util │ │ ├── py_call_super.c │ │ ├── py_call_super.cpp │ │ └── py_call_super.h │ ├── Watchers │ │ ├── DictWatcher.c │ │ ├── DictWatcher.h │ │ ├── cWatchers.c │ │ └── watcher_example.py │ ├── cpp │ │ ├── cUnicode.cpp │ │ └── placement_new.cpp │ ├── pyextpatt_util.c │ └── pyextpatt_util.h ├── debugging │ └── XcodeExample │ │ └── PythonSubclassList │ │ ├── PythonSubclassList.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── PythonSubclassList.xccheckout │ │ │ └── xcuserdata │ │ │ │ └── paulross.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── paulross.xcuserdatad │ │ │ ├── xcdebugger │ │ │ └── Breakpoints_v2.xcbkptlist │ │ │ └── xcschemes │ │ │ ├── PythonSubclassList.xcscheme │ │ │ └── xcschememanagement.plist │ │ └── PythonSubclassList │ │ ├── SubclassList.c │ │ ├── SubclassList.h │ │ ├── main.c │ │ ├── py_call_super.c │ │ ├── py_call_super.h │ │ ├── py_import_call_execute.c │ │ ├── py_import_call_execute.h │ │ ├── setup.py │ │ └── test_sclist.py ├── main.c ├── memleak.py ├── pidmon.py └── scratch.c ├── tests └── unit │ ├── test_c_capsules.py │ ├── test_c_cpp.py │ ├── test_c_ctxmgr.py │ ├── test_c_custom_pickle.py │ ├── test_c_exceptions.py │ ├── test_c_file.py │ ├── test_c_iterators.py │ ├── test_c_logging.py │ ├── test_c_module_globals.py │ ├── test_c_object.py │ ├── test_c_parse_args.py │ ├── test_c_parse_args_helper.py │ ├── test_c_py_refs.py │ ├── test_c_ref_count.py │ ├── test_c_seqobject.py │ ├── test_c_simple_example.py │ ├── test_c_struct_sequence.py │ ├── test_c_subclass.py │ └── test_c_threads.py └── type_objects ├── Python_3.10.1.h ├── Python_3.11.1.h ├── Python_3.12.1.h ├── Python_3.13.0b3.h ├── Python_3.6.2.h ├── Python_3.7.1.h ├── Python_3.8.3.h ├── Python_3.8.6.h ├── Python_3.9.0.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | PythonExtensionPatterns.bbprojectd/ 2 | 3 | *.so 4 | build/ 5 | doc/sphinx/build/ 6 | 7 | __pycache__/ 8 | 9 | .DS_Store 10 | 11 | .idea 12 | 13 | cmake-build-debug/ 14 | cmake-build-release/ 15 | 16 | cPyExtPatt.egg-info/ 17 | 18 | dist/ 19 | 20 | cPyExtPatt/ 21 | 22 | tmp/ 23 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | PythonExtensionPatterns 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME}/src 5 | 6 | python 3.0 7 | python3.3 8 | 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: doc/sphinx/source/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | 24 | formats: 25 | - pdf 26 | - epub 27 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | History 3 | ===================== 4 | 5 | 0.3.0 (2025-03-20) 6 | ===================== 7 | 8 | Added Chapters 9 | -------------- 10 | 11 | - "Containers and Reference Counts" which corrects the Python documentation where that is wrong, misleading or missing. 12 | - "Struct Sequences (namedtuple in C)" which corrects the Python documentation where that is wrong, misleading or missing. 13 | - "Context Managers" with practical C code examples. 14 | - "Watchers" with practical examples for dictionary watchers (Python 3.12+). 15 | - "Installation" for the project. 16 | - "Source Code Layout" for the project. 17 | 18 | Changed Chapters 19 | ---------------- 20 | 21 | - Update the "Homogeneous Python Containers and C++" chapter. 22 | - Expand the "Memory Leaks" chapter. 23 | - Extended the "Logging" chapter to show how to access the CPython Frame from C. 24 | - Add "Emulating Sequence Types" to the "Creating New Types" chapter. 25 | - Expand the Index. 26 | 27 | Other 28 | ------ 29 | 30 | - Python versions supported: 3.9, 3.10, 3.11, 3.12, 3.13. 31 | - Development Status :: 5 - Production/Stable 32 | - The documentation content, example and test code has roughly doubled since version 0.2.2. 33 | - PDF Documentation is 339 pages. 34 | 35 | TODO 36 | ---- 37 | 38 | - Add "Debugging Python with CLion". 39 | 40 | .. 41 | .. todo:: 42 | 43 | Update this history file. 44 | 45 | 0.2.2 (2024-10-21) 46 | ===================== 47 | 48 | - Expand note on PyDict_SetItem(), PySet_Add() with code in src/cpy/RefCount/cRefCount.c and tests. 49 | 50 | 0.2.1 (2024-07-29) 51 | ===================== 52 | 53 | - Python versions supported: 3.9, 3.10, 3.11, 3.12, 3.13 (possibly backwards compatible with Python 3.6, 3.7, 3.8) 54 | - Almost all example code is built and tested against these Python versions. 55 | - Added a chapter on managing file paths and files between Python and C. 56 | - Added a chapter on subclassing from your classes or builtin classes. 57 | - Added a chapter on pickling from C. 58 | - Added a chapter on Capsules. 59 | - Added a chapter on Iterators and Generators. 60 | - Added a chapter on memory leaks and how to detect them. 61 | - Added a chapter on thread safety. 62 | - Update "Homogeneous Python Containers and C++" to refer to https://github.com/paulross/PyCppContainers 63 | - All the documentation has been extensively reviewed and corrected where necessary. 64 | - Development Status :: 5 - Production/Stable 65 | 66 | Contributors 67 | ------------------------- 68 | 69 | Many thanks! 70 | 71 | Pull Requests 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | - https://github.com/marioemmanuel 75 | - https://github.com/miurahr 76 | - https://github.com/gdevanla 77 | - https://github.com/joelwhitehouse 78 | - https://github.com/dhermes 79 | - https://github.com/gst 80 | - https://github.com/adamchainz 81 | - https://github.com/nnathan 82 | 83 | 84 | Issues 85 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 86 | 87 | - https://github.com/ngoldbaum 88 | - https://github.com/niki-sp 89 | - https://github.com/ldo 90 | - https://github.com/1a1a11a 91 | - https://github.com/congma 92 | 93 | 0.1.0 (2014-09-09) 94 | ===================== 95 | 96 | - First release. 97 | - Originally "Examples of reliable coding of Python 'C' extensions by Paul Ross.". 98 | - Development Status :: 3 - Alpha 99 | -------------------------------------------------------------------------------- /INSTALL.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ======================== 3 | 4 | This is primarily a documentation project hosted on 5 | `Read the Docs `_. 6 | However all the example code is buildable and testable so if you want to examine that then here is how to get the 7 | project. 8 | 9 | From PyPi 10 | ------------------------ 11 | 12 | .. code-block:: console 13 | 14 | pip install cPyExtPatt 15 | 16 | From Source 17 | ------------------------ 18 | 19 | Choose a directory of your choice, in this case: ``~/dev/tmp``. 20 | 21 | .. code-block:: console 22 | 23 | mkdir -p ~/dev/tmp 24 | cd ~/dev/tmp 25 | git clone https://github.com/paulross/PythonExtensionPatterns.git 26 | cd PythonExtensionPatterns 27 | 28 | Virtual Environment 29 | --------------------- 30 | 31 | Create a Python environment in the directory of your choice, in this case: 32 | ``~/dev/tmp/PythonExtensionPatterns/venv_3.11`` and activate it: 33 | 34 | .. code-block:: console 35 | 36 | python3.11 -m venv venv_3.11 37 | source venv_3.11/bin/activate 38 | 39 | 40 | Install the Dependencies 41 | --------------------------------- 42 | 43 | .. code-block:: console 44 | 45 | pip install -r requirements.txt 46 | 47 | Running the Tests 48 | ----------------------- 49 | 50 | You now should be able to run the following commands successfully in 51 | ``~/dev/tmp/PythonExtensionPatterns`` with your environment activated: 52 | 53 | .. code-block:: console 54 | 55 | pytest tests/ 56 | 57 | Building the Documentation 58 | ---------------------------------- 59 | 60 | This will build the html and PDF documentation (requires a latex installation): 61 | 62 | .. code-block:: console 63 | 64 | cd doc/sphinx 65 | make html latexpdf 66 | 67 | 68 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2025 Paul Ross https://github.com/paulross 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft type_objects 2 | graft tests 3 | 4 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcshareddata/PythonExtensionPatterns.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 0D639DDC-8BBE-4924-85DB-6C4AC5A72B99 9 | IDESourceControlProjectName 10 | PythonExtensionPatterns 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 016E9095344C63B565906E2AD06B924DF62961F7 14 | https://github.com/paulross/PythonExtensionPatterns.git 15 | 16 | IDESourceControlProjectPath 17 | PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 016E9095344C63B565906E2AD06B924DF62961F7 21 | ../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/paulross/PythonExtensionPatterns.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 016E9095344C63B565906E2AD06B924DF62961F7 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 016E9095344C63B565906E2AD06B924DF62961F7 36 | IDESourceControlWCCName 37 | PythonExtensionPatterns 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcuserdata/engun.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcuserdata/engun.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcuserdata/paulross.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/project.xcworkspace/xcuserdata/paulross.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/xcuserdata/engun.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PythonExtensionPatterns.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/xcuserdata/paulross.xcuserdatad/xcschemes/PythonExtensionPatterns.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns.xcodeproj/xcuserdata/paulross.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PythonExtensionPatterns.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C3BEEEC1191AB448007D7F35 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns/Python: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/PythonExtensionPatterns/PythonExtensionPatterns/Python -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns/PythonExtensionPatterns.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 07/05/2014 \" DATE 7 | .Dt PythonExtensionPatterns 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm PythonExtensionPatterns, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns/PythonExtensionPatterns.c: -------------------------------------------------------------------------------- 1 | // 2 | // PythonExtensionPatterns.c 3 | // PythonExtensionPatterns 4 | // 5 | // Created by Paul Ross on 07/05/2014. 6 | // Copyright (c) 2014 Paul Ross. All rights reserved. 7 | // 8 | #include "Python.h" 9 | 10 | #include 11 | #if 0 12 | 13 | /* Examples of design patterns in C that are helpful for Python extensions. */ 14 | 15 | static PyObject *python_arithmitic_trad(PyObject *str_a, PyObject *str_b, int op) { 16 | /* Local Python objects. */ 17 | PyObject *num_a = NULL; 18 | PyObject *num_b = NULL; 19 | /* The Python object to be returned. */ 20 | PyObject *ret = NULL; 21 | 22 | if(op < 0 || op > 3) { 23 | PyErr_SetString(PyExc_RuntimeError, "Operator not in range."); 24 | return NULL; 25 | } 26 | num_a = PyLong_FromString(str_a, NULL, 0); /* New ref. */ 27 | if (! num_a) { 28 | PyErr_SetString(PyExc_ValueError, "Can not read string a."); 29 | return NULL; 30 | } 31 | num_b = PyLong_FromString(str_b, NULL, 0); /* New ref. */ 32 | if (! num_b) { 33 | PyErr_SetString(PyExc_ValueError, "Can not read string a."); 34 | return NULL; /* Ooops, forgot to free num_a. */ 35 | } 36 | 37 | switch (op) { 38 | case 0: 39 | ret = PyNumber_Add(num_a, num_b); 40 | break; 41 | case 1: 42 | ret = PyNumber_Subtract(num_a, num_b); 43 | break; 44 | case 2: 45 | ret = PyNumber_Multiply(num_a, num_b); 46 | break; 47 | case 3: 48 | ret = PyNumber_FloorDivide(num_a, num_b); 49 | break; 50 | default: 51 | exit(1); 52 | break; 53 | } 54 | /* Oh dear, if ret is NULL both loc_a and loc_b are leaked. */ 55 | return ret; 56 | } 57 | 58 | /* Does some arithmitic on two objects - Returns a new reference. */ 59 | static PyObject *_PyNumber_Operate(PyObject *o1, PyObject *o2, int op) { 60 | PyObject *ret= NULL; 61 | 62 | goto try; 63 | try: 64 | /* Increment the reference count of the arguments. */ 65 | if (o1) { 66 | Py_INCREF(o1); 67 | } 68 | if (o2) { 69 | Py_INCREF(o2); 70 | } 71 | switch (op) { 72 | case 0: 73 | ret = PyNumber_Add(o1, o2); 74 | break; 75 | case 1: 76 | ret = PyNumber_Subtract(o1, o2); 77 | break; 78 | case 2: 79 | ret = PyNumber_Multiply(o1, o2); 80 | break; 81 | case 3: 82 | ret = PyNumber_FloorDivide(o1, o2); 83 | break; 84 | default: 85 | exit(1); 86 | break; 87 | } 88 | if (!ret) { 89 | PyErr_SetString(PyExc_ValueError, "Can not do the math."); 90 | goto except; 91 | } else { 92 | goto finally; 93 | } 94 | except: 95 | /* Failure so Py_XDECREF the return value. */ 96 | Py_XDECREF(ret); 97 | ret = NULL; 98 | finally: 99 | /* All _local_ PyObjects declared at the entry point are Py_XDECREF'd. */ 100 | /* (nothing to do here). */ 101 | /* Decrement the ref count of the arguments. */ 102 | Py_XDECREF(o2); 103 | Py_XDECREF(o1); 104 | return ret; 105 | } 106 | 107 | static PyObject *python_arithmitic(char *str_a, char *str_b, int op) { 108 | /* Local C variables. */ 109 | int err; 110 | /* Local Python objects. */ 111 | PyObject *loc_a = NULL; 112 | PyObject *loc_b = NULL; 113 | /* The Python object to be returned. */ 114 | PyObject *ret = NULL; 115 | 116 | goto try; 117 | 118 | try: 119 | err = _check_operator(op); 120 | if(err) { 121 | if (err) { 122 | PyErr_SetString(PyExc_RuntimeError, "Operator not in range."); 123 | goto except; 124 | } 125 | } 126 | loc_a = _PyObject_str_to_int(str_a); 127 | if (!loc_a) { 128 | PyErr_SetString(PyExc_ValueError, "Can not read string a."); 129 | goto except; 130 | } 131 | loc_b = _PyObject_str_to_int(str_b); 132 | if (!loc_b) { 133 | PyErr_SetString(PyExc_ValueError, "Can not read string a."); 134 | goto except; 135 | } 136 | 137 | ret = _PyNumber_Operate(loc_a, loc_b, op); 138 | if (!ret) { 139 | goto except; 140 | } 141 | goto finally; 142 | 143 | except: 144 | /* Failure so Py_XDECREF the return value. */ 145 | Py_XDECREF(ret); 146 | ret = NULL; 147 | finally: 148 | /* All _local_ PyObjects declared at the entry point are Py_XDECREF'd. */ 149 | Py_XDECREF(loc_a); 150 | Py_XDECREF(loc_b); 151 | return ret; 152 | } 153 | 154 | #endif 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /PythonExtensionPatterns/PythonExtensionPatterns/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // PythonExtensionPatterns 4 | // 5 | // Created by Paul Ross on 07/05/2014. 6 | // Copyright (c) 2014 Paul Ross. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | 14 | // insert code here... 15 | printf("Hello, World!\n"); 16 | return 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | *************************** 2 | PythonExtensionPatterns 3 | *************************** 4 | 5 | If you need to write C extension for Python then this is the place for you. 6 | 7 | The full documentation is on 8 | `Read the Docs `_. 9 | 10 | Code examples and documentation source are `on GitHub `_. 11 | The example and test code is available `on PyPi `_. 12 | 13 | ==================================== 14 | Subjects Covered 15 | ==================================== 16 | 17 | - Introduction 18 | - A Simple Example 19 | - PyObjects and Reference Counting 20 | - Containers and Reference Counts 21 | - Struct Sequence Objects (a namedtuple in C) 22 | - Exception Raising 23 | - A Pythonic Coding Pattern for C Functions 24 | - Parsing Python Arguments 25 | - Creating New Types 26 | - Setting and Getting Module Globals 27 | - Logging and Frames 28 | - File Paths and Files 29 | - Subclassing and Using super() 30 | - Capsules 31 | - Iterators and Generators 32 | - Context Managers 33 | - Pickling C Extension Types 34 | - Watchers [Python 3.12+] 35 | - Setting Compiler Flags 36 | - Debugging 37 | - Memory Leaks 38 | - Thread Safety 39 | - Source Code Layout 40 | - Using C++ With CPython Code 41 | - Miscellaneous 42 | - Installation 43 | - Further Reading 44 | - TODO 45 | - History 46 | - Index 47 | 48 | ============= 49 | Project Links 50 | ============= 51 | 52 | - Source is `on GitHub `_. 53 | - Documentation `on Read the Docs `_. 54 | - Project is `on PyPi `_. 55 | 56 | ================== 57 | Videos 58 | ================== 59 | 60 | I have presented some of this, well mostly the chapter "PyObjects and Reference Counting", 61 | at Python conferences so if you prefer videos they are here: 62 | 63 | - `PyCon UK 2015 `_ 64 | - `PyCon US 2016 `_ 65 | -------------------------------------------------------------------------------- /doc/presentations/PyConUK_2015_HereBeDragons_v05.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/doc/presentations/PyConUK_2015_HereBeDragons_v05.pdf -------------------------------------------------------------------------------- /doc/presentations/PyConUS_2016_HereBeDragons_v11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/doc/presentations/PyConUS_2016_HereBeDragons_v11.pdf -------------------------------------------------------------------------------- /doc/presentations/ReadMe.md: -------------------------------------------------------------------------------- 1 | # Presentations about Python Extension Patterns 2 | 3 | PyCon UK 2015: [https://www.youtube.com/watch?v=ViRIYqiU128](https://www.youtube.com/watch?v=ViRIYqiU128) 4 | 5 | PyCon US 2016: [https://www.youtube.com/watch?v=Yq__HtUIH5Y](https://www.youtube.com/watch?v=Yq__HtUIH5Y) 6 | -------------------------------------------------------------------------------- /doc/sphinx/source/HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. index:: 2 | single: History 3 | 4 | .. include:: ../../../HISTORY.rst 5 | -------------------------------------------------------------------------------- /doc/sphinx/source/_headings.rst: -------------------------------------------------------------------------------- 1 | 2 | ############### 3 | PartOne 4 | ############### 5 | 6 | Section headers (ref) are created by underlining (and optionally overlining) the section title with a punctuation 7 | character, at least as long as the text: 8 | 9 | Normally, there are no heading levels assigned to certain characters as the structure is determined from the succession 10 | of headings. However, this convention is used in Python Developer’s Guide for documenting which you may follow: 11 | 12 | # with overline, for parts 13 | * with overline, for chapters 14 | = for sections 15 | - for subsections 16 | ^ for subsubsections 17 | " for paragraphs 18 | 19 | ``#`` with overline. 20 | 21 | Text for PartOne. 22 | 23 | *************************** 24 | PartOne.ChapterOne 25 | *************************** 26 | 27 | ``*`` with overline. 28 | 29 | Text for PartOne.ChapterOne. 30 | 31 | ====================================== 32 | PartOne.ChapterOne.SectionOne 33 | ====================================== 34 | 35 | ``=`` with overline. 36 | 37 | Text for PartOne.ChapterOne.SectionOne 38 | 39 | PartOne.ChapterOne.SectionOne 40 | ====================================== 41 | 42 | ``=`` without overline. 43 | 44 | Text for PartOne.ChapterOne.SectionOne 45 | 46 | -------------------------------------------------- 47 | PartOne.ChapterOne.SectionOne.SubsectionOne 48 | -------------------------------------------------- 49 | 50 | ``-`` with overline. 51 | 52 | PartOne.ChapterOne.SectionOne.SubsectionOne 53 | -------------------------------------------------- 54 | 55 | ``-`` without overline. 56 | 57 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne 58 | 59 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 60 | PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne 61 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | 63 | ``^`` with overline. 64 | 65 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne 66 | 67 | PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne 68 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 69 | 70 | ``^`` without overline. 71 | 72 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne 73 | 74 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 75 | PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphOne 76 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 77 | 78 | ``"`` with overline. 79 | 80 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphOne 81 | 82 | PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphOne 83 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 84 | 85 | ``"`` without overline. 86 | 87 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphOne 88 | 89 | PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphTwo 90 | """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" 91 | 92 | ``"`` without overline. 93 | 94 | Text for PartOne.ChapterOne.SectionOne.SubsectionOne.SubsubsectionOne.ParagraphTwo 95 | -------------------------------------------------------------------------------- /doc/sphinx/source/_index_styles.rst: -------------------------------------------------------------------------------- 1 | 2 | .. 3 | Explore different index styles. 4 | 5 | ====================================== 6 | Index Styles 7 | ====================================== 8 | 9 | See: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-index 10 | 11 | .. 12 | .. index:: 13 | single: execution; context 14 | pair: module; __main__ 15 | pair: module; sys 16 | triple: module; search; path 17 | seealso: execution 18 | 19 | ---------------------- 20 | Inline Index Entries 21 | ---------------------- 22 | 23 | This is a normal reStructuredText :index:`paragraph` that contains several :index:`index entries `. 24 | 25 | .. raw:: latex 26 | 27 | [Continued on the next page] 28 | 29 | \pagebreak 30 | 31 | .. index:: 32 | single: execution 33 | 34 | ----------------- 35 | Single execution 36 | ----------------- 37 | 38 | ``single: execution``. 39 | 40 | .. raw:: latex 41 | 42 | [Continued on the next page] 43 | 44 | \pagebreak 45 | 46 | .. index:: 47 | single: execution; context 48 | 49 | ------------------------- 50 | Single execution; context 51 | ------------------------- 52 | 53 | ``single: execution; context``. 54 | 55 | .. raw:: latex 56 | 57 | [Continued on the next page] 58 | 59 | \pagebreak 60 | 61 | .. index:: 62 | pair: module; __main__ 63 | pair: module; sys 64 | 65 | ----------------- 66 | Pairs 67 | ----------------- 68 | 69 | ``pair: module; __main__``. 70 | 71 | ``pair: module; sys``. 72 | 73 | .. raw:: latex 74 | 75 | [Continued on the next page] 76 | 77 | \pagebreak 78 | 79 | .. index:: 80 | triple: module; search; path 81 | 82 | ----------------- 83 | Triple 84 | ----------------- 85 | 86 | ``triple: module; search; path`` 87 | 88 | .. raw:: latex 89 | 90 | [Continued on the next page] 91 | 92 | \pagebreak 93 | 94 | .. index:: 95 | see: execution; context 96 | 97 | ------------------------- 98 | See execution; context 99 | ------------------------- 100 | 101 | ``see: execution; context`` 102 | 103 | .. raw:: latex 104 | 105 | [Continued on the next page] 106 | 107 | \pagebreak 108 | 109 | .. index:: 110 | seealso: execution; context 111 | 112 | ------------------------------ 113 | See Also execution; context 114 | ------------------------------ 115 | 116 | ``seealso: execution; context`` 117 | -------------------------------------------------------------------------------- /doc/sphinx/source/code_layout.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | .. index:: 8 | single: Source Code Layout 9 | 10 | ================================= 11 | Source Code Layout 12 | ================================= 13 | 14 | I find it useful to physically separate out the source code into different categories: 15 | 16 | .. list-table:: Recommended Code Directories 17 | :widths: 10 10 10 10 10 30 18 | :header-rows: 1 19 | 20 | * - Category 21 | - Language 22 | - ``#include ``? 23 | - Testable? 24 | - Where? 25 | - Description 26 | * - Pure Python 27 | - Python 28 | - No 29 | - Yes 30 | - ``py/`` 31 | - Regular Python code tested by pytest or similar. 32 | * - CPython interface 33 | - Mostly C 34 | - Yes 35 | - No 36 | - ``cpy/`` 37 | - C code that defines Python modules and classes. Functions that are exposed directly to Python. 38 | * - CPython utilities 39 | - C, C++ 40 | - Yes 41 | - Yes 42 | - ``cpy/`` 43 | - Utility C/C++ code that works with Python objects but these functions that are *not* exposed directly to Python. 44 | This code can be tested in a C/C++ environment with a specialised test framework. 45 | See :ref:`cpp_and_cpython` for some examples. 46 | * - C/C++ core 47 | - C, C++ 48 | - No 49 | - Yes 50 | - ``cpp/`` 51 | - C/C++ code that knows nothing about Python. This code can be tested in a C/C++ environment with a standard C/C++ 52 | test framework. 53 | 54 | -------------------------------------- 55 | Testing CPython Utility Code 56 | -------------------------------------- 57 | 58 | When making Python C API calls from a C/C++ environment it is important to initialise the Python interpreter. 59 | For example, this small program segfaults: 60 | 61 | .. code-block:: c 62 | :linenos: 63 | :emphasize-lines: 4, 5, 6 64 | 65 | #include 66 | 67 | int main(int /* argc */, const char *[] /* argv[] */) { 68 | /* Forgot this: 69 | Py_Initialize(); 70 | */ 71 | PyErr_Format(PyExc_TypeError, "Stuff",); 72 | return 0; 73 | } 74 | 75 | The reason is that ``PyErr_Format`` calls ``PyThreadState *thread_state = PyThreadState_Get();`` then ``thread_state`` 76 | will be NULL unless the Python interpreter is initialised. 77 | 78 | So you need to call ``Py_Initialize()`` to set up statically allocated interpreter data. 79 | Alternatively put ``if (! Py_IsInitialized()) Py_Initialize();`` in every test. See: `https://docs.python.org/3/c-api/init.html `_ 80 | 81 | Here are a couple of useful C++ functions that assert all is well that can be used at the beginning of any function: 82 | 83 | .. code-block:: c 84 | 85 | /* Returns non zero if Python is initialised and there is no Python error set. 86 | * The second version also checks that the given pointer is non-NULL 87 | * Use this thus, it will do nothing if NDEBUG is defined: 88 | * 89 | * assert(cpython_asserts()); 90 | * assert(cpython_asserts(p)); 91 | */ 92 | int cpython_asserts() { 93 | return Py_IsInitialized() && PyErr_Occurred() == NULL; 94 | } 95 | 96 | int cpython_asserts(PyObject *pobj) { 97 | return cpython_asserts() && pobj != NULL; 98 | } 99 | -------------------------------------------------------------------------------- /doc/sphinx/source/cpp.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _cpp_and_: 3 | 4 | .. index:: 5 | single: C++ 6 | 7 | ******************************************** 8 | Using C++ With CPython Code 9 | ******************************************** 10 | 11 | Using C++ can take a lot of the pain out of interfacing CPython code, here are some examples. 12 | 13 | .. toctree:: 14 | 15 | cpp/cpp_and_cpython 16 | cpp/cpp_and_placement_new 17 | cpp/cpp_and_unicode 18 | cpp/cpp_and_numpy 19 | cpp/cpp_and_buffer_protocol 20 | -------------------------------------------------------------------------------- /doc/sphinx/source/cpp/cpp_and_buffer_protocol.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 2 3 | 4 | .. _cpp_and_buffer_protocol: 5 | 6 | .. index:: 7 | single: C++; Buffer Protocol 8 | 9 | ==================================== 10 | C++ and the Python Buffer Protocol 11 | ==================================== 12 | 13 | Python's buffer protocol is a general purpose wrapper around data structures that contain 14 | homogeneous types with a regular structure. 15 | Examples are numpy ``ndarrays``, PIL images and Python types such as ``bytes``, ``bytearray`` and ``array.array`` types. 16 | 17 | The buffer protocol is described in `PEP 3118 `_. 18 | 19 | The great advantage of this is that it uses a shared memory model so that the data can be passed between Python or C++ 20 | without copying. 21 | 22 | It is fairly straightforward to create a C++ wrapper class around the buffer protocol. 23 | 24 | 25 | .. todo:: 26 | 27 | Complete the Buffer Protocol chapter with examples from RaPiVot and the C++ wrapper code. 28 | 29 | ----------- 30 | References: 31 | ----------- 32 | 33 | * Python documentation on objects that support the 34 | `Buffer protocol `_. 35 | * Python standard library for the `array module `_. 36 | 37 | 38 | -------------------------------------------------------------------------------- /doc/sphinx/source/cpp/cpp_and_placement_new.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | .. _cpp_and_placement_new: 8 | 9 | ========================================== 10 | A CPython Extension containing C++ Objects 11 | ========================================== 12 | 13 | Here is an example of using C++ classes within a CPython extension type (Python object). 14 | It shows the various stages of construction and destruction. 15 | 16 | Only the important code is shown here. The complete code is in ``src/cpy/cpp/placement_new.cpp`` and the tests are 17 | in ``tests/unit/test_c_cpp.py`` 18 | 19 | ----------------------------------------------- 20 | Allocation of C++ Objects and Placement new 21 | ----------------------------------------------- 22 | 23 | In ``src/cpy/cpp/placement_new.cpp`` there is a C++ class ``Verbose`` which: 24 | 25 | - Reports on ``stdout`` construction and destruction events. 26 | - Allocates an in-memory buffer of 256MB so that the memory usage, and any leaks, will show up in the process RSS. 27 | 28 | We are going to create a Python extension that has a Python class that contains the C++ ``Verbose`` objects in two 29 | ways: 30 | 31 | - Directly. 32 | - With a dynamically allocated pointer. 33 | 34 | This will illustrate the different techniques needed for both. 35 | Here is the CPython structure: 36 | 37 | .. code-block:: cpp 38 | 39 | typedef struct { 40 | PyObject_HEAD 41 | Verbose Attr; 42 | Verbose *pAttr; 43 | } CppCtorDtorInPyObject; 44 | 45 | Here is the function to allocate a new CPython object. 46 | The important point here is that the line below: 47 | 48 | .. code-block:: cpp 49 | 50 | self = (CppCtorDtorInPyObject *) type->tp_alloc(type, 0); 51 | 52 | Allocates sufficient, uninitialised, space for the ``CppCtorDtorInPyObject`` object. 53 | This will mean that both the ``Verbose Attr;`` and ``Verbose *pAttr;`` are uninitialised. 54 | To initialise them two different techniques must be used: 55 | 56 | - For ``Verbose Attr;`` this must be initialised with *placement new* ``new(&self->Attr) Verbose;``. 57 | - For ``Verbose *pAttr;`` this must be initialised with a dynamic new: ``self->pAttr = new Verbose("pAttr");``. 58 | 59 | Here is the complete code: 60 | 61 | .. code-block:: cpp 62 | 63 | static PyObject * 64 | CppCtorDtorInPyObject_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds)) { 65 | printf("-- %s()\n", __FUNCTION__); 66 | CppCtorDtorInPyObject *self; 67 | self = (CppCtorDtorInPyObject *) type->tp_alloc(type, 0); 68 | if (self != NULL) { 69 | // Placement new used for direct allocation. 70 | new(&self->Attr) Verbose; 71 | self->Attr.print("Initial self->Attr"); 72 | // Dynamically allocated new. 73 | self->pAttr = new Verbose("pAttr"); 74 | if (self->pAttr == NULL) { 75 | Py_DECREF(self); 76 | return NULL; 77 | } else { 78 | self->pAttr->print("Initial self->pAttr"); 79 | } 80 | } 81 | return (PyObject *) self; 82 | } 83 | 84 | The complimentary de-allocation function uses different deletion techniques for the two objects. 85 | 86 | .. code-block:: cpp 87 | 88 | static void 89 | CppCtorDtorInPyObject_dealloc(CppCtorDtorInPyObject *self) { 90 | printf("-- %s()\n", __FUNCTION__); 91 | self->Attr.print("self->Attr before delete"); 92 | // For self->Attr call the destructor directly. 93 | self->Attr.~Verbose(); 94 | self->pAttr->print("self->pAttr before delete"); 95 | // For self->pAttr use delete. 96 | delete self->pAttr; 97 | Py_TYPE(self)->tp_free((PyObject *) self); 98 | } 99 | 100 | The C++ ``Verbose`` class writes to ``stdout`` the stages of construction and deletion, typically the output is: 101 | 102 | .. code-block:: bash 103 | 104 | RSS start: 35,586,048 105 | -- CppCtorDtorInPyObject_new() 106 | Constructor at 0x102afa840 with argument "Default" buffer len: 268435456 107 | Default constructor at 0x102afa840 with argument "Default" 108 | Initial self->Attr: Verbose object at 0x102afa840 m_str: "Default" 109 | Constructor at 0x600003158000 with argument "pAttr" buffer len: 268435456 110 | Initial self->pAttr: Verbose object at 0x600003158000 m_str: "pAttr" 111 | -- CppCtorDtorInPyObject_buffer_size() 112 | Buffer size: 536,871,116 113 | RSS new: 572,506,112 +536,920,064 114 | -- CppCtorDtorInPyObject_dealloc() 115 | self->Attr before delete: Verbose object at 0x102afa840 m_str: "Default" 116 | Destructor at 0x102afa840 m_str: "Default" 117 | self->pAttr before delete: Verbose object at 0x600003158000 m_str: "pAttr" 118 | Destructor at 0x600003158000 m_str: "pAttr" 119 | RSS del: 35,602,432 +16,384 120 | RSS end: 35,602,432 +16,384 121 | -------------------------------------------------------------------------------- /doc/sphinx/source/debugging/debug.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | .. index:: 8 | single: Debugging 9 | 10 | ***************** 11 | Debugging 12 | ***************** 13 | 14 | This is very much work in progress. I will add to it/correct it as I develop new techniques. 15 | 16 | .. toctree:: 17 | :maxdepth: 3 18 | 19 | debug_tools 20 | debug_python 21 | valgrind 22 | leak_newrefs_vg 23 | debug_tactics 24 | gcov 25 | debug_in_ide 26 | pyatexit 27 | 28 | .. todo:: 29 | 30 | Review the Debugging chapter as some of it might be obsolete. 31 | -------------------------------------------------------------------------------- /doc/sphinx/source/debugging/debug_tactics.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. highlight:: c 5 | :linenothreshold: 10 6 | 7 | .. toctree:: 8 | :maxdepth: 3 9 | 10 | .. index:: 11 | single: Debugging; Tactics 12 | 13 | ================================= 14 | Debugging Tactics 15 | ================================= 16 | 17 | So what is the problem that you are trying to solve? 18 | 19 | 20 | .. index:: 21 | single: Access After Free 22 | single: Debugging; Access After Free 23 | 24 | ---------------------------- 25 | Access After Free 26 | ---------------------------- 27 | 28 | ^^^^^^^^^^^^^^^^^^^ 29 | Problem 30 | ^^^^^^^^^^^^^^^^^^^ 31 | 32 | You suspect that you are accessing a Python object after it has been free'd. Code such as this: 33 | 34 | .. code-block:: c 35 | 36 | static PyObject *access_after_free(PyObject *pModule) { 37 | PyObject *pA = PyLong_FromLong(1024L); 38 | Py_DECREF(pA); 39 | PyObject_Print(pA, stdout, 0); 40 | Py_RETURN_NONE; 41 | } 42 | 43 | ``pA`` has been freed before ``PyObject_Print`` is called. 44 | 45 | ^^^^^^^^^^^^^^^^^^^ 46 | Solution 47 | ^^^^^^^^^^^^^^^^^^^ 48 | 49 | .. code-block:: c 50 | 51 | Python 3.4.3 (default, Sep 16 2015, 16:56:10) 52 | [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] on darwin 53 | Type "help", "copyright", "credits" or "license" for more information. 54 | >>> from cPyExtPatt import cPyRefs 55 | >>> cPyRefs.afterFree() 56 | 57 | >>> 58 | -------------------------------------------------------------------------------- /doc/sphinx/source/debugging/debug_tools.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. highlight:: c 5 | :linenothreshold: 10 6 | 7 | .. toctree:: 8 | :maxdepth: 3 9 | 10 | .. index:: 11 | single: Debugging; Tools 12 | 13 | ================================= 14 | Debugging Tools 15 | ================================= 16 | 17 | First create your toolbox, in this one we have: 18 | 19 | * Debug version of Python - great for finding out more detail of your Python code as it executes. 20 | * Valgrind - the goto tool for memory leaks. It is a little tricky to get working but should be in every developers toolbox. 21 | * OS memory monitioring - this is a quick and simple way of identifying whether memory leaks are happening or not. 22 | An example is given below: :ref:`simple-memory-monitor-label` 23 | 24 | .. _debug-tools-debug-python-label: 25 | 26 | .. index:: 27 | single: Debugging; A Debug Python Version 28 | 29 | ------------------------------------------------ 30 | Build a Debug Version of Python 31 | ------------------------------------------------ 32 | 33 | There are a large combination of debug builds of Python that you can create and each one will give you extra information when you either: 34 | 35 | * Invoke Python with a command line option. 36 | 37 | * Example: a ``Py_DEBUG`` build invoking Python with ``python -X showrefcount`` 38 | 39 | * Set an environment variable. 40 | 41 | * Example: a ``Py_DEBUG`` build invoking Python with ``PYTHONMALLOCSTATS=1 python`` 42 | 43 | * Additional functions that are added to the ``sys`` module that can give useful information. 44 | 45 | * Example: a ``Py_DEBUG`` build an calling ``sys.getobjects(...)``. 46 | 47 | 48 | See here :ref:`debug-version-of-python-label` for instructions on how to do this. 49 | 50 | .. _debug-tools-valgrind-label: 51 | 52 | .. index:: 53 | single: Debugging; Valgrind 54 | 55 | ------------------------------------------------ 56 | Valgrind 57 | ------------------------------------------------ 58 | 59 | See here :ref:`building-python-for-valgrind-label` for instructions on how to build Valgrind. 60 | 61 | See here :ref:`using-valgrind-label` for instructions on how to use Valgrind. 62 | 63 | Here :ref:`leaked-new-references-valgrind-label` is an example of finding a leak with Valgrind. 64 | 65 | 66 | .. _simple-memory-monitor-label: 67 | 68 | .. index:: 69 | single: Debugging; Memory Monitor 70 | single: Memory Monitor 71 | see: Memory Monitor; pymemtrace 72 | see: pymemtrace; Memory Monitor 73 | 74 | ------------------------------------------------ 75 | A Simple Memory Monitor 76 | ------------------------------------------------ 77 | 78 | A useful technique is to monitor the memory usage of a Python program. 79 | Here is a simple process memory monitor using the ``psutil`` library. 80 | See the :ref:`memory_leaks-label` chapter for a more comprehensive approach, in particular 81 | :ref:`memory-leaks.pymemtrace`. 82 | 83 | 84 | .. code-block:: python 85 | 86 | import sys 87 | import time 88 | 89 | import psutil 90 | 91 | def memMon(pid, freq=1.0): 92 | proc = psutil.Process(pid) 93 | print(proc.memory_info_ex()) 94 | prev_mem = None 95 | while True: 96 | try: 97 | mem = proc.memory_info().rss / 1e6 98 | if prev_mem is None: 99 | print('{:10.3f} [Mb]'.format(mem)) 100 | else: 101 | print('{:10.3f} [Mb] {:+10.3f} [Mb]'.format(mem, mem - prev_mem)) 102 | prev_mem = mem 103 | time.sleep(freq) 104 | except KeyboardInterrupt: 105 | try: 106 | input(' Pausing memMon, to continue, ^C to end...') 107 | except KeyboardInterrupt: 108 | print('\n') 109 | return 110 | 111 | if __name__ == '__main__': 112 | if len(sys.argv) < 2: 113 | print('Usage: python pidmon.py ') 114 | sys.exit(1) 115 | pid = int(sys.argv[1]) 116 | memMon(pid) 117 | sys.exit(0) 118 | 119 | Lets test it. In one shell fire up Python and find its PID:: 120 | 121 | >>> import os 122 | >>> os.getpid() 123 | 13360 124 | 125 | In a second shell fire up pidmon.py with this PID: 126 | 127 | .. code-block:: bash 128 | 129 | $ python3 pidmon.py 13360 130 | pextmem(rss=7364608, vms=2526482432, pfaults=9793536, pageins=24576) 131 | 7.365 [Mb] 132 | 7.365 [Mb] +0.000 [Mb] 133 | 7.365 [Mb] +0.000 [Mb] 134 | ... 135 | 136 | Pause pidmon.py with Ctrl-C: 137 | 138 | .. code-block:: bash 139 | 140 | ^C Pausing memMon, to continue, ^C to end... 141 | 142 | Go back to the first shell and create a large string (1Gb):: 143 | 144 | >>> s = ' ' * 1024**3 145 | 146 | In the second shell continue pidmon.py with and we see the memory usage: 147 | 148 | .. code-block:: bash 149 | 150 | 1077.932 [Mb] +1070.567 [Mb] 151 | 1077.932 [Mb] +0.000 [Mb] 152 | 1077.932 [Mb] +0.000 [Mb] 153 | ... 154 | 155 | Go back to the first shell and delete the string:: 156 | 157 | >>> del s 158 | 159 | In the second shell we see the memory usage drop: 160 | 161 | .. code-block:: bash 162 | 163 | 1077.953 [Mb] +0.020 [Mb] 164 | 1077.953 [Mb] +0.000 [Mb] 165 | 272.679 [Mb] -805.274 [Mb] 166 | 4.243 [Mb] -268.435 [Mb] 167 | 4.243 [Mb] +0.000 [Mb] 168 | ... 169 | 170 | In the second shell halt pidmon with two Ctrl-C commands: 171 | 172 | .. code-block:: bash 173 | 174 | ^C Pausing memMon, to continue, ^C to end...^C 175 | 176 | So we can observe the total memory usage of another process simply and cheaply. This is often the first test to do when examining processes for memory leaks. 177 | -------------------------------------------------------------------------------- /doc/sphinx/source/debugging/valgrind.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. highlight:: c 5 | :linenothreshold: 10 6 | 7 | .. toctree:: 8 | :maxdepth: 3 9 | 10 | 11 | .. _valgrind-label: 12 | 13 | .. index:: 14 | single: Valgrind 15 | 16 | =============================================== 17 | Valgrind 18 | =============================================== 19 | 20 | This is about how to build Valgrind, a Valgrind friendly version of Python and finally how to use and interpret Valgrind. 21 | 22 | .. note:: 23 | 24 | These instructions have been tested on Mac OS X 10.9 (Mavericks). 25 | They may or may not work on other OS's 26 | 27 | .. index:: 28 | single: Valgrind; Building 29 | 30 | --------------------------------- 31 | Building Valgrind 32 | --------------------------------- 33 | 34 | This should be fairly straightforward: 35 | 36 | .. code-block:: bash 37 | 38 | svn co svn://svn.valgrind.org/valgrind 39 | cd valgrind 40 | ./autogen.sh 41 | ./configure 42 | make 43 | make install 44 | 45 | .. _building-python-for-valgrind-label: 46 | 47 | .. index:: 48 | single: Valgrind; Building Python For 49 | 50 | --------------------------------- 51 | Building Python for Valgrind 52 | --------------------------------- 53 | 54 | 55 | Prepare the source by uncommenting ``Py_USING_MEMORY_DEBUGGER`` in Objects/obmalloc.c around line 1082 or so. 56 | 57 | ^^^^^^^^^^^^^^ 58 | Configuring 59 | ^^^^^^^^^^^^^^ 60 | 61 | ``configure`` takes the following aguments: 62 | 63 | ======================= ================================================================== 64 | Argument 65 | ======================= ================================================================== 66 | ``--enable-framework`` Installs it in /Library/Frameworks/Python.framework/Versions/ 67 | ``--with-pydebug`` Debug build of Python. See Misc/SpecialBuilds.txt 68 | ``--without-pymalloc`` With Valgrind support Misc/README.valgrind 69 | ======================= ================================================================== 70 | 71 | To make a framework install: 72 | 73 | .. code-block:: bash 74 | 75 | ./configure --enable-framework --with-pydebug --without-pymalloc --with-valgrind 76 | sudo make frameworkinstall 77 | 78 | 79 | To make a local version cd to the source tree and we will build a Valgrind version of Python in the ``valgrind/`` directory: 80 | 81 | .. code-block:: bash 82 | 83 | mkdir valgrind 84 | cd valgrind 85 | ../configure --with-pydebug --without-pymalloc --with-valgrind 86 | make 87 | 88 | Check debug build 89 | ----------------- 90 | 91 | .. code-block:: bash 92 | 93 | $ python3 -X showrefcount 94 | 95 | .. code-block:: python 96 | 97 | Python 3.4.3 (default, May 26 2015, 19:54:01) 98 | [GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.51)] on darwin 99 | Type "help", "copyright", "credits" or "license" for more information. 100 | >>> 23 101 | 23 102 | [54793 refs, 0 blocks] 103 | >>> import sys 104 | [54795 refs, 0 blocks] 105 | >>> sys.gettotalrefcount() 106 | 54817 107 | [54795 refs, 0 blocks] 108 | >>> import sysconfig 109 | >>> sysconfig.get_config_var('Py_DEBUG') 110 | 1 111 | 112 | .. _using-valgrind-label: 113 | 114 | .. index:: 115 | single: Valgrind; Using 116 | 117 | --------------------------------- 118 | Using Valgrind 119 | --------------------------------- 120 | 121 | In the ``/Misc`` directory there is a ``valgrind-python.supp`` file that supresses some Valgrind spurious warnings. I find that this needs editing so: 122 | 123 | .. code-block:: bash 124 | 125 | cp /Misc/valgrind-python.supp ~/valgrind-python.supp 126 | vi ~/valgrind-python.supp 127 | 128 | Uncomment ``PyObject_Free`` and ``PyObject_Realloc`` in the valgrind suppression file. 129 | 130 | Invoking the Python interpreter with Valgrind: 131 | 132 | .. code-block:: bash 133 | 134 | valgrind --tool=memcheck --dsymutil=yes --track-origins=yes --show-leak-kinds=all --trace-children=yes --suppressions=$HOME/.valgrind-python.supp /valgrind/python.exe -X showrefcount 135 | 136 | 137 | An example of using Valgrind to detect leaks is here: :ref:`leaked-new-references-valgrind-label`. 138 | -------------------------------------------------------------------------------- /doc/sphinx/source/further_reading.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | .. _further_reading: 8 | 9 | .. index:: 10 | single: Further Reading 11 | 12 | *************************** 13 | Further Reading 14 | *************************** 15 | 16 | Some other sources of information for you. 17 | 18 | .. index:: 19 | single: Further Reading; General 20 | 21 | ============================================ 22 | C Extensions - General 23 | ============================================ 24 | 25 | * python.org `Extension tutorial `_ 26 | * python.org `C/C++ reference `_ 27 | * Joe Jevnik's "How to Write and Debug C Extension Modules" 28 | `Documentation `_ 29 | and `Code `_ 30 | 31 | .. index:: 32 | single: Further Reading; Books 33 | 34 | ============================================ 35 | Books 36 | ============================================ 37 | 38 | * The `CPython Internals book (RealPython) `_ 39 | * The `Python Cookbook `_ 40 | by David Beazley and Brian Jones. 41 | 42 | .. index:: 43 | single: Further Reading; Projects 44 | 45 | ============================================ 46 | Projects 47 | ============================================ 48 | 49 | * Python memory tracing: https://github.com/paulross/pymemtrace 50 | * Python/C++ homogeneous containers: https://github.com/paulross/PyCppContainers 51 | -------------------------------------------------------------------------------- /doc/sphinx/source/images/DebugXcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/doc/sphinx/source/images/DebugXcode.png -------------------------------------------------------------------------------- /doc/sphinx/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Python Extension Patterns documentation master file, created by 2 | sphinx-quickstart on Sun May 11 19:42:01 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Coding Patterns for Python Extensions 7 | ===================================================== 8 | 9 | .. Omitting test chapters that illustrate Sphinx markup: 10 | 11 | _headings 12 | _index_styles 13 | 14 | 15 | .. toctree:: 16 | :numbered: 17 | :maxdepth: 3 18 | 19 | introduction 20 | simple_example 21 | refcount 22 | containers_and_refcounts 23 | struct_sequence 24 | exceptions 25 | canonical_function 26 | parsing_arguments 27 | new_types 28 | module_globals 29 | logging 30 | files 31 | subclassing_and_super_call 32 | capsules 33 | iterators_generators 34 | context_manager 35 | pickle 36 | watchers 37 | compiler_flags 38 | debugging/debug 39 | memory_leaks 40 | thread_safety 41 | code_layout 42 | cpp 43 | miscellaneous 44 | install 45 | further_reading 46 | todo 47 | HISTORY 48 | 49 | Search 50 | ================== 51 | 52 | * :ref:`genindex` 53 | * :ref:`modindex` 54 | * :ref:`search` 55 | -------------------------------------------------------------------------------- /doc/sphinx/source/memory_leaks.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. highlight:: c 5 | :linenothreshold: 10 6 | 7 | .. toctree:: 8 | :maxdepth: 3 9 | 10 | 11 | .. _memory_leaks-label: 12 | 13 | .. index:: 14 | single: Memory Leaks 15 | 16 | ******************* 17 | Memory Leaks 18 | ******************* 19 | 20 | This chapter describes some techniques for detecting and fixing memory leaks. 21 | Much of this is taken from the `pymemtrace project `_ 22 | 23 | .. toctree:: 24 | :maxdepth: 3 25 | 26 | memory_leaks/introduction 27 | memory_leaks/tools 28 | memory_leaks/techniques 29 | memory_leaks/pymemtrace 30 | -------------------------------------------------------------------------------- /doc/sphinx/source/memory_leaks/techniques.rst: -------------------------------------------------------------------------------- 1 | .. moduleauthor:: Paul Ross 2 | .. sectionauthor:: Paul Ross 3 | 4 | .. index:: single: Memory Leaks; Techniques 5 | 6 | Techniques 7 | ==================================== 8 | 9 | This describes some of the techniques I have found useful. 10 | Bear in mind: 11 | 12 | * Tracking down memory leaks can take a long, long time. 13 | * Every memory leak is its own special little snowflake! 14 | So what works will be situation specific. 15 | 16 | High Level 17 | ------------------ 18 | 19 | It is worth spending a fair bit of time at high level before diving into the code since: 20 | 21 | * Working at high level is relatively cheap. 22 | * It is usually non-invasive. 23 | * It will quickly find out the *scale* of the problem. 24 | * It will quickly find out the *repeatability* of the problem. 25 | * You should be able to create the test that shows that the leak is firstly not fixed, then fixed. 26 | 27 | At the end of this you should be able to state: 28 | 29 | * The *frequency* of the memory leak. 30 | * The *severity* of the memory leak. 31 | 32 | Relevant quote: **"Time spent on reconnaissance is seldom wasted."** 33 | 34 | 35 | Using Platform Tools 36 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 37 | 38 | The high level investigation will usually concentrate on using platform tools such as builtin memory management tools or 39 | Python tools such as ``pymentrace``'s :ref:`chapter_memory_leaks.pymemtrace.proces` or ``psutil`` will prove useful. 40 | 41 | Specific Tricks 42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 43 | 44 | TODO: Finish this. 45 | -------------------------------------------------------------------------------- /doc/sphinx/source/miscellaneous.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: c 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | .. _miscellaneous: 8 | 9 | .. index:: 10 | single: Miscellaneous 11 | 12 | ==================================== 13 | Miscellaneous 14 | ==================================== 15 | 16 | This chapter covers various miscellaneous issues that the author has found with creating Python Extensions over the 17 | years. 18 | 19 | ------------------------------------ 20 | No ``PyInit_...`` Function Found 21 | ------------------------------------ 22 | 23 | This is probably Mac OS X and Clang specific but when you import your extension and you get an error like: 24 | 25 | ``ImportError: dynamic module does not define module export function (PyInit_Foo)`` 26 | 27 | Have a look at the binary. 28 | 29 | .. code-block:: sh 30 | 31 | $ nm -m Foo.cpython-36m-darwin.so | grep Init 32 | 00000000000010d0 (__TEXT,__text) non-external (was a private external) _PyInit_Foo 33 | 34 | Sometimes (why?) clang does not make the symbol external. I have found that adding ``__attribute__((visibility("default")))`` to the module initialisation function can fix this: 35 | 36 | .. code-block:: sh 37 | 38 | __attribute__((visibility("default"))) 39 | PyMODINIT_FUNC 40 | PyInit_Foo(void) { 41 | /* ... */ 42 | } 43 | 44 | And the binary now looks like this: 45 | 46 | .. code-block:: sh 47 | 48 | $ nm -m Foo.cpython-36m-darwin.so | grep Init 49 | 00000000000010d0 (__TEXT,__text) external _PyInit_Foo 50 | 51 | .. _miscellaneous_migration_python_c: 52 | 53 | --------------------------------------- 54 | Migrating from Python to a C Extension 55 | --------------------------------------- 56 | 57 | Suppose you have followed my advice in :ref:`introduction_summary_advice` in that you write you code in Python first 58 | then, when profiling shows the slow spots, rewrite in C. 59 | You might not want to do this all at once so here is a technique that allows you to migrate to C in a flexible way, say 60 | over a number of releases, without you users having to change *their* code. 61 | 62 | Suppose you have a bunch of functions and classes in a Python module ``spam.py``. 63 | Then take all that Python code an put it in a file, say, ``py_spam.py``. 64 | Now create an empty C Extension calling it, say, ``c_spam``. 65 | 66 | Change ``spam.py`` to be merely: 67 | 68 | .. code-block:: python 69 | 70 | from py_spam import * 71 | from c_spam import * 72 | 73 | Your users, including your test code, just uses ``import spam`` and they get all of ``py_spam`` for now and nothing 74 | from ``c_spam`` as it is empty. 75 | 76 | You can now, judiciously, add functionality and classes to ``c_spam`` and your users will automatically get those as 77 | ``spam`` overwrites the appropriate imports from ``py_spam`` with the ones from ``c_spam``. 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /doc/sphinx/source/todo.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: python 2 | :linenothreshold: 10 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | .. index:: 8 | single: To Do 9 | 10 | ==================================== 11 | TODO 12 | ==================================== 13 | 14 | -------------------------------------------- 15 | Existing TODOs 16 | -------------------------------------------- 17 | 18 | .. todolist:: 19 | 20 | -------------------------------------------- 21 | Work Estimate for v0.3.0 Release 22 | -------------------------------------------- 23 | 24 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 25 | New Chapter "Debugging Python with CLion" 26 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 27 | 28 | Estimate: High [4] 29 | 30 | 31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 32 | TOTAL 33 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 34 | 35 | 4 points. 36 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx>=7.4 2 | psutil>=6.0 3 | pymemtrace>=0.2 4 | pytest>=8.3 5 | setuptools 6 | -------------------------------------------------------------------------------- /src/FaultHandlerExample.py: -------------------------------------------------------------------------------- 1 | import faulthandler 2 | faulthandler.enable() 3 | 4 | from cPyExtPatt import cPyRefs 5 | 6 | a_list = ['abc' * 200] 7 | cPyRefs.pop_and_print_BAD(a_list) 8 | -------------------------------------------------------------------------------- /src/cCanonical.c: -------------------------------------------------------------------------------- 1 | // 2 | // cCanonical.c 3 | // PythonExtensionPatterns 4 | // 5 | // Created by Paul Ross on 09/05/2014. 6 | // Copyright (c) 2014 Paul Ross. All rights reserved. 7 | // 8 | // This is the Canonical CPython Function. 9 | // This is example code for documentation and is not built. 10 | 11 | #include "Python.h" 12 | 13 | /* So the canonical form is: */ 14 | static PyObject *_func(PyObject *arg1) { 15 | /* Create any local Python *objects as NULL. */ 16 | PyObject *obj_a = NULL; 17 | /* Create the Python object to be returned as NULL. */ 18 | PyObject *ret = NULL; 19 | 20 | goto try; /* Pythonic 'C' ;-) */ 21 | try: 22 | assert(! PyErr_Occurred()); 23 | /* Increment the reference count of the arguments. */ 24 | assert(arg1); 25 | Py_INCREF(arg1); /* Alt: if (arg1) Py_INCREF(arg1); See Py_XDECREF below. */ 26 | 27 | /* Your code here */ 28 | 29 | /* Local object creation. */ 30 | /* obj_a = ...; */ 31 | /* If an error then set error condition and goto except; */ 32 | if (! obj_a) { 33 | PyErr_SetString(PyExc_ValueError, "Ooops."); 34 | goto except; 35 | } 36 | /* Only do this if f obj_a is a borrowed reference. */ 37 | Py_INCREF(obj_a); 38 | 39 | /* More of your code to do stuff with obj_a. */ 40 | 41 | /* Return object creation, ret must be a new reference. */ 42 | /* ret = ...; */ 43 | if (! ret) { 44 | PyErr_SetString(PyExc_ValueError, "Ooops again."); 45 | goto except; 46 | } 47 | 48 | /* Any other error checking here. */ 49 | 50 | /* If success then check exception is clear, 51 | * then goto finally; with non-NULL return value. */ 52 | assert(! PyErr_Occurred()); 53 | assert(ret); 54 | goto finally; 55 | except: 56 | /* Failure so Py_XDECREF the return value here. */ 57 | Py_XDECREF(ret); 58 | /* Check a Python error is set somewhere above. */ 59 | assert(PyErr_Occurred()); 60 | /* Signal failure. */ 61 | ret = NULL; 62 | finally: 63 | /* All _local_ PyObjects declared at the entry point are Py_XDECREF'd here. 64 | * For new references this will free them. For borrowed references this 65 | * will return them to their previous state. 66 | */ 67 | Py_XDECREF(obj_a); 68 | /* Decrement the ref count of externally supplied the arguments here. 69 | * If you allow arg1 == NULL then Py_XDECREF(arg1). */ 70 | Py_DECREF(arg1); 71 | /* And return...*/ 72 | return ret; 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/cPyRefs.so.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.cPyRefs.so 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/cpy/Capsules/custom_capsule.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/06/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_CUSTOM_CAPSULE_H 6 | #define PYTHONEXTENSIONSBASIC_CUSTOM_CAPSULE_H 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | /* Define structure for C API. */ 12 | typedef struct { 13 | /* type objects */ 14 | PyTypeObject *CustomType; 15 | // PyTypeObject *DateTimeType; 16 | // PyTypeObject *TimeType; 17 | // PyTypeObject *DeltaType; 18 | // PyTypeObject *TZInfoType; 19 | // 20 | // /* singletons */ 21 | // PyObject *TimeZone_UTC; 22 | // 23 | // /* constructors */ 24 | // PyObject *(*Date_FromDate)(int, int, int, PyTypeObject*); 25 | // PyObject *(*DateTime_FromDateAndTime)(int, int, int, int, int, int, int, 26 | // PyObject*, PyTypeObject*); 27 | // PyObject *(*Time_FromTime)(int, int, int, int, PyObject*, PyTypeObject*); 28 | // PyObject *(*Delta_FromDelta)(int, int, int, int, PyTypeObject*); 29 | // PyObject *(*TimeZone_FromTimeZone)(PyObject *offset, PyObject *name); 30 | // 31 | // /* constructors for the DB API */ 32 | // PyObject *(*DateTime_FromTimestamp)(PyObject*, PyObject*, PyObject*); 33 | // PyObject *(*Date_FromTimestamp)(PyObject*, PyObject*); 34 | // 35 | // /* PEP 495 constructors */ 36 | // PyObject *(*DateTime_FromDateAndTimeAndFold)(int, int, int, int, int, int, int, 37 | // PyObject*, int, PyTypeObject*); 38 | // PyObject *(*Time_FromTimeAndFold)(int, int, int, int, PyObject*, int, PyTypeObject*); 39 | // 40 | } PyCustom_CAPI; 41 | 42 | #define PyCustom_CAPSULE_NAME "custom3_capsule.CAPI" 43 | 44 | #ifdef CUSTOM_CAPSULE 45 | /* Code that is used for a standard build of custom such as done by setup.py. */ 46 | 47 | #else 48 | /* Code that provides a C API to custom. */ 49 | static void **PyCustom_API; 50 | 51 | #define PySpam_System \ 52 | (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) 53 | 54 | /* Return -1 on error, 0 on success. 55 | * PyCapsule_Import will set an exception if there's an error. 56 | */ 57 | static int 58 | import_custom(void) 59 | { 60 | PyCustom_API = (void **)PyCapsule_Import("custom._C_API", 0); 61 | return (PyCustom_API != NULL) ? 0 : -1; 62 | } 63 | 64 | #endif 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | #endif //PYTHONEXTENSIONSBASIC_CUSTOM_CAPSULE_H 70 | -------------------------------------------------------------------------------- /src/cpy/Capsules/custom_use.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 19/06/2021. 3 | // 4 | #define PY_SSIZE_T_CLEAN 5 | #include 6 | 7 | #include "custom_capsule.h" 8 | 9 | 10 | //PyMODINIT_FUNC 11 | //PyInit_customuse(void) 12 | //{ 13 | // PyObject *m; 14 | // 15 | // m = PyModule_Create(&clientmodule); 16 | // if (m == NULL) 17 | // return NULL; 18 | // if (import_custom() < 0) 19 | // return NULL; 20 | // /* additional initialization can happen here */ 21 | // return m; 22 | //} 23 | -------------------------------------------------------------------------------- /src/cpy/Capsules/spam.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 13/07/2024. 3 | // 4 | // Implements: https://docs.python.org/3/extending/extending.html#extending-simpleexample 5 | // Excludes specific exception. 6 | // Lightly edited. 7 | 8 | #define PY_SSIZE_T_CLEAN 9 | #include 10 | 11 | static PyObject * 12 | spam_system(PyObject *Py_UNUSED(self), PyObject *args) { 13 | const char *command; 14 | int sts; 15 | 16 | if (!PyArg_ParseTuple(args, "s", &command)) 17 | return NULL; 18 | sts = system(command); 19 | return PyLong_FromLong(sts); 20 | } 21 | 22 | static PyMethodDef SpamMethods[] = { 23 | /* ... */ 24 | {"system", spam_system, METH_VARARGS, 25 | "Execute a shell command."}, 26 | /* ... */ 27 | {NULL, NULL, 0, NULL} /* Sentinel */ 28 | }; 29 | 30 | static struct PyModuleDef spammodule = { 31 | PyModuleDef_HEAD_INIT, 32 | "spam", /* name of module */ 33 | PyDoc_STR("Documentation for the spam module"), /* module documentation, may be NULL */ 34 | -1, /* size of per-interpreter state of the module, 35 | or -1 if the module keeps state in global variables. */ 36 | SpamMethods, 37 | NULL, 38 | NULL, 39 | NULL, 40 | NULL, 41 | }; 42 | 43 | PyMODINIT_FUNC 44 | PyInit_spam(void) { 45 | return PyModule_Create(&spammodule); 46 | } 47 | -------------------------------------------------------------------------------- /src/cpy/Capsules/spam_capsule.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 13/07/2024. 3 | // 4 | // Implements: https://docs.python.org/3/extending/extending.html#extending-simpleexample 5 | // as a capsule: https://docs.python.org/3/extending/extending.html#providing-a-c-api-for-an-extension-module 6 | // Includes specific exception. 7 | // Lightly edited. 8 | 9 | #define PY_SSIZE_T_CLEAN 10 | 11 | #include 12 | 13 | #define SPAM_CAPSULE 14 | 15 | #include "spam_capsule.h" 16 | 17 | static int 18 | PySpam_System(const char *command) { 19 | return system(command); 20 | } 21 | 22 | static PyObject * 23 | spam_system(PyObject *Py_UNUSED(self), PyObject *args) { 24 | const char *command; 25 | int sts; 26 | 27 | if (!PyArg_ParseTuple(args, "s", &command)) 28 | return NULL; 29 | sts = PySpam_System(command); 30 | return PyLong_FromLong(sts); 31 | } 32 | 33 | static PyMethodDef SpamMethods[] = { 34 | /* ... */ 35 | {"system", spam_system, METH_VARARGS, 36 | "Execute a shell command."}, 37 | /* ... */ 38 | {NULL, NULL, 0, NULL} /* Sentinel */ 39 | }; 40 | 41 | static struct PyModuleDef spammodule = { 42 | PyModuleDef_HEAD_INIT, 43 | "spam_capsule", /* name of module */ 44 | PyDoc_STR("Documentation for the spam module"), /* module documentation, may be NULL */ 45 | -1, /* size of per-interpreter state of the module, 46 | or -1 if the module keeps state in global variables. */ 47 | SpamMethods, 48 | NULL, NULL, NULL, NULL, 49 | }; 50 | 51 | PyMODINIT_FUNC 52 | PyInit_spam_capsule(void) { 53 | PyObject *m; 54 | static void *PySpam_API[PySpam_API_pointers]; 55 | PyObject *c_api_object; 56 | 57 | m = PyModule_Create(&spammodule); 58 | if (m == NULL) 59 | return NULL; 60 | 61 | /* Initialize the C API pointer array */ 62 | PySpam_API[PySpam_System_NUM] = (void *) PySpam_System; 63 | 64 | /* Create a Capsule containing the API pointer array's address */ 65 | c_api_object = PyCapsule_New((void *) PySpam_API, "cPyExtPatt.Capsules.spam_capsule._C_API", NULL); 66 | 67 | if (PyModule_AddObject(m, "_C_API", c_api_object) < 0) { 68 | Py_XDECREF(c_api_object); 69 | Py_DECREF(m); 70 | return NULL; 71 | } 72 | return m; 73 | } 74 | -------------------------------------------------------------------------------- /src/cpy/Capsules/spam_capsule.h: -------------------------------------------------------------------------------- 1 | #ifndef Py_SPAM_CAPSULE_H 2 | #define Py_SPAM_CAPSULE_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | 9 | /* Header file for spammodule */ 10 | 11 | /* C API functions */ 12 | #define PySpam_System_NUM 0 13 | #define PySpam_System_RETURN int 14 | #define PySpam_System_PROTO (const char *command) 15 | 16 | /* Total number of C API pointers */ 17 | #define PySpam_API_pointers 1 18 | 19 | #ifdef SPAM_CAPSULE 20 | 21 | /* This section is used when compiling spam_capsule.c */ 22 | static PySpam_System_RETURN PySpam_System PySpam_System_PROTO; 23 | 24 | #else 25 | /* This section is used in modules that use spam_capsule's API */ 26 | static void **PySpam_API; 27 | 28 | #define PySpam_System \ 29 | (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM]) 30 | 31 | /* Return -1 on error, 0 on success. 32 | * PyCapsule_Import will set an exception if there's an error. 33 | */ 34 | static int 35 | import_spam_capsule(void) { 36 | PySpam_API = (void **)PyCapsule_Import("cPyExtPatt.Capsules.spam_capsule._C_API", 0); 37 | return (PySpam_API != NULL) ? 0 : -1; 38 | } 39 | #endif 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | #endif /* !defined(Py_SPAM_CAPSULE_H) */ 44 | -------------------------------------------------------------------------------- /src/cpy/Capsules/spam_client.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 13/07/2024. 3 | // 4 | // Implements: https://docs.python.org/3/extending/extending.html#extending-simpleexample 5 | // but using a capsule exported by spam_capsule.h/.c 6 | // Excludes specific exception. 7 | // Lightly edited. 8 | 9 | #define PY_SSIZE_T_CLEAN 10 | 11 | #include 12 | #include "spam_capsule.h" 13 | 14 | static PyObject * 15 | spam_system(PyObject *Py_UNUSED(self), PyObject *args) { 16 | const char *command; 17 | int sts; 18 | 19 | if (!PyArg_ParseTuple(args, "s", &command)) 20 | return NULL; 21 | sts = PySpam_System(command); 22 | return PyLong_FromLong(sts); 23 | } 24 | 25 | static PyMethodDef SpamMethods[] = { 26 | /* ... */ 27 | {"system", spam_system, METH_VARARGS, 28 | "Execute a shell command."}, 29 | /* ... */ 30 | {NULL, NULL, 0, NULL} /* Sentinel */ 31 | }; 32 | 33 | static struct PyModuleDef spam_clientmodule = { 34 | PyModuleDef_HEAD_INIT, 35 | "spam_client", /* name of module */ 36 | PyDoc_STR("Documentation for the spam module"), /* module documentation, may be NULL */ 37 | -1, /* size of per-interpreter state of the module, 38 | or -1 if the module keeps state in global variables. */ 39 | SpamMethods, 40 | NULL, NULL, NULL, NULL, 41 | }; 42 | 43 | PyMODINIT_FUNC 44 | PyInit_spam_client(void) { 45 | PyObject *m; 46 | 47 | m = PyModule_Create(&spam_clientmodule); 48 | if (m == NULL) 49 | return NULL; 50 | if (import_spam_capsule() < 0) 51 | return NULL; 52 | /* additional initialization can happen here */ 53 | return m; 54 | } 55 | -------------------------------------------------------------------------------- /src/cpy/Containers/DebugContainers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 15/12/2024. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONPATTERNS_DEBUGCONTAINERS_H 6 | #define PYTHONEXTENSIONPATTERNS_DEBUGCONTAINERS_H 7 | 8 | #include "pyextpatt_util.h" 9 | 10 | #define ACCEPT_SIGSEGV 0 11 | 12 | #pragma mark - Tuples 13 | void dbg_PyTuple_SetItem_steals(void); 14 | void dbg_PyTuple_SET_ITEM_steals(void); 15 | void dbg_PyTuple_SetItem_steals_replace(void); 16 | void dbg_PyTuple_SET_ITEM_steals_replace(void); 17 | void dbg_PyTuple_SetItem_replace_with_same(void); 18 | void dbg_PyTuple_SET_ITEM_replace_with_same(void); 19 | void dbg_PyTuple_SetIem_NULL(void); 20 | void dbg_PyTuple_SET_ITEM_NULL(void); 21 | void dbg_PyTuple_SetIem_NULL_SetItem(void); 22 | void dbg_PyTuple_SET_ITEM_NULL_SET_ITEM(void); 23 | void dbg_PyTuple_SetItem_fails_not_a_tuple(void); 24 | void dbg_PyTuple_SetItem_fails_out_of_range(void); 25 | void dbg_PyTuple_PyTuple_Pack(void); 26 | void dbg_PyTuple_Py_BuildValue(void); 27 | #pragma mark - Lists 28 | void dbg_PyList_SetItem_steals(void); 29 | void dbg_PyList_SET_ITEM_steals(void); 30 | void dbg_PyList_SetItem_steals_replace(void); 31 | void dbg_PyList_SET_ITEM_steals_replace(void); 32 | void dbg_PyList_SetItem_replace_with_same(void); 33 | void dbg_PyList_SET_ITEM_replace_with_same(void); 34 | void dbg_PyList_SetIem_NULL(void); 35 | void dbg_PyList_SET_ITEM_NULL(void); 36 | void dbg_PyList_SetIem_NULL_SetItem(void); 37 | void dbg_PyList_SET_ITEM_NULL_SET_ITEM(void); 38 | void dbg_PyList_SetItem_fails_not_a_tuple(void); 39 | void dbg_PyList_SetItem_fails_out_of_range(void); 40 | void dbg_PyList_Append(void); 41 | void dbg_PyList_Append_fails_not_a_list(void); 42 | void dbg_PyList_Append_fails_NULL(void); 43 | void dbg_PyList_Insert(void); 44 | void dbg_PyList_Insert_Is_Truncated(void); 45 | void dbg_PyList_Insert_Negative_Index(void); 46 | void dbg_PyList_Insert_fails_not_a_list(void); 47 | void dbg_PyList_Insert_fails_NULL(void); 48 | void dbg_PyList_Py_BuildValue(void); 49 | 50 | #pragma mark - Dictionaries - setters 51 | void dbg_PyDict_SetItem_increments(void); 52 | 53 | #if ACCEPT_SIGSEGV 54 | void dbg_PyDict_SetItem_NULL_key(void); 55 | void dbg_PyDict_SetItem_NULL_value(void); 56 | #endif // ACCEPT_SIGSEGV 57 | 58 | void dbg_PyDict_SetItem_fails_not_a_dict(void); 59 | void dbg_PyDict_SetItem_fails_not_hashable(void); 60 | void dbg_PyDict_SetDefault_default_unused(void); 61 | void dbg_PyDict_SetDefault_default_used(void); 62 | void dbg_PyDict_SetDefaultRef_default_unused(void); 63 | #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 64 | void dbg_PyDict_SetDefaultRef_default_used(void); 65 | void dbg_PyDict_SetDefaultRef_default_unused_result_non_null(void); 66 | #endif // #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 67 | 68 | #pragma mark - Dictionaries - getters 69 | void dbg_PyDict_GetItem(void); 70 | #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 71 | void dbg_PyDict_GetItemRef(void); 72 | #endif // #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 73 | void dbg_PyDict_GetItemWithError_fails(void); 74 | 75 | #pragma mark - Dictionaries - deleters 76 | 77 | #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 78 | void dbg_PyDict_Pop_key_present(void); 79 | void dbg_PyDict_Pop_key_absent(void); 80 | #endif // #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13 81 | 82 | #pragma mark - Sets 83 | 84 | void dbg_PySet_Add(void); 85 | void dbg_PySet_Discard(void); 86 | void dbg_PySet_Pop(void); 87 | 88 | #pragma mark - Struct Sequence 89 | 90 | void dbg_PyStructSequence_simple_ctor(void); 91 | void dbg_PyStructSequence_setitem_abandons(void); 92 | void dbg_PyStructSequence_n_in_sequence_too_large(void); 93 | void dbg_PyStructSequence_with_unnamed_field(void); 94 | 95 | #if ACCEPT_SIGSEGV 96 | void dbg_PyTuple_SetItem_SIGSEGV_on_same_value(void); 97 | void dbg_PyList_SetItem_SIGSEGV_on_same_value(void); 98 | void dbg_PyDict_SetItem_SIGSEGV_on_key_NULL(void); 99 | void dbg_PyDict_SetItem_SIGSEGV_on_value_NULL(void); 100 | void dbg_PyDict_GetItem_key_NULL(void); 101 | #endif 102 | 103 | #endif //PYTHONEXTENSIONPATTERNS_DEBUGCONTAINERS_H 104 | -------------------------------------------------------------------------------- /src/cpy/CtxMgr/cCtxMgr.c: -------------------------------------------------------------------------------- 1 | /* A context manager example. */ 2 | 3 | /* MyObj objects */ 4 | 5 | #define PY_SSIZE_T_CLEAN 6 | 7 | #include "Python.h" 8 | 9 | static const ssize_t BUFFER_LENGTH = (ssize_t)1024 * 1024 * 128; 10 | 11 | typedef struct { 12 | PyObject_HEAD 13 | /* Buffer created for the lifetime of the object. A memory check can show leaks. */ 14 | char *buffer_lifetime; 15 | /* Buffer created for the lifetime of the context. A memory check can show leaks. */ 16 | char *buffer_context; 17 | } ContextManager; 18 | 19 | /** Forward declaration. */ 20 | static PyTypeObject ContextManager_Type; 21 | 22 | #define ContextManager_Check(v) (Py_TYPE(v) == &ContextManager_Type) 23 | 24 | static ContextManager * 25 | ContextManager_new(PyObject *Py_UNUSED(arg)) { 26 | ContextManager *self; 27 | self = PyObject_New(ContextManager, &ContextManager_Type); 28 | if (self == NULL) { 29 | return NULL; 30 | } 31 | self->buffer_lifetime = malloc(BUFFER_LENGTH); 32 | // Force an initialisation. 33 | for (ssize_t i = 0; i < BUFFER_LENGTH; ++i) { 34 | self->buffer_lifetime[i] = ' '; 35 | } 36 | self->buffer_context = NULL; 37 | // fprintf(stdout, "%24s DONE REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 38 | return self; 39 | } 40 | 41 | /* ContextManager methods */ 42 | static void 43 | ContextManager_dealloc(ContextManager *self) { 44 | // fprintf(stdout, "%24s STRT REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 45 | free(self->buffer_lifetime); 46 | self->buffer_lifetime = NULL; 47 | assert(self->buffer_context == NULL); 48 | PyObject_Del(self); 49 | // fprintf(stdout, "%24s DONE REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 50 | } 51 | 52 | static PyObject * 53 | ContextManager_enter(ContextManager *self, PyObject *Py_UNUSED(args)) { 54 | assert(self->buffer_lifetime != NULL); 55 | assert(self->buffer_context == NULL); 56 | // fprintf(stdout, "%24s STRT REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 57 | self->buffer_context = malloc(BUFFER_LENGTH); 58 | // Force an initialisation. 59 | for (ssize_t i = 0; i < BUFFER_LENGTH; ++i) { 60 | self->buffer_context[i] = ' '; 61 | } 62 | Py_INCREF(self); 63 | // fprintf(stdout, "%24s DONE REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 64 | return (PyObject *)self; 65 | } 66 | 67 | static PyObject * 68 | ContextManager_exit(ContextManager *self, PyObject *Py_UNUSED(args)) { 69 | assert(self->buffer_lifetime != NULL); 70 | assert(self->buffer_context != NULL); 71 | // fprintf(stdout, "%24s STRT REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 72 | free(self->buffer_context); 73 | self->buffer_context = NULL; 74 | // fprintf(stdout, "%24s DONE REFCNT = %zd\n", __FUNCTION__, Py_REFCNT(self)); 75 | Py_RETURN_FALSE; 76 | } 77 | 78 | static PyObject * 79 | ContextManager_len_buffer_lifetime(ContextManager *self, PyObject *Py_UNUSED(args)) { 80 | return Py_BuildValue("n", self->buffer_lifetime ? BUFFER_LENGTH : 0); 81 | } 82 | 83 | static PyObject * 84 | ContextManager_len_buffer_context(ContextManager *self, PyObject *Py_UNUSED(args)) { 85 | return Py_BuildValue("n", self->buffer_context ? BUFFER_LENGTH : 0); 86 | } 87 | 88 | static PyMethodDef ContextManager_methods[] = { 89 | {"__enter__", (PyCFunction) ContextManager_enter, METH_NOARGS, 90 | PyDoc_STR("__enter__() -> ContextManager")}, 91 | {"__exit__", (PyCFunction) ContextManager_exit, METH_VARARGS, 92 | PyDoc_STR("__exit__(exc_type, exc_value, exc_tb) -> bool")}, 93 | {"len_buffer_lifetime", (PyCFunction) ContextManager_len_buffer_lifetime, METH_NOARGS, 94 | PyDoc_STR("len_buffer_lifetime() -> int")}, 95 | {"len_buffer_context", (PyCFunction) ContextManager_len_buffer_context, METH_NOARGS, 96 | PyDoc_STR("len_buffer_context() -> int")}, 97 | {NULL, NULL, 0, NULL} /* sentinel */ 98 | }; 99 | 100 | static PyTypeObject ContextManager_Type = { 101 | PyVarObject_HEAD_INIT(NULL, 0) 102 | .tp_name = "cObject.ContextManager", 103 | .tp_basicsize = sizeof(ContextManager), 104 | .tp_dealloc = (destructor) ContextManager_dealloc, 105 | .tp_flags = Py_TPFLAGS_DEFAULT, 106 | .tp_methods = ContextManager_methods, 107 | .tp_new = (newfunc) ContextManager_new, 108 | }; 109 | 110 | PyDoc_STRVAR(module_doc, "Example of a context manager."); 111 | 112 | static struct PyModuleDef cCtxMgr = { 113 | PyModuleDef_HEAD_INIT, 114 | .m_name = "cCtxMgr", 115 | .m_doc = module_doc, 116 | .m_size = -1, 117 | }; 118 | 119 | PyMODINIT_FUNC 120 | PyInit_cCtxMgr(void) { 121 | PyObject *m = NULL; 122 | /* Create the module and add the functions */ 123 | m = PyModule_Create(&cCtxMgr); 124 | if (m == NULL) { 125 | goto fail; 126 | } 127 | /* Finalize the type object including setting type of the new type 128 | * object; doing it here is required for portability, too. */ 129 | if (PyType_Ready(&ContextManager_Type) < 0) { 130 | goto fail; 131 | } 132 | if (PyModule_AddObject(m, "ContextManager", (PyObject *) &ContextManager_Type)) { 133 | goto fail; 134 | } 135 | if (PyModule_AddObject(m, "BUFFER_LENGTH", Py_BuildValue("n", BUFFER_LENGTH))) { 136 | goto fail; 137 | } 138 | return m; 139 | fail: 140 | Py_XDECREF(m); 141 | return NULL; 142 | } 143 | -------------------------------------------------------------------------------- /src/cpy/File/PythonFileWrapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 08/07/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHONFILEWRAPPER_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHONFILEWRAPPER_H 7 | #define PY_SSIZE_T_CLEAN 8 | 9 | #include 10 | #include "structmember.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | //#include 17 | 18 | class ExceptionPythonFileObjectWrapper : public std::exception { 19 | public: 20 | explicit ExceptionPythonFileObjectWrapper(std::string in_msg) : m_msg(std::move(in_msg)) {} 21 | 22 | [[nodiscard]] const std::string &message() const { return m_msg; } 23 | 24 | [[nodiscard]] const char *what() const 25 | 26 | noexcept override{return m_msg.c_str();} 27 | protected: 28 | std::string m_msg{}; 29 | }; 30 | 31 | 32 | /// Class that is created with a PyObject* that looks like a Python File. 33 | /// This can then read from that file object ans write to a user provided C++ stream or read from a user provided C++ 34 | /// stream and write to the give Python file like object. 35 | class PythonFileObjectWrapper { 36 | public: 37 | explicit PythonFileObjectWrapper(PyObject *python_file_object); 38 | 39 | /// Read from a Python file and write to the C++ stream. 40 | /// Return zero on success, non-zero on failure. 41 | int read_py_write_cpp(Py_ssize_t number_of_bytes, std::iostream &ios); 42 | 43 | /// Read from a C++ stream and write to a Python file. 44 | /// Return zero on success, non-zero on failure. 45 | int read_cpp_write_py(std::iostream &ios, Py_ssize_t number_of_bytes); 46 | 47 | /// Read a number of bytes from a Python file and load them into the result. 48 | /// Return zero on success, non-zero on failure. 49 | int read(Py_ssize_t number_of_bytes, std::vector &result); 50 | 51 | /// Write a number of bytes to a Python file. 52 | /// Return zero on success, non-zero on failure. 53 | int write(const char *buffer, Py_ssize_t number_of_bytes); 54 | 55 | /// Move the file pointer to the given position. 56 | /// whence is: 57 | /// 0 – start of the stream (the default); offset should be zero or positive. 58 | /// 1 – current stream position; offset may be negative. 59 | /// 2 – end of the stream; offset is usually negative. 60 | /// Returns the new absolute position. 61 | long seek(Py_ssize_t pos, int whence = 0); 62 | 63 | /// Returns the current absolute position. 64 | long tell(); 65 | /// Returns a multi-line string that describes the class state. 66 | std::string str_pointers() const; 67 | /// Returns a Python multi-line bytes object that describes the class state. 68 | PyObject *py_str_pointers() const; 69 | /// Destructor, this decrements the held references. 70 | virtual ~PythonFileObjectWrapper(); 71 | 72 | protected: 73 | PyObject *m_python_file_object = NULL; 74 | PyObject *m_python_read_method = NULL; 75 | PyObject *m_python_write_method = NULL; 76 | PyObject *m_python_seek_method = NULL; 77 | PyObject *m_python_tell_method = NULL; 78 | }; 79 | 80 | #endif //PYTHONEXTENSIONSBASIC_PYTHONFILEWRAPPER_H 81 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/cFibA.c: -------------------------------------------------------------------------------- 1 | #define PPY_SSIZE_T_CLEAN 2 | 3 | #include "Python.h" 4 | 5 | long fibonacci(long index) { 6 | if (index < 2) { 7 | return index; 8 | } 9 | return fibonacci(index - 2) + fibonacci(index - 1); 10 | } 11 | 12 | //long fibonacci(long index) { 13 | // static long *cache = NULL; 14 | // if (!cache) { 15 | // /* FIXME */ 16 | // cache = calloc(1000, sizeof(long)); 17 | // } 18 | // if (index < 2) { 19 | // return index; 20 | // } 21 | // if (!cache[index]) { 22 | // cache[index] = fibonacci(index - 2) + fibonacci(index - 1); 23 | // } 24 | // return cache[index]; 25 | //} 26 | 27 | static PyObject * 28 | py_fibonacci(PyObject *Py_UNUSED(module), PyObject *args) { 29 | long index; 30 | 31 | if (!PyArg_ParseTuple(args, "l", &index)) { 32 | return NULL; 33 | } 34 | long result = fibonacci(index); 35 | return Py_BuildValue("l", result); 36 | } 37 | 38 | //static PyObject * 39 | //py_fibonacci(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs) { 40 | // long index; 41 | // 42 | // static char *keywords[] = {"index", NULL}; 43 | // if (!PyArg_ParseTupleAndKeywords(args, kwargs, "l", keywords, &index)) { 44 | // return NULL; 45 | // } 46 | // long result = fibonacci(index); 47 | // return Py_BuildValue("l", result); 48 | //} 49 | // 50 | 51 | static PyMethodDef module_methods[] = { 52 | {"fibonacci", 53 | (PyCFunction) py_fibonacci, 54 | METH_VARARGS, 55 | "Returns the Fibonacci value." 56 | }, 57 | {NULL, NULL, 0, NULL} /* Sentinel */ 58 | }; 59 | 60 | static PyModuleDef cFibA = { 61 | PyModuleDef_HEAD_INIT, 62 | .m_name = "cFibA", 63 | .m_doc = "Fibonacci in C.", 64 | .m_size = -1, 65 | .m_methods = module_methods, 66 | }; 67 | 68 | PyMODINIT_FUNC PyInit_cFibA(void) { 69 | PyObject *m = PyModule_Create(&cFibA); 70 | return m; 71 | } 72 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/cFibA.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 20/02/2023. 3 | // 4 | 5 | #ifndef PYEXTEXAMPLE_CFIB_H 6 | #define PYEXTEXAMPLE_CFIB_H 7 | 8 | long fibonacci(long index); 9 | 10 | #endif //PYEXTEXAMPLE_CFIB_H 11 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/cFibB.c: -------------------------------------------------------------------------------- 1 | #define PPY_SSIZE_T_CLEAN 2 | 3 | #include "Python.h" 4 | 5 | long fibonacci(long index) { 6 | static long *cache = NULL; 7 | if (!cache) { 8 | /* FIXME */ 9 | cache = calloc(1000, sizeof(long)); 10 | } 11 | if (index < 2) { 12 | return index; 13 | } 14 | if (!cache[index]) { 15 | cache[index] = fibonacci(index - 2) + fibonacci(index - 1); 16 | } 17 | return cache[index]; 18 | } 19 | 20 | static PyObject * 21 | py_fibonacci(PyObject *Py_UNUSED(module), PyObject *args) { 22 | long index; 23 | 24 | if (!PyArg_ParseTuple(args, "l", &index)) { 25 | return NULL; 26 | } 27 | long result = fibonacci(index); 28 | return Py_BuildValue("l", result); 29 | } 30 | 31 | //static PyObject * 32 | //py_fibonacci(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs) { 33 | // long index; 34 | // 35 | // static char *keywords[] = {"index", NULL}; 36 | // if (!PyArg_ParseTupleAndKeywords(args, kwargs, "l", keywords, &index)) { 37 | // return NULL; 38 | // } 39 | // long result = fibonacci(index); 40 | // return Py_BuildValue("l", result); 41 | //} 42 | // 43 | 44 | static PyMethodDef module_methods[] = { 45 | { 46 | "fibonacci", 47 | (PyCFunction) py_fibonacci, 48 | METH_VARARGS, 49 | "Returns the Fibonacci value." 50 | }, 51 | {NULL, NULL, 0, NULL} /* Sentinel */ 52 | }; 53 | 54 | static PyModuleDef cFibB = { 55 | PyModuleDef_HEAD_INIT, 56 | .m_name = "cFibB", 57 | .m_doc = "Fibonacci in C with cache.", 58 | .m_size = -1, 59 | .m_methods = module_methods, 60 | }; 61 | 62 | PyMODINIT_FUNC PyInit_cFibB(void) { 63 | PyObject *m = PyModule_Create(&cFibB); 64 | return m; 65 | } 66 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/pFibA.py: -------------------------------------------------------------------------------- 1 | def fibonacci(index: int) -> int: 2 | if index < 2: 3 | return index 4 | return fibonacci(index - 2) + fibonacci(index - 1) 5 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/pFibB.py: -------------------------------------------------------------------------------- 1 | import functools 2 | 3 | @functools.cache 4 | def fibonacci(index: int) -> int: 5 | if index < 2: 6 | return index 7 | return fibonacci(index - 2) + fibonacci(index - 1) 8 | -------------------------------------------------------------------------------- /src/cpy/SimpleExample/timeit_test.py: -------------------------------------------------------------------------------- 1 | import timeit 2 | 3 | index = 32 4 | number = 20 5 | 6 | print(f'Index: {index} number of times: {number}') 7 | print('Version A, no cacheing:') 8 | # Use pFibA 9 | ti_py = timeit.timeit(f'pFibA.fibonacci({index})', setup='import pFibA', number=number) 10 | print(f'Python timeit: {ti_py:8.6f}') 11 | 12 | # Use cFibA 13 | ti_c = timeit.timeit(f'cFibA.fibonacci({index})', 14 | setup='from cPyExtPatt.SimpleExample import cFibA', number=number) 15 | print(f' C timeit: {ti_c:8.6f}') 16 | 17 | print( 18 | f'C is {ti_py / ti_c if ti_py > ti_c else ti_c / ti_py:.1f}' 19 | f' times {"FASTER" if ti_py > ti_c else "SLOWER"}.' 20 | ) 21 | 22 | print() 23 | print('Version A with Python cache, no C cache:') 24 | # Use pFibB 25 | ti_py = timeit.timeit(f'pFibB.fibonacci({index})', setup='import pFibB', number=number) 26 | print(f'Python timeit: {ti_py:8.6f}') 27 | 28 | # Use cFibB 29 | ti_c = timeit.timeit(f'cFibA.fibonacci({index})', 30 | setup='from cPyExtPatt.SimpleExample import cFibA', number=number) 31 | print(f' C timeit: {ti_c:8.6f}') 32 | 33 | print( 34 | f'C is {ti_py / ti_c if ti_py > ti_c else ti_c / ti_py:.1f}' 35 | f' times {"FASTER" if ti_py > ti_c else "SLOWER"}.' 36 | ) 37 | 38 | print() 39 | print('Version B, both are cached:') 40 | # Use pFibB 41 | ti_py = timeit.timeit(f'pFibB.fibonacci({index})', setup='import pFibB', number=number) 42 | print(f'Python timeit: {ti_py:8.6f}') 43 | 44 | # Use cFibB 45 | ti_c = timeit.timeit(f'cFibB.fibonacci({index})', 46 | setup='from cPyExtPatt.SimpleExample import cFibB', number=number) 47 | print(f' C timeit: {ti_c:8.6f}') 48 | 49 | print( 50 | f'C is {ti_py / ti_c if ti_py > ti_c else ti_c / ti_py:.1f}' 51 | f' times {"FASTER" if ti_py > ti_c else "SLOWER"}.' 52 | ) 53 | -------------------------------------------------------------------------------- /src/cpy/SubClass/sublist.c: -------------------------------------------------------------------------------- 1 | // 2 | // sublist.c 3 | // Subclassing a Python list. 4 | // 5 | // Created by Paul Ross on 22/07/2024. 6 | // Copyright (c) 2024 Paul Ross. All rights reserved. 7 | // 8 | // Based on: https://docs.python.org/3/extending/newtypes_tutorial.html#subclassing-other-types 9 | // That describes sub-classing a list. 10 | // However as well as the increment function this counts how many times 11 | // append() is called and uses the super() class to call the base class append. 12 | 13 | #define PY_SSIZE_T_CLEAN 14 | #include 15 | #include "structmember.h" 16 | 17 | #include "py_call_super.h" 18 | 19 | typedef struct { 20 | PyListObject list; 21 | int state; 22 | int appends; 23 | } SubListObject; 24 | 25 | static int 26 | SubList_init(SubListObject *self, PyObject *args, PyObject *kwds) { 27 | if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0) { 28 | return -1; 29 | } 30 | self->state = 0; 31 | self->appends = 0; 32 | return 0; 33 | } 34 | 35 | static PyObject * 36 | SubList_increment(SubListObject *self, PyObject *Py_UNUSED(unused)) { 37 | self->state++; 38 | return PyLong_FromLong(self->state); 39 | } 40 | 41 | static PyObject * 42 | SubList_append(SubListObject *self, PyObject *args) { 43 | PyObject *result = call_super_name((PyObject *)self, "append", 44 | args, NULL); 45 | if (result) { 46 | self->appends++; 47 | } 48 | return result; 49 | } 50 | 51 | 52 | static PyMethodDef SubList_methods[] = { 53 | {"increment", (PyCFunction) SubList_increment, METH_NOARGS, 54 | PyDoc_STR("increment state counter")}, 55 | {"append", (PyCFunction) SubList_append, METH_VARARGS, 56 | PyDoc_STR("append an item")}, 57 | {NULL, NULL, 0, NULL}, 58 | }; 59 | 60 | static PyMemberDef SubList_members[] = { 61 | {"state", T_INT, offsetof(SubListObject, state), 0, 62 | "Value of the state."}, 63 | {"appends", T_INT, offsetof(SubListObject, appends), 0, 64 | "Number of append operations."}, 65 | {NULL, 0, 0, 0, NULL} /* Sentinel */ 66 | }; 67 | 68 | static PyTypeObject SubListType = { 69 | PyVarObject_HEAD_INIT(NULL, 0) 70 | .tp_name = "sublist.SubList", 71 | .tp_doc = PyDoc_STR("SubList objects"), 72 | .tp_basicsize = sizeof(SubListObject), 73 | .tp_itemsize = 0, 74 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 75 | .tp_init = (initproc) SubList_init, 76 | .tp_methods = SubList_methods, 77 | .tp_members = SubList_members, 78 | }; 79 | 80 | static PyModuleDef sublistmodule = { 81 | PyModuleDef_HEAD_INIT, 82 | .m_name = "sublist", 83 | .m_doc = "Module that contains a subclass of a list.", 84 | .m_size = -1, 85 | }; 86 | 87 | PyMODINIT_FUNC 88 | PyInit_sublist(void) { 89 | PyObject *m; 90 | SubListType.tp_base = &PyList_Type; 91 | if (PyType_Ready(&SubListType) < 0) { 92 | return NULL; 93 | } 94 | m = PyModule_Create(&sublistmodule); 95 | if (m == NULL) { 96 | return NULL; 97 | } 98 | Py_INCREF(&SubListType); 99 | if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) { 100 | Py_DECREF(&SubListType); 101 | Py_DECREF(m); 102 | return NULL; 103 | } 104 | return m; 105 | } 106 | -------------------------------------------------------------------------------- /src/cpy/Threads/cThreadLock.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 22/07/2024. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONPATTERNS_CTHREADLOCK_H 6 | #define PYTHONEXTENSIONPATTERNS_CTHREADLOCK_H 7 | 8 | #include 9 | #include "structmember.h" 10 | 11 | #ifdef WITH_THREAD 12 | #include "pythread.h" 13 | #endif 14 | 15 | #ifdef WITH_THREAD 16 | /* A RAII wrapper around the PyThread_type_lock. */ 17 | template 18 | class AcquireLock { 19 | public: 20 | AcquireLock(T *pObject) : m_pObject(pObject) { 21 | assert(m_pObject); 22 | assert(m_pObject->lock); 23 | Py_INCREF(m_pObject); 24 | if (!PyThread_acquire_lock(m_pObject->lock, NOWAIT_LOCK)) { 25 | Py_BEGIN_ALLOW_THREADS 26 | PyThread_acquire_lock(m_pObject->lock, WAIT_LOCK); 27 | Py_END_ALLOW_THREADS 28 | } 29 | } 30 | ~AcquireLock() { 31 | assert(m_pObject); 32 | assert(m_pObject->lock); 33 | PyThread_release_lock(m_pObject->lock); 34 | Py_DECREF(m_pObject); 35 | } 36 | private: 37 | T *m_pObject; 38 | }; 39 | 40 | #else 41 | /* Make the class a NOP which should get optimised out. */ 42 | template 43 | class AcquireLock { 44 | public: 45 | AcquireLock(T *) {} 46 | }; 47 | #endif 48 | 49 | // From https://github.com/python/cpython/blob/main/Modules/_bz2module.c 50 | // #define ACQUIRE_LOCK(obj) do { \ 51 | // if (!PyThread_acquire_lock((obj)->lock, 0)) { \ 52 | // Py_BEGIN_ALLOW_THREADS \ 53 | // PyThread_acquire_lock((obj)->lock, 1); \ 54 | // Py_END_ALLOW_THREADS \ 55 | // } } while (0) 56 | //#define RELEASE_LOCK(obj) PyThread_release_lock((obj)->lock) 57 | 58 | // /Library/Frameworks/Python.framework/Versions/3.11/include/python3.11/ceval.h 59 | // #define Py_BEGIN_ALLOW_THREADS { \ 60 | // PyThreadState *_save; \ 61 | // _save = PyEval_SaveThread(); 62 | //#define Py_BLOCK_THREADS PyEval_RestoreThread(_save); 63 | //#define Py_UNBLOCK_THREADS _save = PyEval_SaveThread(); 64 | //#define Py_END_ALLOW_THREADS PyEval_RestoreThread(_save); \ 65 | // } 66 | 67 | #endif //PYTHONEXTENSIONPATTERNS_CTHREADLOCK_H 68 | -------------------------------------------------------------------------------- /src/cpy/Threads/cppsublist.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // cppsublist.cpp 3 | // Subclassing a Python list. 4 | // 5 | // Created by Paul Ross on 22/07/2024. 6 | // Copyright (c) 2024 Paul Ross. All rights reserved. 7 | // 8 | // Based on: https://docs.python.org/3/extending/newtypes_tutorial.html#subclassing-other-types 9 | // 10 | // This is very like src/cpy/SubClass/sublist.c but it includes a slow max() method 11 | // to illustrate thread contention. 12 | // So it needs a thread lock. 13 | 14 | #define PY_SSIZE_T_CLEAN 15 | 16 | #include 17 | #include "structmember.h" 18 | 19 | #include "py_call_super.h" 20 | #include "cThreadLock.h" 21 | #include 22 | 23 | 24 | typedef struct { 25 | PyListObject list; 26 | #ifdef WITH_THREAD 27 | PyThread_type_lock lock; 28 | #endif 29 | } SubListObject; 30 | 31 | static int 32 | SubList_init(SubListObject *self, PyObject *args, PyObject *kwds) { 33 | if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0) { 34 | return -1; 35 | } 36 | #ifdef WITH_THREAD 37 | self->lock = PyThread_allocate_lock(); 38 | if (self->lock == NULL) { 39 | PyErr_SetString(PyExc_MemoryError, "Unable to allocate thread lock."); 40 | return -2; 41 | } 42 | #endif 43 | return 0; 44 | } 45 | 46 | static void 47 | SubList_dealloc(SubListObject *self) { 48 | /* Deallocate other fields here. */ 49 | #ifdef WITH_THREAD 50 | if (self->lock) { 51 | PyThread_free_lock(self->lock); 52 | self->lock = NULL; 53 | } 54 | #endif 55 | Py_TYPE(self)->tp_free((PyObject *)self); 56 | } 57 | 58 | void sleep_milliseconds(long ms) { 59 | struct timespec tim_request, tim_remain; 60 | tim_request.tv_sec = 0; 61 | tim_request.tv_nsec = ms * 1000L * 1000L; 62 | nanosleep(&tim_request, &tim_remain); 63 | } 64 | 65 | /** append with a thread lock. */ 66 | static PyObject * 67 | SubList_append(SubListObject *self, PyObject *args) { 68 | AcquireLock local_lock((SubListObject *)self); 69 | PyObject *result = call_super_name( 70 | (PyObject *) self, "append", args, NULL 71 | ); 72 | // 0.25s delay to demonstrate holding on to the thread. 73 | sleep_milliseconds(250L); 74 | return result; 75 | } 76 | 77 | /** This is a deliberately laborious find of the maximum value to 78 | * demonstrate protection against thread contention. 79 | */ 80 | static PyObject * 81 | SubList_max(PyObject *self, PyObject *Py_UNUSED(unused)) { 82 | assert(!PyErr_Occurred()); 83 | AcquireLock local_lock((SubListObject *)self); 84 | PyObject *ret = NULL; 85 | // SubListObject 86 | size_t length = PyList_Size(self); 87 | if (length == 0) { 88 | // Raise 89 | PyErr_SetString(PyExc_ValueError, "max() on empty list."); 90 | } else { 91 | // Return first 92 | ret = PyList_GetItem(self, 0); 93 | if (length > 1) { 94 | // laborious compare 95 | PyObject *item = NULL; 96 | for(Py_ssize_t i = 1; i 0) { 103 | ret = item; 104 | } 105 | // 2ms delay to demonstrate holding on to the thread. 106 | sleep_milliseconds(2L); 107 | } 108 | } 109 | Py_INCREF(ret); 110 | } 111 | // // 0.25s delay to demonstrate holding on to the thread. 112 | // sleep_milliseconds(250L); 113 | return ret; 114 | } 115 | 116 | static PyMethodDef SubList_methods[] = { 117 | {"append", (PyCFunction) SubList_append, METH_VARARGS, 118 | PyDoc_STR("append an item with sleep(1).")}, 119 | {"max", (PyCFunction) SubList_max, METH_NOARGS, 120 | PyDoc_STR("Return the maximum value with sleep(1).")}, 121 | {NULL, NULL, 0, NULL}, 122 | }; 123 | 124 | static PyMemberDef SubList_members[] = { 125 | {NULL, 0, 0, 0, NULL} /* Sentinel */ 126 | }; 127 | 128 | static PyTypeObject cppSubListType = { 129 | PyVarObject_HEAD_INIT(NULL, 0) 130 | .tp_name = "cppsublist.cppSubList", 131 | .tp_basicsize = sizeof(SubListObject), 132 | .tp_itemsize = 0, 133 | .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, 134 | .tp_doc = PyDoc_STR("C++ SubList object"), 135 | .tp_methods = SubList_methods, 136 | .tp_members = SubList_members, 137 | .tp_init = (initproc) SubList_init, 138 | .tp_dealloc = (destructor) SubList_dealloc, 139 | }; 140 | 141 | static PyModuleDef cppsublistmodule = { 142 | PyModuleDef_HEAD_INIT, 143 | .m_name = "cppsublist", 144 | .m_doc = "Example module that creates an extension type.", 145 | .m_size = -1, 146 | }; 147 | 148 | PyMODINIT_FUNC 149 | PyInit_cppsublist(void) { 150 | PyObject * m; 151 | cppSubListType.tp_base = &PyList_Type; 152 | if (PyType_Ready(&cppSubListType) < 0) { 153 | return NULL; 154 | } 155 | m = PyModule_Create(&cppsublistmodule); 156 | if (m == NULL) { 157 | return NULL; 158 | } 159 | Py_INCREF(&cppSubListType); 160 | if (PyModule_AddObject(m, "cppSubList", (PyObject *) &cppSubListType) < 0) { 161 | Py_DECREF(&cppSubListType); 162 | Py_DECREF(m); 163 | return NULL; 164 | } 165 | return m; 166 | } 167 | -------------------------------------------------------------------------------- /src/cpy/Util/py_call_super.h: -------------------------------------------------------------------------------- 1 | // 2 | // py_call_super.h 3 | // PythonSubclassList 4 | // 5 | // Provides C functions to call the Python super() class. 6 | // 7 | // Created by Paul Ross on 03/05/2016. 8 | // Copyright (c) 2016 Paul Ross. All rights reserved. 9 | // 10 | 11 | #ifndef __UTIL_PY_CALL_SUPER__ 12 | #define __UTIL_PY_CALL_SUPER__ 13 | 14 | #include 15 | 16 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 17 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 18 | * func_name is a Python string. 19 | * The implementation creates a new super object on each call. 20 | */ 21 | PyObject * 22 | call_super_pyname(PyObject *self, PyObject *func_name, PyObject *args, PyObject *kwargs); 23 | 24 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 25 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 26 | * func_name is a C string. 27 | * The implementation creates a new super object on each call. 28 | */ 29 | PyObject * 30 | call_super_name(PyObject *self, const char *func_cname, PyObject *args, PyObject *kwargs); 31 | 32 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 33 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 34 | * func_name is a Python string. 35 | * The implementation uses the builtin super(). 36 | */ 37 | PyObject * 38 | call_super_pyname_lookup(PyObject *self, PyObject *func_name, PyObject *args, PyObject *kwargs); 39 | 40 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 41 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 42 | * func_name is a C string. 43 | * The implementation uses the builtin super(). 44 | */ 45 | PyObject * 46 | call_super_name_lookup(PyObject *self, const char *func_cname, PyObject *args, PyObject *kwargs); 47 | 48 | #endif /* #ifndef __UTIL_PY_CALL_SUPER__ */ 49 | -------------------------------------------------------------------------------- /src/cpy/Watchers/DictWatcher.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 30/01/2025. 3 | // 4 | // Explores a dict watcher: https://docs.python.org/3/c-api/dict.html#c.PyDict_AddWatcher 5 | 6 | #ifndef PYTHONEXTENSIONPATTERNS_DICTWATCHER_H 7 | #define PYTHONEXTENSIONPATTERNS_DICTWATCHER_H 8 | 9 | #define PPY_SSIZE_T_CLEAN 10 | 11 | #include "Python.h" 12 | 13 | /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2 14 | * Therefore 0x030C0000 == 3.12.0 15 | */ 16 | #if PY_VERSION_HEX < 0x030C0000 17 | 18 | #error "Required version of Python is 3.12+ (PY_VERSION_HEX >= 0x030C0000)" 19 | 20 | #else 21 | 22 | long get_static_dict_added(void); 23 | long get_static_dict_modified(void); 24 | long get_static_dict_deleted(void); 25 | long get_static_dict_cloned(void); 26 | long get_static_dict_cleared(void); 27 | long get_static_dict_deallocated(void); 28 | 29 | void dbg_PyDict_EVENT_ADDED(void); 30 | void dbg_PyDict_EVENT_MODIFIED(void); 31 | void dbg_PyDict_EVENT_MODIFIED_same_value_no_event(void); 32 | 33 | 34 | int dict_watcher_verbose_add(PyObject *dict); 35 | 36 | int dict_watcher_verbose_remove(int watcher_id, PyObject *dict); 37 | 38 | #endif // #if PY_VERSION_HEX >= 0x030C0000 39 | 40 | #endif //PYTHONEXTENSIONPATTERNS_DICTWATCHER_H 41 | -------------------------------------------------------------------------------- /src/cpy/Watchers/cWatchers.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 31/01/2025. 3 | // 4 | // Provides Python accessible watchers. 5 | 6 | #define PPY_SSIZE_T_CLEAN 7 | 8 | #include "Python.h" 9 | 10 | /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2 11 | * Therefore 0x030C0000 == 3.12.0 12 | */ 13 | #if PY_VERSION_HEX < 0x030C0000 14 | 15 | #error "Required version of Python is 3.12+ (PY_VERSION_HEX >= 0x030C0000)" 16 | 17 | #else 18 | 19 | #pragma mark Dictionary Watcher 20 | 21 | #include "DictWatcher.h" 22 | 23 | static PyObject * 24 | py_dict_watcher_verbose_add(PyObject *Py_UNUSED(module), PyObject *arg) { 25 | if (!PyDict_Check(arg)) { 26 | PyErr_Format(PyExc_TypeError, "Argument must be a dict not type %s", Py_TYPE(arg)->tp_name); 27 | return NULL; 28 | } 29 | long watcher_id = dict_watcher_verbose_add(arg); 30 | return Py_BuildValue("l", watcher_id); 31 | } 32 | 33 | 34 | static PyObject * 35 | py_dict_watcher_verbose_remove(PyObject *Py_UNUSED(module), PyObject *args) { 36 | long watcher_id; 37 | PyObject *dict = NULL; 38 | 39 | if (!PyArg_ParseTuple(args, "lO", &watcher_id, &dict)) { 40 | return NULL; 41 | } 42 | 43 | if (!PyDict_Check(dict)) { 44 | PyErr_Format(PyExc_TypeError, "Argument must be a dict not type %s", Py_TYPE(dict)->tp_name); 45 | return NULL; 46 | } 47 | long result = dict_watcher_verbose_remove(watcher_id, dict); 48 | return Py_BuildValue("l", result); 49 | } 50 | 51 | #pragma mark Dictionary Watcher Context Manager 52 | 53 | typedef struct { 54 | PyObject_HEAD 55 | int watcher_id; 56 | PyObject *dict; 57 | } PyDictWatcher; 58 | 59 | /** Forward declaration. */ 60 | static PyTypeObject PyDictWatcher_Type; 61 | 62 | #define PyDictWatcher_Check(v) (Py_TYPE(v) == &PyDictWatcher_Type) 63 | 64 | static PyDictWatcher * 65 | PyDictWatcher_new(PyObject *Py_UNUSED(arg)) { 66 | PyDictWatcher *self; 67 | self = PyObject_New(PyDictWatcher, &PyDictWatcher_Type); 68 | if (self == NULL) { 69 | return NULL; 70 | } 71 | self->watcher_id = -1; 72 | self->dict = NULL; 73 | return self; 74 | } 75 | 76 | static PyObject * 77 | PyDictWatcher_init(PyDictWatcher *self, PyObject *args) { 78 | if (!PyArg_ParseTuple(args, "O", &self->dict)) { 79 | return NULL; 80 | } 81 | if (!PyDict_Check(self->dict)) { 82 | PyErr_Format(PyExc_TypeError, "Argument must be a dictionary not a %s", Py_TYPE(self->dict)->tp_name); 83 | return NULL; 84 | } 85 | Py_INCREF(self->dict); 86 | return (PyObject *)self; 87 | } 88 | 89 | static void 90 | PyDictWatcher_dealloc(PyDictWatcher *self) { 91 | Py_DECREF(self->dict); 92 | PyObject_Del(self); 93 | } 94 | 95 | static PyObject * 96 | PyDictWatcher_enter(PyDictWatcher *self, PyObject *Py_UNUSED(args)) { 97 | self->watcher_id = dict_watcher_verbose_add(self->dict); 98 | Py_INCREF(self); 99 | return (PyObject *)self; 100 | } 101 | 102 | static PyObject * 103 | PyDictWatcher_exit(PyDictWatcher *self, PyObject *Py_UNUSED(args)) { 104 | int result = dict_watcher_verbose_remove(self->watcher_id, self->dict); 105 | if (result) { 106 | PyErr_Format(PyExc_RuntimeError, "dict_watcher_verbose_remove() returned %d", result); 107 | Py_RETURN_TRUE; 108 | } 109 | Py_RETURN_FALSE; 110 | } 111 | 112 | static PyMethodDef PyDictWatcher_methods[] = { 113 | {"__enter__", (PyCFunction) PyDictWatcher_enter, METH_VARARGS, 114 | PyDoc_STR("__enter__() -> PyDictWatcher")}, 115 | {"__exit__", (PyCFunction) PyDictWatcher_exit, METH_VARARGS, 116 | PyDoc_STR("__exit__(exc_type, exc_value, exc_tb) -> bool")}, 117 | {NULL, NULL, 0, NULL} /* sentinel */ 118 | }; 119 | 120 | static PyTypeObject PyDictWatcher_Type = { 121 | PyVarObject_HEAD_INIT(NULL, 0) 122 | .tp_name = "cWatchers.PyDictWatcher", 123 | .tp_basicsize = sizeof(PyDictWatcher), 124 | .tp_dealloc = (destructor) PyDictWatcher_dealloc, 125 | .tp_flags = Py_TPFLAGS_DEFAULT, 126 | .tp_methods = PyDictWatcher_methods, 127 | .tp_new = (newfunc) PyDictWatcher_new, 128 | .tp_init = (initproc) PyDictWatcher_init 129 | }; 130 | 131 | static PyMethodDef module_methods[] = { 132 | {"py_dict_watcher_verbose_add", 133 | (PyCFunction) py_dict_watcher_verbose_add, 134 | METH_O, 135 | "Adds watcher to a dictionary. Returns the watcher ID." 136 | }, 137 | {"py_dict_watcher_verbose_remove", 138 | (PyCFunction) py_dict_watcher_verbose_remove, 139 | METH_VARARGS, 140 | "Removes the watcher ID from the dictionary." 141 | }, 142 | {NULL, NULL, 0, NULL} /* Sentinel */ 143 | }; 144 | 145 | static PyModuleDef cWatchers = { 146 | PyModuleDef_HEAD_INIT, 147 | .m_name = "cWatchers", 148 | .m_doc = "Dictionary and type watchers.", 149 | .m_size = -1, 150 | .m_methods = module_methods, 151 | }; 152 | 153 | PyMODINIT_FUNC PyInit_cWatchers(void) { 154 | PyObject *m = PyModule_Create(&cWatchers); 155 | if (!m) { 156 | goto fail; 157 | } 158 | if (PyType_Ready(&PyDictWatcher_Type) < 0) { 159 | goto fail; 160 | } 161 | if (PyModule_AddObject(m, "PyDictWatcher", (PyObject *) &PyDictWatcher_Type)) { 162 | goto fail; 163 | } 164 | return m; 165 | fail: 166 | Py_XDECREF(m); 167 | return NULL; 168 | } 169 | 170 | #endif // #if PY_VERSION_HEX >= 0x030C0000 171 | -------------------------------------------------------------------------------- /src/cpy/Watchers/watcher_example.py: -------------------------------------------------------------------------------- 1 | """Example of using watchers.""" 2 | import sys 3 | 4 | from cPyExtPatt import cWatchers 5 | 6 | 7 | def dict_watcher_demo() -> None: 8 | print('dict_watcher_demo():') 9 | d = {} 10 | with cWatchers.PyDictWatcher(d): 11 | dd = {'age': 17, } 12 | d.update(dd) 13 | d['age'] = 42 14 | del d['age'] 15 | d['name'] = 'Python' 16 | d.clear() 17 | del d 18 | 19 | 20 | def dict_watcher_demo_refcount() -> None: 21 | """Checks that the reference count of the dictionary is managed correctly by the context manager.""" 22 | print('dict_watcher_demo_refcount():') 23 | d = {} 24 | print(f'Ref count pre {sys.getrefcount(d)}') 25 | ref_count = sys.getrefcount(d) 26 | # assert ref_count == 1 27 | with cWatchers.PyDictWatcher(d): 28 | d['age'] = 42 29 | print(f'Ref count post {sys.getrefcount(d)}') 30 | assert sys.getrefcount(d) == ref_count 31 | 32 | 33 | def dict_watcher_add() -> None: 34 | print('dict_watcher_add():') 35 | d = {} 36 | with cWatchers.PyDictWatcher(d): 37 | d['age'] = 42 38 | 39 | 40 | def dict_watcher_add_and_replace() -> None: 41 | print('dict_watcher_add_and_replace():') 42 | d = {} 43 | d['age'] = 42 44 | with cWatchers.PyDictWatcher(d): 45 | d['age'] = 43 46 | 47 | 48 | def dict_watcher_add_and_del() -> None: 49 | print('dict_watcher_add_and_del():') 50 | d = {} 51 | d['age'] = 42 52 | with cWatchers.PyDictWatcher(d): 53 | del d['age'] 54 | 55 | 56 | def dict_watcher_add_and_clear() -> None: 57 | print('dict_watcher_add_and_clear():') 58 | d = {} 59 | d['age'] = 42 60 | with cWatchers.PyDictWatcher(d): 61 | d.clear() 62 | 63 | 64 | def dict_watcher_del() -> None: 65 | print('dict_watcher_del():') 66 | d = {} 67 | d['age'] = 42 68 | with cWatchers.PyDictWatcher(d): 69 | del d 70 | 71 | 72 | def dict_watcher_cloned() -> None: 73 | print('dict_watcher_cloned():') 74 | d = {} 75 | with cWatchers.PyDictWatcher(d): 76 | dd = {'age': 42, } 77 | d.update(dd) 78 | 79 | 80 | def dict_watcher_deallocated() -> None: 81 | print('dict_watcher_deallocated():') 82 | d = {'age': 42, } 83 | dd = d 84 | with cWatchers.PyDictWatcher(dd): 85 | del d 86 | del dd 87 | 88 | 89 | def dict_watcher_add_no_context_manager() -> None: 90 | print('dict_watcher_add_no_context_manager():') 91 | d = {} 92 | watcher_id = cWatchers.py_dict_watcher_verbose_add(d) 93 | d['age'] = 42 94 | cWatchers.py_dict_watcher_verbose_remove(watcher_id, d) 95 | 96 | 97 | # def temp() -> None: 98 | # d = {} 99 | # cm = cWatchers.PyDictWatcher(d) 100 | # cmm = cm.__enter__(d) 101 | # d['age'] = 42 102 | # d['age'] = 43 103 | # cmm.__exit__() 104 | # 105 | # 106 | # def temp_2() -> None: 107 | # d = {} 108 | # watcher_id = cWatchers.py_dict_watcher_verbose_add(d) 109 | # d['age'] = 22 110 | # d['age'] = 23 111 | # del d['age'] 112 | # cWatchers.py_dict_watcher_verbose_remove(watcher_id, d) 113 | 114 | 115 | def main() -> int: 116 | # temp() 117 | # temp_2() 118 | dict_watcher_demo() 119 | dict_watcher_demo_refcount() 120 | dict_watcher_add() 121 | dict_watcher_add_and_replace() 122 | dict_watcher_add_and_del() 123 | dict_watcher_add_and_clear() 124 | dict_watcher_del() 125 | dict_watcher_cloned() 126 | dict_watcher_deallocated() 127 | dict_watcher_add_no_context_manager() 128 | return 0 129 | 130 | 131 | if __name__ == '__main__': 132 | exit(main()) 133 | -------------------------------------------------------------------------------- /src/cpy/pyextpatt_util.c: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 30/01/2025. 3 | // 4 | 5 | #include "pyextpatt_util.h" 6 | 7 | /* This is used to guarantee that Python is not caching a string value when we want to check the 8 | * reference counts after each string creation. 9 | * */ 10 | static long debug_test_count = 0L; 11 | 12 | PyObject * 13 | new_unique_string(const char *function_name, const char *suffix) { 14 | PyObject *value = NULL; 15 | if (suffix) { 16 | value = PyUnicode_FromFormat("%s-%s-%ld", function_name, suffix, debug_test_count); 17 | } else { 18 | value = PyUnicode_FromFormat("%s-%ld", function_name, debug_test_count); 19 | } 20 | /* To view in the debugger. */ 21 | Py_UCS1 *buffer = PyUnicode_1BYTE_DATA(value); 22 | assert(buffer); 23 | ++debug_test_count; 24 | return value; 25 | } 26 | -------------------------------------------------------------------------------- /src/cpy/pyextpatt_util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 30/01/2025. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONPATTERNS_PYEXTPATT_UTIL_H 6 | #define PYTHONEXTENSIONPATTERNS_PYEXTPATT_UTIL_H 7 | 8 | #define PPY_SSIZE_T_CLEAN 9 | 10 | #include "Python.h" 11 | 12 | PyObject *new_unique_string(const char *function_name, const char *suffix); 13 | 14 | #endif //PYTHONEXTENSIONPATTERNS_PYEXTPATT_UTIL_H 15 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/project.xcworkspace/xcshareddata/PythonSubclassList.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | AD5F5709-D629-495F-8E17-386A41FA129F 9 | IDESourceControlProjectName 10 | PythonSubclassList 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 016E9095344C63B565906E2AD06B924DF62961F7 14 | https://github.com/paulross/PythonExtensionPatterns.git 15 | 16 | IDESourceControlProjectPath 17 | src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 016E9095344C63B565906E2AD06B924DF62961F7 21 | ../../../../../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/paulross/PythonExtensionPatterns.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 016E9095344C63B565906E2AD06B924DF62961F7 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 016E9095344C63B565906E2AD06B924DF62961F7 36 | IDESourceControlWCCName 37 | PythonExtensionPatterns 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/project.xcworkspace/xcuserdata/paulross.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulross/PythonExtensionPatterns/b2b1ba84df99e86ccfda56cde6eeab3d15e71383/src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/project.xcworkspace/xcuserdata/paulross.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/xcuserdata/paulross.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 35 | 36 | 50 | 51 | 52 | 53 | 54 | 56 | 68 | 69 | 83 | 84 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/xcuserdata/paulross.xcuserdatad/xcschemes/PythonSubclassList.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 64 | 65 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList.xcodeproj/xcuserdata/paulross.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PythonSubclassList.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C31CD3891CD6484100863554 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/SubclassList.c: -------------------------------------------------------------------------------- 1 | // 2 | // SubclassList.c 3 | // PythonSubclassList 4 | // 5 | // Created by Paul Ross on 01/05/2016. 6 | // Copyright (c) 2016 Paul Ross. All rights reserved. 7 | // 8 | // Based on: file:///Library/Frameworks/Python.framework/Versions/3.3/Resources/English.lproj/Documentation/extending/newtypes.html#subclassing-other-types 9 | // That describes the 'Shoddy' module and classs. 10 | // This renames that to ScList ('sub-class list')and, instead of the increment 11 | // menber this counts how many times append() is called and uses the super() 12 | // class to call the base class append. 13 | 14 | #include "SubclassList.h" 15 | 16 | #include 17 | #include "structmember.h" 18 | 19 | #include "py_call_super.h" 20 | 21 | typedef struct { 22 | PyListObject list; 23 | int appends; 24 | } ScList; 25 | 26 | static PyObject * 27 | ScList_append(ScList *self, PyObject *args) { 28 | PyObject *result = call_super_name((PyObject *)self, "append", 29 | args, NULL); 30 | if (result) { 31 | self->appends++; 32 | } 33 | return result; 34 | } 35 | 36 | static PyMethodDef ScList_methods[] = { 37 | {"append", (PyCFunction)ScList_append, METH_VARARGS, 38 | PyDoc_STR("Append to the list")}, 39 | {NULL, NULL, 0, NULL}, 40 | }; 41 | 42 | static PyMemberDef ScList_members[] = { 43 | {"appends", T_INT, offsetof(ScList, appends), 0, 44 | "Number of append operations."}, 45 | {NULL, 0, 0, 0, NULL} /* Sentinel */ 46 | }; 47 | 48 | static int 49 | ScList_init(ScList *self, PyObject *args, PyObject *kwds) 50 | { 51 | if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0) { 52 | return -1; 53 | } 54 | self->appends = 0; 55 | return 0; 56 | } 57 | 58 | static PyTypeObject ScListType = { 59 | PyVarObject_HEAD_INIT(NULL, 0) 60 | "ScList.ScList", /* tp_name */ 61 | sizeof(ScList), /* tp_basicsize */ 62 | 0, /* tp_itemsize */ 63 | 0, /* tp_dealloc */ 64 | 0, /* tp_print */ 65 | 0, /* tp_getattr */ 66 | 0, /* tp_setattr */ 67 | 0, /* tp_reserved */ 68 | 0, /* tp_repr */ 69 | 0, /* tp_as_number */ 70 | 0, /* tp_as_sequence */ 71 | 0, /* tp_as_mapping */ 72 | 0, /* tp_hash */ 73 | 0, /* tp_call */ 74 | 0, /* tp_str */ 75 | 0, /* tp_getattro */ 76 | 0, /* tp_setattro */ 77 | 0, /* tp_as_buffer */ 78 | Py_TPFLAGS_DEFAULT | 79 | Py_TPFLAGS_BASETYPE, /* tp_flags */ 80 | 0, /* tp_doc */ 81 | 0, /* tp_traverse */ 82 | 0, /* tp_clear */ 83 | 0, /* tp_richcompare */ 84 | 0, /* tp_weaklistoffset */ 85 | 0, /* tp_iter */ 86 | 0, /* tp_iternext */ 87 | ScList_methods, /* tp_methods */ 88 | ScList_members, /* tp_members */ 89 | 0, /* tp_getset */ 90 | 0, /* tp_base */ 91 | 0, /* tp_dict */ 92 | 0, /* tp_descr_get */ 93 | 0, /* tp_descr_set */ 94 | 0, /* tp_dictoffset */ 95 | (initproc)ScList_init, /* tp_init */ 96 | 0, /* tp_alloc */ 97 | 0, /* tp_new */ 98 | /* To suppress -Wmissing-field-initializers */ 99 | 0, /* tp_free */ 100 | 0, /* tp_is_gc */ 101 | 0, /* tp_bases */ 102 | 0, /* tp_mro */ 103 | 0, /* tp_cache */ 104 | 0, /* tp_subclasses */ 105 | 0, /* tp_weaklist */ 106 | 0, /* tp_del */ 107 | 0, /* tp_version_tag */ 108 | NULL, /* tp_finalize */ 109 | NULL, /* tp_vectorcall */ 110 | }; 111 | 112 | static PyModuleDef ScListmodule = { 113 | PyModuleDef_HEAD_INIT, 114 | "ScList", 115 | "ScList module", 116 | -1, 117 | NULL, NULL, NULL, NULL, NULL 118 | }; 119 | 120 | PyMODINIT_FUNC 121 | PyInit_ScList(void) 122 | { 123 | PyObject *m; 124 | 125 | ScListType.tp_base = &PyList_Type; 126 | if (PyType_Ready(&ScListType) < 0) 127 | return NULL; 128 | 129 | m = PyModule_Create(&ScListmodule); 130 | if (m == NULL) 131 | return NULL; 132 | 133 | Py_INCREF(&ScListType); 134 | PyModule_AddObject(m, "ScList", (PyObject *) &ScListType); 135 | return m; 136 | } 137 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/SubclassList.h: -------------------------------------------------------------------------------- 1 | // 2 | // SubclassList.h 3 | // PythonSubclassList 4 | // 5 | // Created by Paul Ross on 01/05/2016. 6 | // Copyright (c) 2016 Paul Ross. All rights reserved. 7 | // 8 | 9 | #ifndef __PythonSubclassList__SubclassList__ 10 | #define __PythonSubclassList__SubclassList__ 11 | 12 | #include 13 | 14 | #endif /* defined(__PythonSubclassList__SubclassList__) */ 15 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // PythonSubclassList 4 | // 5 | // Created by Paul Ross on 01/05/2016. 6 | // Copyright (c) 2016 Paul Ross. All rights reserved. 7 | // 8 | 9 | #include "py_import_call_execute.h" 10 | 11 | int main_(int argc, const char *argv[]) { 12 | return import_call_execute(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/py_call_super.h: -------------------------------------------------------------------------------- 1 | // 2 | // py_call_super.h 3 | // PythonSubclassList 4 | // 5 | // Provides C functions to call the Python super() class. 6 | // 7 | // Created by Paul Ross on 03/05/2016. 8 | // Copyright (c) 2016 Paul Ross. All rights reserved. 9 | // 10 | 11 | #ifndef __PythonSubclassList__py_call_super__ 12 | #define __PythonSubclassList__py_call_super__ 13 | 14 | #include 15 | 16 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 17 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 18 | * func_name is a Python string. 19 | * The implementation creates a new super object on each call. 20 | */ 21 | extern PyObject * 22 | call_super_pyname(PyObject *self, PyObject *func_name, PyObject *args, PyObject *kwargs); 23 | 24 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 25 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 26 | * func_name is a C string. 27 | * The implementation creates a new super object on each call. 28 | */ 29 | extern PyObject * 30 | call_super_name(PyObject *self, const char *func_cname, PyObject *args, PyObject *kwargs); 31 | 32 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 33 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 34 | * func_name is a Python string. 35 | * The implementation uses the builtin super(). 36 | */ 37 | extern PyObject * 38 | call_super_pyname_lookup(PyObject *self, PyObject *func_name, PyObject *args, PyObject *kwargs); 39 | 40 | /* Call func_name on the super classes of self with the arguments and keyword arguments. 41 | * Equivalent to getattr(super(type(self), self), func_name)(*args, **kwargs) 42 | * func_name is a C string. 43 | * The implementation uses the builtin super(). 44 | */ 45 | extern PyObject * 46 | call_super_name_lookup(PyObject *self, const char *func_cname, PyObject *args, PyObject *kwargs); 47 | 48 | 49 | #endif /* defined(__PythonSubclassList__py_call_super__) */ 50 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/py_import_call_execute.c: -------------------------------------------------------------------------------- 1 | // 2 | // py_import_call_execute.c 3 | // PythonSubclassList 4 | // 5 | // Created by Paul Ross on 10/06/2016. 6 | // Copyright (c) 2016 Paul Ross. All rights reserved. 7 | // 8 | 9 | #include "py_import_call_execute.h" 10 | 11 | #include 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | /** Takes a path and adds it to sys.paths by calling PyRun_SimpleString. 18 | * This does rather laborious C string concatenation so that it will work in 19 | * a primitive C environment. 20 | * 21 | * Returns 0 on success, non-zero on failure. 22 | */ 23 | int add_path_to_sys_module(const char *path) { 24 | int ret = 0; 25 | const char *prefix = "import sys\nsys.path.append(\""; 26 | const char *suffix = "\")\n"; 27 | char *command = (char*)malloc(strlen(prefix) 28 | + strlen(path) 29 | + strlen(suffix) 30 | + 1); 31 | if (! command) { 32 | return -1; 33 | } 34 | strcpy(command, prefix); 35 | strcat(command, path); 36 | strcat(command, suffix); 37 | ret = PyRun_SimpleString(command); 38 | #ifdef DEBUG 39 | printf("Calling PyRun_SimpleString() with:\n"); 40 | printf("%s", command); 41 | printf("PyRun_SimpleString() returned: %d\n", ret); 42 | fflush(stdout); 43 | #endif 44 | free(command); 45 | return ret; 46 | } 47 | 48 | /** This imports a Python module and calls a specific function in it. 49 | * It's arguments are similar to main(): 50 | * argc - Number of strings in argv 51 | * argv - Expected to be 4 strings: 52 | * - Name of the executable. 53 | * - Path to the directory that the Python module is in. 54 | * - Name of the Python module. 55 | * - Name of the function in the module. 56 | * 57 | * The Python interpreter will be initialised and the path to the Python module 58 | * will be added to sys.paths then the module will be imported. 59 | * The function will be called with no arguments and its return value will be 60 | * ignored. 61 | * 62 | * This returns 0 on success, non-zero on failure. 63 | */ 64 | int import_call_execute(int argc, const char *argv[]) { 65 | int return_value = 0; 66 | PyObject *pModule = NULL; 67 | PyObject *pFunc = NULL; 68 | PyObject *pResult = NULL; 69 | 70 | if (argc != 4) { 71 | fprintf(stderr, 72 | "Wrong arguments!" 73 | " Usage: %s package_path module function\n", argv[0]); 74 | return_value = -1; 75 | goto except; 76 | } 77 | Py_SetProgramName((wchar_t*)argv[0]); 78 | Py_Initialize(); 79 | if (add_path_to_sys_module(argv[1])) { 80 | return_value = -2; 81 | goto except; 82 | } 83 | pModule = PyImport_ImportModule(argv[2]); 84 | if (! pModule) { 85 | fprintf(stderr, 86 | "%s: Failed to load module \"%s\"\n", argv[0], argv[2]); 87 | return_value = -3; 88 | goto except; 89 | } 90 | pFunc = PyObject_GetAttrString(pModule, argv[3]); 91 | if (! pFunc) { 92 | fprintf(stderr, 93 | "%s: Can not find function \"%s\"\n", argv[0], argv[3]); 94 | return_value = -4; 95 | goto except; 96 | } 97 | if (! PyCallable_Check(pFunc)) { 98 | fprintf(stderr, 99 | "%s: Function \"%s\" is not callable\n", argv[0], argv[3]); 100 | return_value = -5; 101 | goto except; 102 | } 103 | pResult = PyObject_CallObject(pFunc, NULL); 104 | if (! pResult) { 105 | fprintf(stderr, "%s: Function call failed\n", argv[0]); 106 | return_value = -6; 107 | goto except; 108 | } 109 | #ifdef DEBUG 110 | printf("%s: PyObject_CallObject() succeeded\n", argv[0]); 111 | #endif 112 | assert(! PyErr_Occurred()); 113 | goto finally; 114 | except: 115 | assert(PyErr_Occurred()); 116 | PyErr_Print(); 117 | finally: 118 | Py_XDECREF(pFunc); 119 | Py_XDECREF(pModule); 120 | Py_XDECREF(pResult); 121 | Py_Finalize(); 122 | return return_value; 123 | } 124 | 125 | #ifdef __cplusplus 126 | // extern "C" { 127 | #endif 128 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/py_import_call_execute.h: -------------------------------------------------------------------------------- 1 | // 2 | // py_import_call_execute.h 3 | // PythonSubclassList 4 | // 5 | // Created by Paul Ross on 10/06/2016. 6 | // Copyright (c) 2016 Paul Ross. All rights reserved. 7 | // 8 | 9 | #ifndef __PythonSubclassList__py_import_call_execute__ 10 | #define __PythonSubclassList__py_import_call_execute__ 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /** This imports a Python module and calls a specific function in it. 17 | * It's arguments are similar to main(): 18 | * argc - Number of strings in argv 19 | * argv - Expected to be 4 strings: 20 | * - Name of the executable. 21 | * - Path to the directory that the Python module is in. 22 | * - Name of the Python module. 23 | * - Name of the function in the module. 24 | * 25 | * The Python interpreter will be initialised and the path to the Python module 26 | * will be added to sys.paths then the module will be imported. 27 | * The function will be called with no arguments and its return value will be 28 | * ignored. 29 | * 30 | * This returns 0 on success, non-zero on failure. 31 | */ 32 | int import_call_execute(int argc, const char *argv[]); 33 | 34 | 35 | #ifdef __cplusplus 36 | } // extern "C" 37 | #endif 38 | 39 | #endif /* defined(__PythonSubclassList__py_import_call_execute__) */ 40 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/setup.py: -------------------------------------------------------------------------------- 1 | """ Usage: 2 | python3 setup.py build 3 | 4 | Created on May 30, 2013 5 | 6 | @author: paulross 7 | """ 8 | import os 9 | 10 | DEBUG = True 11 | 12 | extra_compile_args=["-std=c99", "-Wall", "-Wextra"] 13 | if DEBUG: 14 | extra_compile_args += ["-g3", "-O0", "-DDEBUG=1",] 15 | 16 | from distutils.core import setup, Extension 17 | setup( 18 | name = 'Extending classes', 19 | version = '0.1.0', 20 | author = 'Paul Ross', 21 | author_email = 'cpipdev@gmail.com', 22 | maintainer = 'Paul Ross', 23 | maintainer_email = 'cpipdev@gmail.com', 24 | description = 'Example of extendig a class', 25 | long_description = """""", 26 | platforms = ['Mac OSX', 'POSIX',], 27 | classifiers = [ 28 | 'Development Status :: 3 - Alpha', 29 | 'Environment :: Console', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)', 32 | 'Operating System :: MacOS :: MacOS X', 33 | 'Operating System :: POSIX :: Linux', 34 | 'Programming Language :: C', 35 | 'Programming Language :: Python', 36 | 'Topic :: Scientific/Engineering :: Data Processing', 37 | ], 38 | license = 'GNU General Public License v2 (GPLv2)', 39 | ext_modules=[ 40 | Extension( 41 | "ScList", 42 | sources=[ 43 | 'SubclassList.c', 44 | 'py_call_super.c', 45 | ], 46 | include_dirs = ['.', '/usr/local/include',], 47 | library_dirs = [os.getcwd(),], 48 | #libraries = ['jpeg',], 49 | extra_compile_args=extra_compile_args, 50 | ), 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /src/debugging/XcodeExample/PythonSubclassList/PythonSubclassList/test_sclist.py: -------------------------------------------------------------------------------- 1 | """ Usage: 2 | python3 setup.py build 3 | 4 | Created on Apr 19, 2016 5 | 6 | @author: paulross 7 | """ 8 | import ScList 9 | 10 | def test(): 11 | s = ScList.ScList() 12 | s.append(8) 13 | print(s.appends) 14 | print(s) 15 | -------------------------------------------------------------------------------- /src/memleak.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on 26 May 2015 3 | 4 | @author: paulross 5 | 6 | Deliberately create a 1GB memory leak. 7 | """ 8 | from cPyExtPatt import cPyRefs 9 | 10 | s = ' ' * 1024**3 11 | cPyRefs.incref(s) 12 | del s 13 | -------------------------------------------------------------------------------- /src/pidmon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Created on 5 May 2015 3 | 4 | @author: paulross 5 | 6 | A simple memory monitoring tool. 7 | """ 8 | import sys 9 | import time 10 | 11 | import psutil 12 | 13 | 14 | def memory_monitor(process_id: int, frequency: float = 1.0) -> None: 15 | """Print out memory usage of a process at regular intervals.""" 16 | proc = psutil.Process(process_id) 17 | print(proc.memory_info_ex()) 18 | prev_mem = None 19 | while True: 20 | try: 21 | mem = proc.memory_info().rss / 1e6 22 | if prev_mem is None: 23 | print('{:10.3f} [Mb]'.format(mem)) 24 | else: 25 | print('{:10.3f} [Mb] {:+10.3f} [Mb]'.format(mem, mem - prev_mem)) 26 | prev_mem = mem 27 | time.sleep(frequency) 28 | except KeyboardInterrupt: 29 | try: 30 | input(' Pausing memMon, to continue, Ctrl-C to end...') 31 | except KeyboardInterrupt: 32 | print('\n') 33 | return 34 | 35 | 36 | def main() -> int: 37 | if len(sys.argv) < 2: 38 | print('Usage: python pidmon.py ') 39 | return -1 40 | pid = int(sys.argv[1]) 41 | memory_monitor(pid) 42 | return 0 43 | 44 | 45 | if __name__ == '__main__': 46 | exit(main()) 47 | -------------------------------------------------------------------------------- /src/scratch.c: -------------------------------------------------------------------------------- 1 | // 2 | // scratch.c 3 | // PythonExtensionPatterns 4 | // 5 | // Created by Paul Ross on 04/04/2015. 6 | // Copyright (c) 2015 Paul Ross. All rights reserved. 7 | // 8 | // Just a scratch area for formatting code that goes into the documentation. 9 | // This is not code that is built. 10 | 11 | #include "Python.h" 12 | 13 | #include 14 | #include 15 | 16 | 17 | void leak(void) { 18 | char *p; 19 | 20 | p = malloc(1024); 21 | fprintf(stdout, "malloc(1024) returns %s", p); 22 | } 23 | 24 | 25 | void access_after_free(void) { 26 | char *p; 27 | 28 | p = malloc(1024); 29 | free(p); 30 | 31 | p[8] = 'A'; 32 | printf("%c", p[8]); 33 | } 34 | 35 | 36 | #include "Python.h" 37 | 38 | void py_leak(void) { 39 | PyObject *pObj = NULL; 40 | 41 | /* Object creation, ref count = 1. */ 42 | pObj = PyBytes_FromString("Hello world\n"); 43 | PyObject_Print(pObj, stdout, 0); 44 | /* Object still has ref count = 1. */ 45 | } 46 | 47 | #include "Python.h" 48 | 49 | void py_access_after_free(void) { 50 | PyObject *pObj = NULL; 51 | 52 | /* Object creation, ref count = 1. */ 53 | pObj = PyBytes_FromString("Hello world\n"); 54 | PyObject_Print(pObj, stdout, 0); 55 | /* ref count = 0 so object deallocated. */ 56 | Py_DECREF(pObj); 57 | /* Now use pObj... */ 58 | PyObject_Print(pObj, stdout, 0); 59 | } 60 | 61 | void py_caller_access_after_free(PyObject *pObj) { 62 | /* ... code here ... */ 63 | Py_DECREF(pObj); 64 | /* ... more code here ... */ 65 | } 66 | 67 | 68 | 69 | 70 | PyObject *bad_incref(PyObject *pObj) { 71 | Py_INCREF(pObj); 72 | int error = 0; 73 | /* ... a metric ton of code here ... */ 74 | if (error) { 75 | /* No matching Py_DECREF, pObj is leaked. */ 76 | return NULL; 77 | } 78 | /* ... more code here ... */ 79 | Py_DECREF(pObj); 80 | Py_RETURN_NONE; 81 | } 82 | 83 | 84 | void bad_steal(void) { 85 | 86 | PyObject *v, *r; 87 | 88 | r = PyTuple_New(3); /* New reference. */ 89 | v = PyLong_FromLong(1L); /* New reference. */ 90 | PyTuple_SetItem(r, 0, v); /* r takes ownership of the reference. */ 91 | Py_DECREF(v); /* Now we are interfering with r's internals. */ 92 | 93 | 94 | /* Two common patterns to avoid this, either: */ 95 | v = PyLong_FromLong(1L); /* New reference. */ 96 | PyTuple_SetItem(r, 0, v); /* r takes ownership of the reference. */ 97 | v = NULL; 98 | /* Or: */ 99 | PyTuple_SetItem(r, 0, PyLong_FromLong(1L)); 100 | 101 | } 102 | 103 | 104 | 105 | 106 | static PyObject *pop_and_print_BAD(PyObject *pList) { 107 | PyObject *pLast; 108 | 109 | pLast = PyList_GetItem(pList, PyList_Size(pList) - 1); 110 | fprintf(stdout, "Ref count was: %zd\n", pLast->ob_refcnt); 111 | //do_something(pList); /* Dragons ahoy me hearties! */ 112 | fprintf(stdout, "Ref count now: %zd\n", pLast->ob_refcnt); 113 | PyObject_Print(pLast, stdout, 0); 114 | fprintf(stdout, "\n"); 115 | Py_RETURN_NONE; 116 | } 117 | 118 | 119 | -------------------------------------------------------------------------------- /tests/unit/test_c_cpp.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sys 3 | import zoneinfo 4 | 5 | import psutil 6 | import pytest 7 | 8 | from cPyExtPatt.cpp import placement_new 9 | from cPyExtPatt.cpp import cUnicode 10 | 11 | 12 | # (PythonExtPatt3.11_A) $ PythonExtensionPatterns git:(develop) ✗ python 13 | # Python 3.11.6 (v3.11.6:8b6ee5ba3b, Oct 2 2023, 11:18:21) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin 14 | # Type "help", "copyright", "credits" or "license" for more information. 15 | # >>> from cPyExtPatt.cpp import placement_new 16 | # >>> dir(placement_new) 17 | # ['CppCtorDtorInPyObject', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] 18 | # >>> c = placement_new.CppCtorDtorInPyObject() 19 | # -- CppCtorDtorInPyObject_new() 20 | # Constructor at 0x10afea110 with argument "Default" 21 | # Default constructor at 0x10afea110 with argument "Default" 22 | # Initial self->Attr: Verbose object at 0x10afea110 m_str: "Default" 23 | # Constructor at 0x60000043c060 with argument "pAttr" 24 | # Initial self->pAttr: Verbose object at 0x60000043c060 m_str: "pAttr" 25 | # >>> del c 26 | # -- CppCtorDtorInPyObject_dealloc() 27 | # self->Attr before delete: Verbose object at 0x10afea110 m_str: "Default" 28 | # Destructor at 0x10afea110 m_str: "Default" 29 | # self->pAttr before delete: Verbose object at 0x60000043c060 m_str: "pAttr" 30 | # Destructor at 0x60000043c060 m_str: "pAttr" 31 | 32 | def test_placement_new(): 33 | obj = placement_new.CppCtorDtorInPyObject() 34 | del (obj) 35 | 36 | 37 | @pytest.mark.parametrize( 38 | 'count', 39 | (1, 4, 8,) 40 | ) 41 | def test_placement_new_memory(count): 42 | """Tests repeated construction and destruction with a del call. 43 | TODO: This is a flakey test measuring memory like this. 44 | """ 45 | proc = psutil.Process() 46 | print() 47 | rss_start = proc.memory_info().rss 48 | print(f'RSS start: {rss_start:,d}') 49 | # Python 3.10: 65_044_684 < 10_485_760 on occasion. 50 | # Python 3.10: 280_023_244 < 10_485_760 on occasion. 51 | rss_margin = 300 * 1024 * 1024 52 | for i in range(count): 53 | obj = placement_new.CppCtorDtorInPyObject() 54 | buffer_size = obj.buffer_size() 55 | print(f'Buffer size: {buffer_size:,d}') 56 | rss = proc.memory_info().rss 57 | print(f' RSS new: {rss:,d} {rss - rss_start:+,d}') 58 | assert abs(rss - rss_start - buffer_size) < rss_margin 59 | del (obj) 60 | rss = proc.memory_info().rss 61 | print(f' RSS del: {rss:,d} {rss - rss_start:+,d}') 62 | assert abs(rss - rss_start) < rss_margin 63 | rss = proc.memory_info().rss 64 | print(f' RSS end: {rss:,d} {rss - rss_start:+,d}') 65 | assert abs(rss - rss_start) < rss_margin 66 | 67 | 68 | @pytest.mark.parametrize( 69 | 'count', 70 | (1, 4, 8,) 71 | ) 72 | def test_placement_new_memory_no_del(count): 73 | """Tests repeated construction and destruction with no del call. 74 | Within the loop the results are not really reproducible.""" 75 | proc = psutil.Process() 76 | print() 77 | rss_start = proc.memory_info().rss 78 | print(f'RSS start: {rss_start:,d}') 79 | rss_margin = 3 * 60 * 1024 * 1024 80 | for i in range(count): 81 | obj = placement_new.CppCtorDtorInPyObject() 82 | buffer_size = obj.buffer_size() 83 | print(f'Buffer size: {buffer_size:,d}') 84 | rss = proc.memory_info().rss 85 | print(f' RSS new: {rss:,d} {rss - rss_start:+,d}') 86 | # assert abs(rss - rss_start - buffer_size) < rss_margin 87 | rss = proc.memory_info().rss 88 | print(f' RSS del: {rss:,d} {rss - rss_start:+,d}') 89 | # assert abs(rss - rss_start) < (rss_margin + buffer_size) 90 | rss = proc.memory_info().rss 91 | print(f' RSS end: {rss:,d} {rss - rss_start:+,d}') 92 | assert abs(rss - rss_start) < (rss_margin + buffer_size) 93 | 94 | 95 | @pytest.mark.parametrize( 96 | 'input, expected', 97 | ( 98 | ('String', 'String',), 99 | ("a\xac\u1234\u20ac\U00008000", 'a¬ሴ€耀',), 100 | ("a\xac\u1234\u20ac\U00018000", 'a¬ሴ€𘀀',), 101 | ) 102 | ) 103 | def test_unicode_to_string_and_back(input, expected): 104 | result = cUnicode.unicode_to_string_and_back(input) 105 | assert result == expected 106 | 107 | 108 | @pytest.mark.parametrize( 109 | 'input, expected', 110 | ( 111 | ('String', 'String',), 112 | (b'String', b'String',), 113 | (bytearray('String', 'ascii'), bytearray(b'String'),), 114 | ) 115 | ) 116 | def test_py_object_to_string_and_back(input, expected): 117 | result = cUnicode.py_object_to_string_and_back(input) 118 | assert result == expected 119 | 120 | -------------------------------------------------------------------------------- /tests/unit/test_c_ctxmgr.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import psutil 4 | import pytest 5 | 6 | from cPyExtPatt import cCtxMgr 7 | 8 | 9 | def test_module_dir(): 10 | assert dir(cCtxMgr) == ['BUFFER_LENGTH', 'ContextManager', '__doc__', '__file__', '__loader__', '__name__', 11 | '__package__', '__spec__'] 12 | 13 | 14 | def test_module_BUFFER_LENGTH(): 15 | assert cCtxMgr.BUFFER_LENGTH == 128 * 1024**2 16 | 17 | 18 | def test_very_simple(): 19 | print() 20 | with cCtxMgr.ContextManager(): 21 | pass 22 | 23 | 24 | def test_simple(): 25 | print() 26 | with cCtxMgr.ContextManager() as context: 27 | assert sys.getrefcount(context) == 3 28 | assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH 29 | assert context.len_buffer_context() == cCtxMgr.BUFFER_LENGTH 30 | assert sys.getrefcount(context) == 2 31 | assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH 32 | assert context.len_buffer_context() == 0 33 | del context 34 | 35 | 36 | def test_memory(): 37 | proc = psutil.Process() 38 | print() 39 | print(f'RSS START: {proc.memory_info().rss:12,d}') 40 | for i in range(8): 41 | print(f'RSS START {i:5d}: {proc.memory_info().rss:12,d}') 42 | with cCtxMgr.ContextManager() as context: 43 | print(f'RSS START CTX: {proc.memory_info().rss:12,d}') 44 | # Does not work in the debugger due to introspection. 45 | # assert sys.getrefcount(context) == 3 46 | assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH 47 | assert context.len_buffer_context() == cCtxMgr.BUFFER_LENGTH 48 | print(f'RSS END CTX: {proc.memory_info().rss:12,d}') 49 | # Does not work in the debugger due to introspection. 50 | # assert sys.getrefcount(context) == 2 51 | assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH 52 | assert context.len_buffer_context() == 0 53 | del context 54 | print(f'RSS END {i:5d}: {proc.memory_info().rss:12,d}') 55 | print(f'RSS END: {proc.memory_info().rss:12,d}') 56 | # assert 0 57 | -------------------------------------------------------------------------------- /tests/unit/test_c_custom_pickle.py: -------------------------------------------------------------------------------- 1 | import io 2 | import pickle 3 | import pickletools 4 | import sys 5 | 6 | import pytest 7 | 8 | from cPyExtPatt import cPickle 9 | 10 | 11 | def test_module_dir(): 12 | assert dir(cPickle) == ['Custom', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__'] 13 | 14 | 15 | ARGS_FOR_CUSTOM_CLASS = ('FIRST', 'LAST', 11) 16 | PICKLE_BYTES_FOR_CUSTOM_CLASS = (b'\x80\x04\x95f\x00\x00\x00\x00\x00\x00\x00\x8c\x12cPyExtPatt.cPickle\x94' 17 | b'\x8c\x06Custom\x94\x93\x94)\x81\x94}\x94(\x8c\x05first\x94\x8c\x05FIRST' 18 | b'\x94\x8c\x04last\x94\x8c\x04LAST\x94\x8c\x06number\x94K\x0b\x8c\x0f_pickle_' 19 | b'version\x94K\x01ub.') 20 | 21 | 22 | def test_pickle_getstate(): 23 | custom = cPickle.Custom(*ARGS_FOR_CUSTOM_CLASS) 24 | pickled_value = pickle.dumps(custom) 25 | print() 26 | print(f'Pickled original is {pickled_value}') 27 | assert pickled_value == PICKLE_BYTES_FOR_CUSTOM_CLASS 28 | # result = pickle.loads(pickled_value) 29 | 30 | 31 | def test_pickle_setstate(): 32 | custom = pickle.loads(PICKLE_BYTES_FOR_CUSTOM_CLASS) 33 | assert custom.first == 'FIRST' 34 | assert custom.last == 'LAST' 35 | assert custom.number == 11 36 | 37 | 38 | def test_pickle_round_trip(): 39 | custom = cPickle.Custom(*ARGS_FOR_CUSTOM_CLASS) 40 | pickled_value = pickle.dumps(custom) 41 | result = pickle.loads(pickled_value) 42 | assert id(result) != id(custom) 43 | 44 | 45 | def test_pickletools(): 46 | outfile = io.StringIO() 47 | pickletools.dis(PICKLE_BYTES_FOR_CUSTOM_CLASS, out=outfile, annotate=1) 48 | result = outfile.getvalue() 49 | # print() 50 | # print(result) 51 | expected = """ 0: \\x80 PROTO 4 Protocol version indicator. 52 | 2: \\x95 FRAME 102 Indicate the beginning of a new frame. 53 | 11: \\x8c SHORT_BINUNICODE 'cPyExtPatt.cPickle' Push a Python Unicode string object. 54 | 31: \\x94 MEMOIZE (as 0) Store the stack top into the memo. The stack is not popped. 55 | 32: \\x8c SHORT_BINUNICODE 'Custom' Push a Python Unicode string object. 56 | 40: \\x94 MEMOIZE (as 1) Store the stack top into the memo. The stack is not popped. 57 | 41: \\x93 STACK_GLOBAL Push a global object (module.attr) on the stack. 58 | 42: \\x94 MEMOIZE (as 2) Store the stack top into the memo. The stack is not popped. 59 | 43: ) EMPTY_TUPLE Push an empty tuple. 60 | 44: \\x81 NEWOBJ Build an object instance. 61 | 45: \\x94 MEMOIZE (as 3) Store the stack top into the memo. The stack is not popped. 62 | 46: } EMPTY_DICT Push an empty dict. 63 | 47: \\x94 MEMOIZE (as 4) Store the stack top into the memo. The stack is not popped. 64 | 48: ( MARK Push markobject onto the stack. 65 | 49: \\x8c SHORT_BINUNICODE 'first' Push a Python Unicode string object. 66 | 56: \\x94 MEMOIZE (as 5) Store the stack top into the memo. The stack is not popped. 67 | 57: \\x8c SHORT_BINUNICODE 'FIRST' Push a Python Unicode string object. 68 | 64: \\x94 MEMOIZE (as 6) Store the stack top into the memo. The stack is not popped. 69 | 65: \\x8c SHORT_BINUNICODE 'last' Push a Python Unicode string object. 70 | 71: \\x94 MEMOIZE (as 7) Store the stack top into the memo. The stack is not popped. 71 | 72: \\x8c SHORT_BINUNICODE 'LAST' Push a Python Unicode string object. 72 | 78: \\x94 MEMOIZE (as 8) Store the stack top into the memo. The stack is not popped. 73 | 79: \\x8c SHORT_BINUNICODE 'number' Push a Python Unicode string object. 74 | 87: \\x94 MEMOIZE (as 9) Store the stack top into the memo. The stack is not popped. 75 | 88: K BININT1 11 Push a one-byte unsigned integer. 76 | 90: \\x8c SHORT_BINUNICODE '_pickle_version' Push a Python Unicode string object. 77 | 107: \\x94 MEMOIZE (as 10) Store the stack top into the memo. The stack is not popped. 78 | 108: K BININT1 1 Push a one-byte unsigned integer. 79 | 110: u SETITEMS (MARK at 48) Add an arbitrary number of key+value pairs to an existing dict. 80 | 111: b BUILD Finish building an object, via __setstate__ or dict update. 81 | 112: . STOP Stop the unpickling machine. 82 | highest protocol among opcodes = 4 83 | """ 84 | assert result == expected 85 | -------------------------------------------------------------------------------- /tests/unit/test_c_exceptions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | from cPyExtPatt import cExceptions 6 | 7 | 8 | def test_raise_error(): 9 | with pytest.raises(ValueError) as err: 10 | cExceptions.raise_error() 11 | assert err.value.args[0] == 'Ooops.' 12 | 13 | 14 | @pytest.mark.skipif(sys.version_info.minor > 9, reason='Python <= 3.9') 15 | def test_raise_error_bad_old(): 16 | with pytest.raises(SystemError) as err: 17 | cExceptions.raise_error_bad() 18 | assert err.value.args[0] == ' returned NULL without setting an error' 19 | 20 | 21 | @pytest.mark.skipif(sys.version_info.minor <= 9, reason='Python > 3.9') 22 | def test_raise_error_bad(): 23 | with pytest.raises(SystemError) as err: 24 | cExceptions.raise_error_bad() 25 | assert err.value.args[0] == ' returned NULL without setting an exception' 26 | 27 | 28 | def test_raise_error_fmt(): 29 | with pytest.raises(ValueError) as err: 30 | cExceptions.raise_error_fmt() 31 | assert err.value.args[0] == 'Can not read 12 bytes when offset 25 in byte length 32.' 32 | 33 | 34 | def test_raise_error_overwrite(): 35 | with pytest.raises(ValueError) as err: 36 | cExceptions.raise_error_overwrite() 37 | assert err.value.args[0] == 'ERROR: raise_error_overwrite()' 38 | 39 | 40 | @pytest.mark.skipif(sys.version_info.minor > 9, reason='Python <= 3.9') 41 | def test_raise_error_silent_old(): 42 | with pytest.raises(SystemError) as err: 43 | cExceptions.raise_error_silent() 44 | assert err.value.args[0] == ' returned a result with an error set' 45 | 46 | 47 | @pytest.mark.skipif(sys.version_info.minor <= 9, reason='Python > 3.9') 48 | def test_raise_error_silent(): 49 | with pytest.raises(SystemError) as err: 50 | cExceptions.raise_error_silent() 51 | assert err.value.args[0] == ' returned a result with an exception set' 52 | 53 | 54 | def test_raise_error_silent_test(): 55 | cExceptions.raise_error_silent_test() 56 | 57 | 58 | def test_ExceptionBase_exists(): 59 | exception = cExceptions.ExceptionBase('FOO') 60 | assert exception.args[0] == 'FOO' 61 | assert str(exception.__class__.__mro__) == ( 62 | "(" 63 | "," 64 | " ," 65 | " ," 66 | " " 67 | ")" 68 | ) 69 | 70 | 71 | def test_SpecialsiedError_exists(): 72 | exception = cExceptions.SpecialisedError('FOO') 73 | assert exception.args[0] == 'FOO' 74 | assert str(exception.__class__.__mro__) == ( 75 | "(" 76 | "," 77 | " ," 78 | " ," 79 | " ," 80 | " " 81 | ")" 82 | ) 83 | 84 | 85 | def test_raise_exception_base(): 86 | with pytest.raises(cExceptions.ExceptionBase) as err: 87 | cExceptions.raise_exception_base() 88 | assert err.value.args[0] == 'One 1 two 2 three 3.' 89 | 90 | 91 | def test_raise_specialised_error(): 92 | with pytest.raises(cExceptions.SpecialisedError) as err: 93 | cExceptions.raise_specialised_error() 94 | assert err.value.args[0] == 'One 1 two 2 three 3.' 95 | -------------------------------------------------------------------------------- /tests/unit/test_c_file.py: -------------------------------------------------------------------------------- 1 | import io 2 | import sys 3 | import pathlib 4 | import typing 5 | 6 | import pytest 7 | 8 | from cPyExtPatt import cFile 9 | 10 | 11 | @pytest.mark.parametrize( 12 | 'arg, expected', 13 | ( 14 | ('~/foo/bar.txt', '~/foo/bar.txt',), 15 | (pathlib.Path('~/foo/bar.txt'), '~/foo/bar.txt',), 16 | ) 17 | ) 18 | def test_parse_filesystem_argument(arg, expected): 19 | assert cFile.parse_filesystem_argument(arg) == expected 20 | 21 | 22 | @pytest.mark.parametrize( 23 | 'arg, expected', 24 | ( 25 | ('~/foo/bar.txt', str,), 26 | ) 27 | ) 28 | def test_parse_filesystem_argument_return_type(arg, expected): 29 | assert type(cFile.parse_filesystem_argument(arg)) == expected 30 | 31 | 32 | @pytest.mark.skipif(not (sys.version_info.minor >= 7), reason='Python 3.7+') 33 | @pytest.mark.parametrize( 34 | 'arg, expected', 35 | ( 36 | # Number of arguments. 37 | (None, "function missing required argument 'path' (pos 1)"), 38 | ([1, 2.9], 'expected str, bytes or os.PathLike object, not list'), 39 | ) 40 | ) 41 | def test_parse_filesystem_argument_raises(arg, expected): 42 | with pytest.raises(TypeError) as err: 43 | if arg is None: 44 | cFile.parse_filesystem_argument() 45 | else: 46 | cFile.parse_filesystem_argument(arg) 47 | assert err.value.args[0] == expected 48 | 49 | 50 | @pytest.mark.skipif(not (sys.version_info.minor < 7), reason='Python < 3.7') 51 | @pytest.mark.parametrize( 52 | 'arg, expected', 53 | ( 54 | # Number of arguments. 55 | (None, "Required argument 'path' (pos 1) not found"), 56 | ([1, 2.9], 'expected str, bytes or os.PathLike object, not list'), 57 | ) 58 | ) 59 | def test_parse_filesystem_argument_raises_pre_37(arg, expected): 60 | with pytest.raises(TypeError) as err: 61 | if arg is None: 62 | cFile.parse_filesystem_argument() 63 | else: 64 | cFile.parse_filesystem_argument(arg) 65 | assert err.value.args[0] == expected 66 | 67 | 68 | @pytest.mark.parametrize( 69 | 'file_object, size, expected', 70 | ( 71 | (io.BytesIO(b'Some bytes.'), 4, b'Some'), 72 | (io.BytesIO(b'Some bytes.'), None, b'Some bytes.'), 73 | ) 74 | ) 75 | def test_read_python_file_to_c(file_object, size, expected): 76 | if size is None: 77 | result = cFile.read_python_file_to_c(file_object) 78 | else: 79 | result = cFile.read_python_file_to_c(file_object, size) 80 | assert result == expected 81 | 82 | 83 | @pytest.mark.parametrize( 84 | 'bytes_to_write, expected', 85 | ( 86 | (b'Some bytes.', len(b'Some bytes.')), 87 | (b'Some\0bytes.', len(b'Some\0bytes.')), 88 | ) 89 | ) 90 | def test_write_bytes_to_python_string_file(bytes_to_write, expected): 91 | file = io.StringIO() 92 | result = cFile.write_bytes_to_python_file(bytes_to_write, file) 93 | assert result == expected 94 | 95 | 96 | @pytest.mark.parametrize( 97 | 'bytes_to_write, expected', 98 | ( 99 | ('Some string.', "a bytes-like object is required, not 'str'"), 100 | ) 101 | ) 102 | def test_write_bytes_to_python_string_file_raises(bytes_to_write, expected): 103 | file = io.StringIO() 104 | with pytest.raises(TypeError) as err: 105 | cFile.write_bytes_to_python_file(bytes_to_write, file) 106 | assert err.value.args[0] == expected 107 | 108 | 109 | @pytest.mark.parametrize( 110 | 'bytes_to_write, expected', 111 | ( 112 | ('Some bytes.', "a bytes-like object is required, not 'str'"), 113 | (b'Some bytes.', "a bytes-like object is required, not 'str'"), 114 | ) 115 | ) 116 | def test_write_bytes_to_python_bytes_file_raises(bytes_to_write, expected): 117 | file = io.BytesIO() 118 | with pytest.raises(TypeError) as err: 119 | cFile.write_bytes_to_python_file(bytes_to_write, file) 120 | assert err.value.args[0] == expected 121 | 122 | # TODO: Fix this. Why is position 420 when it is a 25 character write? String termination? 123 | @pytest.mark.skip( 124 | reason=( 125 | "Fails on Python 3.9 and 3.11 with" 126 | " \"UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 420: ordinal not in range(128)\"" 127 | ), 128 | ) 129 | def test_wrap_python_file(): 130 | file = io.BytesIO() 131 | result = cFile.wrap_python_file(file) 132 | print() 133 | print(' Result '.center(75, '-')) 134 | print(result.decode('ascii')) 135 | print(' Result DONE '.center(75, '-')) 136 | print(' file.getvalue() '.center(75, '-')) 137 | get_value = file.getvalue() 138 | print(get_value) 139 | print(' file.getvalue() DONE '.center(75, '-')) 140 | assert get_value == b'Test write to python file' 141 | -------------------------------------------------------------------------------- /tests/unit/test_c_logging.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | import pytest 5 | 6 | from cPyExtPatt.Logging import cLogging 7 | 8 | #: Default log format (terse) 9 | DEFAULT_OPT_LOG_FORMAT = '%(asctime)s %(process)d %(levelname)-8s %(message)s' 10 | #: Default log format (verbose) 11 | DEFAULT_OPT_LOG_FORMAT_VERBOSE = '%(asctime)s - %(filename)-16s#%(lineno)-4d - %(process)5d - (%(threadName)-10s) - %(levelname)-8s - %(message)s' 12 | 13 | logging.basicConfig(level=logging.DEBUG, stream=sys.stderr, format=DEFAULT_OPT_LOG_FORMAT) 14 | 15 | logger = logging.getLogger(__file__) 16 | 17 | 18 | def test_logging(): 19 | logger.setLevel(logging.DEBUG) 20 | logger.warning('Test warning message XXXX') 21 | logger.debug('Test debug message XXXX') 22 | # assert 0 23 | 24 | 25 | def test_c_logging_dir(): 26 | result = dir(cLogging) 27 | assert result == [ 28 | 'CRITICAL', 29 | 'DEBUG', 30 | 'ERROR', 31 | 'EXCEPTION', 32 | 'INFO', 33 | 'WARNING', 34 | '__doc__', 35 | '__file__', 36 | '__loader__', 37 | '__name__', 38 | '__package__', 39 | '__spec__', 40 | 'c_file_line_function', 41 | 'log', 42 | 'py_file_line_function', 43 | 'py_log_set_level', 44 | ] 45 | 46 | 47 | def test_c_logging_log(): 48 | print() 49 | cLogging.py_log_set_level(10) 50 | result = cLogging.log(cLogging.ERROR, "Test log message") 51 | assert result is None 52 | 53 | 54 | def test_c_file_line_function_file(): 55 | file, line, function = cLogging.c_file_line_function() 56 | assert file == 'src/cpy/Logging/cLogging.c' 57 | assert line == 143 58 | assert function == 'c_file_line_function' 59 | 60 | 61 | def test_py_file_line_function_file(): 62 | file, _line, _function = cLogging.py_file_line_function() 63 | assert file == __file__ 64 | 65 | 66 | def test_py_file_line_function_line(): 67 | _file, line, _function = cLogging.py_file_line_function() 68 | assert line == 67 69 | 70 | 71 | def test_py_file_line_function_function(): 72 | _file, _line, function = cLogging.py_file_line_function() 73 | assert function == 'test_py_file_line_function_function' 74 | 75 | 76 | def main(): 77 | logger.setLevel(logging.DEBUG) 78 | logger.info('main') 79 | logger.warning('Test warning message XXXX') 80 | logger.debug('Test debug message XXXX') 81 | logger.info('_test_logging') 82 | test_logging() 83 | print() 84 | print(cLogging) 85 | print(dir(cLogging)) 86 | print() 87 | logger.info('cLogging.log():') 88 | cLogging.py_log_set_level(10) 89 | cLogging.log(cLogging.ERROR, "cLogging.log(): Test log message") 90 | 91 | return 0 92 | 93 | 94 | if __name__ == '__main__': 95 | exit(main()) 96 | -------------------------------------------------------------------------------- /tests/unit/test_c_module_globals.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cPyExtPatt import cModuleGlobals 4 | 5 | 6 | def test_int(): 7 | assert cModuleGlobals.INT == 42 8 | 9 | 10 | def test_str(): 11 | assert cModuleGlobals.STR == 'String value' 12 | 13 | 14 | def test_list(): 15 | assert cModuleGlobals.LST == [66, 68, 73,] 16 | 17 | 18 | def test_list_alter(): 19 | assert cModuleGlobals.LST == [66, 68, 73,] 20 | cModuleGlobals.LST.append(100) 21 | assert cModuleGlobals.LST == [66, 68, 73, 100,] 22 | assert cModuleGlobals.LST.pop(-1) == 100 23 | assert cModuleGlobals.LST == [66, 68, 73,] 24 | 25 | 26 | def test_tuple(): 27 | assert cModuleGlobals.TUP == (66, 68, 73,) 28 | 29 | 30 | def test_map(): 31 | assert cModuleGlobals.MAP == {b'123': 123, b'66': 66} 32 | 33 | 34 | def test_print(): 35 | cModuleGlobals.print() 36 | # assert 0 37 | -------------------------------------------------------------------------------- /tests/unit/test_c_py_refs.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cPyExtPatt import cPyRefs 4 | 5 | 6 | def test_module_dir(): 7 | assert dir(cPyRefs) == ['__doc__', 8 | '__file__', 9 | '__loader__', 10 | '__name__', 11 | '__package__', 12 | '__spec__', 13 | 'access_after_free', 14 | 'dec_ref', 15 | 'inc_ref', 16 | 'leak_new_reference', 17 | 'make_tuple', 18 | 'pop_and_print_BAD', 19 | 'pop_and_print_OK', 20 | 'ref_count', 21 | 'subtract_two_longs', 22 | ] 23 | 24 | 25 | def test_ref_count(): 26 | s = ''.join(dir(cPyRefs)) 27 | assert cPyRefs.ref_count(s) == 2 28 | 29 | 30 | def test_ref_count_inc(): 31 | s = ''.join(dir(cPyRefs)) 32 | original_refcount = cPyRefs.ref_count(s) 33 | assert original_refcount == 2 34 | assert cPyRefs.inc_ref(s) == original_refcount 35 | assert cPyRefs.ref_count(s) == original_refcount + 1 36 | assert cPyRefs.dec_ref(s) == original_refcount + 1 37 | assert cPyRefs.ref_count(s) == original_refcount 38 | 39 | 40 | def test_subtract_two_longs(): 41 | assert cPyRefs.subtract_two_longs() == (421 - 17) 42 | 43 | 44 | def test_make_tuple(): 45 | assert cPyRefs.make_tuple() == (1, 2, 'three') 46 | -------------------------------------------------------------------------------- /tests/unit/test_c_simple_example.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cPyExtPatt.SimpleExample import cFibA 4 | from cPyExtPatt.SimpleExample import cFibB 5 | 6 | 7 | @pytest.mark.parametrize( 8 | 'index, expected', 9 | ( 10 | (1, 1,), 11 | (2, 1,), 12 | (3, 2,), 13 | (8, 21,), 14 | (30, 832040,), 15 | ) 16 | ) 17 | def test_cFibA_fibonacci(index, expected): 18 | result = cFibA.fibonacci(index) 19 | assert result == expected 20 | 21 | 22 | @pytest.mark.parametrize( 23 | 'index, expected', 24 | ( 25 | (1, 1,), 26 | (2, 1,), 27 | (3, 2,), 28 | (8, 21,), 29 | (30, 832040,), 30 | ) 31 | ) 32 | def test_cFibB_fibonacci(index, expected): 33 | result = cFibB.fibonacci(index) 34 | assert result == expected 35 | -------------------------------------------------------------------------------- /tests/unit/test_c_subclass.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sys 3 | import zoneinfo 4 | 5 | import pytest 6 | 7 | from cPyExtPatt.SubClass import sublist 8 | 9 | 10 | 11 | 12 | def test_sublist_dir(): 13 | result = dir(sublist) 14 | assert result == ['SubList', 15 | '__doc__', 16 | '__file__', 17 | '__loader__', 18 | '__name__', 19 | '__package__', 20 | '__spec__'] 21 | 22 | 23 | 24 | @pytest.mark.skipif(not (sys.version_info.minor <= 10), reason='Python 3.9, 3.10') 25 | def test_sublist_sublist_dir_pre_311(): 26 | sublist_object = sublist.SubList() 27 | result = dir(sublist_object) 28 | assert result == ['__add__', 29 | '__class__', 30 | '__class_getitem__', 31 | '__contains__', 32 | '__delattr__', 33 | '__delitem__', 34 | '__dir__', 35 | '__doc__', 36 | '__eq__', 37 | '__format__', 38 | '__ge__', 39 | '__getattribute__', 40 | '__getitem__', 41 | '__gt__', 42 | '__hash__', 43 | '__iadd__', 44 | '__imul__', 45 | '__init__', 46 | '__init_subclass__', 47 | '__iter__', 48 | '__le__', 49 | '__len__', 50 | '__lt__', 51 | '__mul__', 52 | '__ne__', 53 | '__new__', 54 | '__reduce__', 55 | '__reduce_ex__', 56 | '__repr__', 57 | '__reversed__', 58 | '__rmul__', 59 | '__setattr__', 60 | '__setitem__', 61 | '__sizeof__', 62 | '__str__', 63 | '__subclasshook__', 64 | 'append', 65 | 'appends', 66 | 'clear', 67 | 'copy', 68 | 'count', 69 | 'extend', 70 | 'increment', 71 | 'index', 72 | 'insert', 73 | 'pop', 74 | 'remove', 75 | 'reverse', 76 | 'sort', 77 | 'state'] 78 | 79 | 80 | @pytest.mark.skipif(not (sys.version_info.minor > 10), reason='Python 3.11+') 81 | def test_sublist_sublist_dir_post_310(): 82 | sublist_object = sublist.SubList() 83 | result = dir(sublist_object) 84 | assert result == ['__add__', 85 | '__class__', 86 | '__class_getitem__', 87 | '__contains__', 88 | '__delattr__', 89 | '__delitem__', 90 | '__dir__', 91 | '__doc__', 92 | '__eq__', 93 | '__format__', 94 | '__ge__', 95 | '__getattribute__', 96 | '__getitem__', 97 | '__getstate__', 98 | '__gt__', 99 | '__hash__', 100 | '__iadd__', 101 | '__imul__', 102 | '__init__', 103 | '__init_subclass__', 104 | '__iter__', 105 | '__le__', 106 | '__len__', 107 | '__lt__', 108 | '__mul__', 109 | '__ne__', 110 | '__new__', 111 | '__reduce__', 112 | '__reduce_ex__', 113 | '__repr__', 114 | '__reversed__', 115 | '__rmul__', 116 | '__setattr__', 117 | '__setitem__', 118 | '__sizeof__', 119 | '__str__', 120 | '__subclasshook__', 121 | 'append', 122 | 'appends', 123 | 'clear', 124 | 'copy', 125 | 'count', 126 | 'extend', 127 | 'increment', 128 | 'index', 129 | 'insert', 130 | 'pop', 131 | 'remove', 132 | 'reverse', 133 | 'sort', 134 | 'state'] 135 | 136 | 137 | def test_sublist_sublist_append(): 138 | obj = sublist.SubList() 139 | assert obj.appends == 0 140 | obj.append(42) 141 | assert obj.appends == 1 142 | assert obj == [42, ] 143 | 144 | 145 | def test_sublist_sublist_state(): 146 | obj = sublist.SubList() 147 | assert obj.state == 0 148 | obj.increment() 149 | assert obj.state == 1 150 | -------------------------------------------------------------------------------- /type_objects/Python_3.10.1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 28/06/2024. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_10_1_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_10_1_H 7 | 8 | struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | struct PyMethodDef *tp_methods; 65 | struct PyMemberDef *tp_members; 66 | struct PyGetSetDef *tp_getset; 67 | // Strong reference on a heap type, borrowed reference on a static type 68 | struct _typeobject *tp_base; 69 | PyObject *tp_dict; 70 | descrgetfunc tp_descr_get; 71 | descrsetfunc tp_descr_set; 72 | Py_ssize_t tp_dictoffset; 73 | initproc tp_init; 74 | allocfunc tp_alloc; 75 | newfunc tp_new; 76 | freefunc tp_free; /* Low-level free-memory routine */ 77 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 78 | PyObject *tp_bases; 79 | PyObject *tp_mro; /* method resolution order */ 80 | PyObject *tp_cache; 81 | PyObject *tp_subclasses; 82 | PyObject *tp_weaklist; 83 | destructor tp_del; 84 | 85 | /* Type attribute cache version tag. Added in version 2.6 */ 86 | unsigned int tp_version_tag; 87 | 88 | destructor tp_finalize; 89 | vectorcallfunc tp_vectorcall; 90 | }; 91 | 92 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_10_1_H 93 | -------------------------------------------------------------------------------- /type_objects/Python_3.11.1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 28/06/2024. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_11_1_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_11_1_H 7 | 8 | struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | PyMethodDef *tp_methods; 65 | PyMemberDef *tp_members; 66 | PyGetSetDef *tp_getset; 67 | // Strong reference on a heap type, borrowed reference on a static type 68 | PyTypeObject *tp_base; 69 | PyObject *tp_dict; 70 | descrgetfunc tp_descr_get; 71 | descrsetfunc tp_descr_set; 72 | Py_ssize_t tp_dictoffset; 73 | initproc tp_init; 74 | allocfunc tp_alloc; 75 | newfunc tp_new; 76 | freefunc tp_free; /* Low-level free-memory routine */ 77 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 78 | PyObject *tp_bases; 79 | PyObject *tp_mro; /* method resolution order */ 80 | PyObject *tp_cache; 81 | PyObject *tp_subclasses; 82 | PyObject *tp_weaklist; 83 | destructor tp_del; 84 | 85 | /* Type attribute cache version tag. Added in version 2.6 */ 86 | unsigned int tp_version_tag; 87 | 88 | destructor tp_finalize; 89 | vectorcallfunc tp_vectorcall; 90 | }; 91 | 92 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_11_1_H 93 | -------------------------------------------------------------------------------- /type_objects/Python_3.12.1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 28/06/2024. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_12_1_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_12_1_H 7 | 8 | struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | PyMethodDef *tp_methods; 65 | PyMemberDef *tp_members; 66 | PyGetSetDef *tp_getset; 67 | // Strong reference on a heap type, borrowed reference on a static type 68 | PyTypeObject *tp_base; 69 | PyObject *tp_dict; 70 | descrgetfunc tp_descr_get; 71 | descrsetfunc tp_descr_set; 72 | Py_ssize_t tp_dictoffset; 73 | initproc tp_init; 74 | allocfunc tp_alloc; 75 | newfunc tp_new; 76 | freefunc tp_free; /* Low-level free-memory routine */ 77 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 78 | PyObject *tp_bases; 79 | PyObject *tp_mro; /* method resolution order */ 80 | PyObject *tp_cache; /* no longer used */ 81 | void *tp_subclasses; /* for static builtin types this is an index */ 82 | PyObject *tp_weaklist; /* not used for static builtin types */ 83 | destructor tp_del; 84 | 85 | /* Type attribute cache version tag. Added in version 2.6 */ 86 | unsigned int tp_version_tag; 87 | 88 | destructor tp_finalize; 89 | vectorcallfunc tp_vectorcall; 90 | 91 | /* bitset of which type-watchers care about this type */ 92 | unsigned char tp_watched; 93 | }; 94 | 95 | 96 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_12_1_H 97 | -------------------------------------------------------------------------------- /type_objects/Python_3.13.0b3.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 28/06/2024. 3 | // 4 | 5 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_13_0b3_H 6 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_13_0b3_H 7 | 8 | struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | PyMethodDef *tp_methods; 65 | PyMemberDef *tp_members; 66 | PyGetSetDef *tp_getset; 67 | // Strong reference on a heap type, borrowed reference on a static type 68 | PyTypeObject *tp_base; 69 | PyObject *tp_dict; 70 | descrgetfunc tp_descr_get; 71 | descrsetfunc tp_descr_set; 72 | Py_ssize_t tp_dictoffset; 73 | initproc tp_init; 74 | allocfunc tp_alloc; 75 | newfunc tp_new; 76 | freefunc tp_free; /* Low-level free-memory routine */ 77 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 78 | PyObject *tp_bases; 79 | PyObject *tp_mro; /* method resolution order */ 80 | PyObject *tp_cache; /* no longer used */ 81 | void *tp_subclasses; /* for static builtin types this is an index */ 82 | PyObject *tp_weaklist; /* not used for static builtin types */ 83 | destructor tp_del; 84 | 85 | /* Type attribute cache version tag. Added in version 2.6 */ 86 | unsigned int tp_version_tag; 87 | 88 | destructor tp_finalize; 89 | vectorcallfunc tp_vectorcall; 90 | 91 | /* bitset of which type-watchers care about this type */ 92 | unsigned char tp_watched; 93 | uint16_t tp_versions_used; 94 | }; 95 | 96 | 97 | #endif // PYTHONEXTENSIONSBASIC_PYTHON_3_13_0b3_H 98 | -------------------------------------------------------------------------------- /type_objects/Python_3.6.2.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/03/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_6_2_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_6_2_H 7 | 8 | #ifdef Py_LIMITED_API 9 | typedef struct _typeobject PyTypeObject; /* opaque */ 10 | #else 11 | typedef struct _typeobject { 12 | PyObject_VAR_HEAD 13 | const char *tp_name; /* For printing, in format "." */ 14 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 15 | 16 | /* Methods to implement standard operations */ 17 | 18 | destructor tp_dealloc; 19 | printfunc tp_print; 20 | getattrfunc tp_getattr; 21 | setattrfunc tp_setattr; 22 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 23 | or tp_reserved (Python 3) */ 24 | reprfunc tp_repr; 25 | 26 | /* Method suites for standard classes */ 27 | 28 | PyNumberMethods *tp_as_number; 29 | PySequenceMethods *tp_as_sequence; 30 | PyMappingMethods *tp_as_mapping; 31 | 32 | /* More standard operations (here for binary compatibility) */ 33 | 34 | hashfunc tp_hash; 35 | ternaryfunc tp_call; 36 | reprfunc tp_str; 37 | getattrofunc tp_getattro; 38 | setattrofunc tp_setattro; 39 | 40 | /* Functions to access object as input/output buffer */ 41 | PyBufferProcs *tp_as_buffer; 42 | 43 | /* Flags to define presence of optional/expanded features */ 44 | unsigned long tp_flags; 45 | 46 | const char *tp_doc; /* Documentation string */ 47 | 48 | /* Assigned meaning in release 2.0 */ 49 | /* call function for all accessible objects */ 50 | traverseproc tp_traverse; 51 | 52 | /* delete references to contained objects */ 53 | inquiry tp_clear; 54 | 55 | /* Assigned meaning in release 2.1 */ 56 | /* rich comparisons */ 57 | richcmpfunc tp_richcompare; 58 | 59 | /* weak reference enabler */ 60 | Py_ssize_t tp_weaklistoffset; 61 | 62 | /* Iterators */ 63 | getiterfunc tp_iter; 64 | iternextfunc tp_iternext; 65 | 66 | /* Attribute descriptor and subclassing stuff */ 67 | struct PyMethodDef *tp_methods; 68 | struct PyMemberDef *tp_members; 69 | struct PyGetSetDef *tp_getset; 70 | struct _typeobject *tp_base; 71 | PyObject *tp_dict; 72 | descrgetfunc tp_descr_get; 73 | descrsetfunc tp_descr_set; 74 | Py_ssize_t tp_dictoffset; 75 | initproc tp_init; 76 | allocfunc tp_alloc; 77 | newfunc tp_new; 78 | freefunc tp_free; /* Low-level free-memory routine */ 79 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 80 | PyObject *tp_bases; 81 | PyObject *tp_mro; /* method resolution order */ 82 | PyObject *tp_cache; 83 | PyObject *tp_subclasses; 84 | PyObject *tp_weaklist; 85 | destructor tp_del; 86 | 87 | /* Type attribute cache version tag. Added in version 2.6 */ 88 | unsigned int tp_version_tag; 89 | 90 | destructor tp_finalize; 91 | 92 | #ifdef COUNT_ALLOCS 93 | /* these must be last and never explicitly initialized */ 94 | Py_ssize_t tp_allocs; 95 | Py_ssize_t tp_frees; 96 | Py_ssize_t tp_maxalloc; 97 | struct _typeobject *tp_prev; 98 | struct _typeobject *tp_next; 99 | #endif 100 | } PyTypeObject; 101 | #endif 102 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_6_2_H 103 | -------------------------------------------------------------------------------- /type_objects/Python_3.7.1.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/03/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_7_1_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_7_1_H 7 | 8 | #ifdef Py_LIMITED_API 9 | typedef struct _typeobject PyTypeObject; /* opaque */ 10 | #else 11 | typedef struct _typeobject { 12 | PyObject_VAR_HEAD 13 | const char *tp_name; /* For printing, in format "." */ 14 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 15 | 16 | /* Methods to implement standard operations */ 17 | 18 | destructor tp_dealloc; 19 | printfunc tp_print; 20 | getattrfunc tp_getattr; 21 | setattrfunc tp_setattr; 22 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 23 | or tp_reserved (Python 3) */ 24 | reprfunc tp_repr; 25 | 26 | /* Method suites for standard classes */ 27 | 28 | PyNumberMethods *tp_as_number; 29 | PySequenceMethods *tp_as_sequence; 30 | PyMappingMethods *tp_as_mapping; 31 | 32 | /* More standard operations (here for binary compatibility) */ 33 | 34 | hashfunc tp_hash; 35 | ternaryfunc tp_call; 36 | reprfunc tp_str; 37 | getattrofunc tp_getattro; 38 | setattrofunc tp_setattro; 39 | 40 | /* Functions to access object as input/output buffer */ 41 | PyBufferProcs *tp_as_buffer; 42 | 43 | /* Flags to define presence of optional/expanded features */ 44 | unsigned long tp_flags; 45 | 46 | const char *tp_doc; /* Documentation string */ 47 | 48 | /* Assigned meaning in release 2.0 */ 49 | /* call function for all accessible objects */ 50 | traverseproc tp_traverse; 51 | 52 | /* delete references to contained objects */ 53 | inquiry tp_clear; 54 | 55 | /* Assigned meaning in release 2.1 */ 56 | /* rich comparisons */ 57 | richcmpfunc tp_richcompare; 58 | 59 | /* weak reference enabler */ 60 | Py_ssize_t tp_weaklistoffset; 61 | 62 | /* Iterators */ 63 | getiterfunc tp_iter; 64 | iternextfunc tp_iternext; 65 | 66 | /* Attribute descriptor and subclassing stuff */ 67 | struct PyMethodDef *tp_methods; 68 | struct PyMemberDef *tp_members; 69 | struct PyGetSetDef *tp_getset; 70 | struct _typeobject *tp_base; 71 | PyObject *tp_dict; 72 | descrgetfunc tp_descr_get; 73 | descrsetfunc tp_descr_set; 74 | Py_ssize_t tp_dictoffset; 75 | initproc tp_init; 76 | allocfunc tp_alloc; 77 | newfunc tp_new; 78 | freefunc tp_free; /* Low-level free-memory routine */ 79 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 80 | PyObject *tp_bases; 81 | PyObject *tp_mro; /* method resolution order */ 82 | PyObject *tp_cache; 83 | PyObject *tp_subclasses; 84 | PyObject *tp_weaklist; 85 | destructor tp_del; 86 | 87 | /* Type attribute cache version tag. Added in version 2.6 */ 88 | unsigned int tp_version_tag; 89 | 90 | destructor tp_finalize; 91 | 92 | #ifdef COUNT_ALLOCS 93 | /* these must be last and never explicitly initialized */ 94 | Py_ssize_t tp_allocs; 95 | Py_ssize_t tp_frees; 96 | Py_ssize_t tp_maxalloc; 97 | struct _typeobject *tp_prev; 98 | struct _typeobject *tp_next; 99 | #endif 100 | } PyTypeObject; 101 | #endif 102 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_7_1_H 103 | -------------------------------------------------------------------------------- /type_objects/Python_3.8.3.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/03/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_8_3_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_8_3_H 7 | 8 | typedef struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | struct PyMethodDef *tp_methods; 65 | struct PyMemberDef *tp_members; 66 | struct PyGetSetDef *tp_getset; 67 | struct _typeobject *tp_base; 68 | PyObject *tp_dict; 69 | descrgetfunc tp_descr_get; 70 | descrsetfunc tp_descr_set; 71 | Py_ssize_t tp_dictoffset; 72 | initproc tp_init; 73 | allocfunc tp_alloc; 74 | newfunc tp_new; 75 | freefunc tp_free; /* Low-level free-memory routine */ 76 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 77 | PyObject *tp_bases; 78 | PyObject *tp_mro; /* method resolution order */ 79 | PyObject *tp_cache; 80 | PyObject *tp_subclasses; 81 | PyObject *tp_weaklist; 82 | destructor tp_del; 83 | 84 | /* Type attribute cache version tag. Added in version 2.6 */ 85 | unsigned int tp_version_tag; 86 | 87 | destructor tp_finalize; 88 | vectorcallfunc tp_vectorcall; 89 | 90 | /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */ 91 | Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); 92 | 93 | #ifdef COUNT_ALLOCS 94 | /* these must be last and never explicitly initialized */ 95 | Py_ssize_t tp_allocs; 96 | Py_ssize_t tp_frees; 97 | Py_ssize_t tp_maxalloc; 98 | struct _typeobject *tp_prev; 99 | struct _typeobject *tp_next; 100 | #endif 101 | } PyTypeObject; 102 | 103 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_8_3_H 104 | -------------------------------------------------------------------------------- /type_objects/Python_3.8.6.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/03/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_8_6_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_8_6_H 7 | 8 | typedef struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | struct PyMethodDef *tp_methods; 65 | struct PyMemberDef *tp_members; 66 | struct PyGetSetDef *tp_getset; 67 | struct _typeobject *tp_base; 68 | PyObject *tp_dict; 69 | descrgetfunc tp_descr_get; 70 | descrsetfunc tp_descr_set; 71 | Py_ssize_t tp_dictoffset; 72 | initproc tp_init; 73 | allocfunc tp_alloc; 74 | newfunc tp_new; 75 | freefunc tp_free; /* Low-level free-memory routine */ 76 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 77 | PyObject *tp_bases; 78 | PyObject *tp_mro; /* method resolution order */ 79 | PyObject *tp_cache; 80 | PyObject *tp_subclasses; 81 | PyObject *tp_weaklist; 82 | destructor tp_del; 83 | 84 | /* Type attribute cache version tag. Added in version 2.6 */ 85 | unsigned int tp_version_tag; 86 | 87 | destructor tp_finalize; 88 | vectorcallfunc tp_vectorcall; 89 | 90 | /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */ 91 | Py_DEPRECATED(3.8) int (*tp_print)(PyObject *, FILE *, int); 92 | 93 | #ifdef COUNT_ALLOCS 94 | /* these must be last and never explicitly initialized */ 95 | Py_ssize_t tp_allocs; 96 | Py_ssize_t tp_frees; 97 | Py_ssize_t tp_maxalloc; 98 | struct _typeobject *tp_prev; 99 | struct _typeobject *tp_next; 100 | #endif 101 | } PyTypeObject; 102 | 103 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_8_6_H 104 | -------------------------------------------------------------------------------- /type_objects/Python_3.9.0.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Paul Ross on 18/03/2021. 3 | // 4 | 5 | #ifndef PYTHONEXTENSIONSBASIC_PYTHON_3_9_0_H 6 | #define PYTHONEXTENSIONSBASIC_PYTHON_3_9_0_H 7 | 8 | struct _typeobject { 9 | PyObject_VAR_HEAD 10 | const char *tp_name; /* For printing, in format "." */ 11 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 12 | 13 | /* Methods to implement standard operations */ 14 | 15 | destructor tp_dealloc; 16 | Py_ssize_t tp_vectorcall_offset; 17 | getattrfunc tp_getattr; 18 | setattrfunc tp_setattr; 19 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 20 | or tp_reserved (Python 3) */ 21 | reprfunc tp_repr; 22 | 23 | /* Method suites for standard classes */ 24 | 25 | PyNumberMethods *tp_as_number; 26 | PySequenceMethods *tp_as_sequence; 27 | PyMappingMethods *tp_as_mapping; 28 | 29 | /* More standard operations (here for binary compatibility) */ 30 | 31 | hashfunc tp_hash; 32 | ternaryfunc tp_call; 33 | reprfunc tp_str; 34 | getattrofunc tp_getattro; 35 | setattrofunc tp_setattro; 36 | 37 | /* Functions to access object as input/output buffer */ 38 | PyBufferProcs *tp_as_buffer; 39 | 40 | /* Flags to define presence of optional/expanded features */ 41 | unsigned long tp_flags; 42 | 43 | const char *tp_doc; /* Documentation string */ 44 | 45 | /* Assigned meaning in release 2.0 */ 46 | /* call function for all accessible objects */ 47 | traverseproc tp_traverse; 48 | 49 | /* delete references to contained objects */ 50 | inquiry tp_clear; 51 | 52 | /* Assigned meaning in release 2.1 */ 53 | /* rich comparisons */ 54 | richcmpfunc tp_richcompare; 55 | 56 | /* weak reference enabler */ 57 | Py_ssize_t tp_weaklistoffset; 58 | 59 | /* Iterators */ 60 | getiterfunc tp_iter; 61 | iternextfunc tp_iternext; 62 | 63 | /* Attribute descriptor and subclassing stuff */ 64 | struct PyMethodDef *tp_methods; 65 | struct PyMemberDef *tp_members; 66 | struct PyGetSetDef *tp_getset; 67 | struct _typeobject *tp_base; 68 | PyObject *tp_dict; 69 | descrgetfunc tp_descr_get; 70 | descrsetfunc tp_descr_set; 71 | Py_ssize_t tp_dictoffset; 72 | initproc tp_init; 73 | allocfunc tp_alloc; 74 | newfunc tp_new; 75 | freefunc tp_free; /* Low-level free-memory routine */ 76 | inquiry tp_is_gc; /* For PyObject_IS_GC */ 77 | PyObject *tp_bases; 78 | PyObject *tp_mro; /* method resolution order */ 79 | PyObject *tp_cache; 80 | PyObject *tp_subclasses; 81 | PyObject *tp_weaklist; 82 | destructor tp_del; 83 | 84 | /* Type attribute cache version tag. Added in version 2.6 */ 85 | unsigned int tp_version_tag; 86 | 87 | destructor tp_finalize; 88 | vectorcallfunc tp_vectorcall; 89 | }; 90 | 91 | #endif //PYTHONEXTENSIONSBASIC_PYTHON_3_9_0_H 92 | -------------------------------------------------------------------------------- /type_objects/README.md: -------------------------------------------------------------------------------- 1 | These are the the `struct _typeobject` for various vereesions of Python: 2 | 3 | From `Include/cpython/object.h` 4 | 5 | From each Python version named by version for easy comparison. 6 | 7 | Versioned header guards are included. 8 | --------------------------------------------------------------------------------