├── .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 |
--------------------------------------------------------------------------------