Library of Assembled Shared Sources
class_definition.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#ifndef LASS_GUARDIAN_OF_INCLUSION_PYTHON_CLASS_DEFINITION_H
44#define LASS_GUARDIAN_OF_INCLUSION_PYTHON_CLASS_DEFINITION_H
45
46#include "python_common.h"
48#include "overload_link.h"
49#include "export_traits.h"
50#include "../util/shared_ptr.h"
51
52namespace lass
53{
54 namespace python
55 {
56 /** @defgroup ClassDefinition Class Definitions
57 * @brief Defining Python classes from C++ with methods, properties, operators, and nested types.
58 *
59 * This module provides the internal machinery used by the class export macros to define
60 * Python classes backed by C++ types. A class definition aggregates constructors, methods
61 * (including Python special methods/operators), properties, static members, nested classes
62 * and enums, and then materializes a Python type when frozen.
63 *
64 *
65 * ### Class Components
66 *
67 * A Python class can contain:
68 *
69 * - **Constructors**: Multiple overloads via \_\_new__ dispatchers
70 * - **Methods**: Regular instance methods with overload support
71 * - **Free Methods**: Functions that operate on the instance but are not members
72 * - **Static Methods**: Class-level methods accessible without instances
73 * - **Operators**: Python special methods (\_\_add__, \_\_eq__, etc.)
74 * - **Properties**: Getter/setter pairs for attribute access
75 * - **Public Members**: Direct access to public member variables
76 * - **Static Constants**: Class-level constant values
77 * - **Nested Classes**: Inner classes defined within the outer class
78 * - **Nested Enums**: Enum types scoped to the class
79 *
80 *
81 * ### Usage Overview
82 *
83 * To export a C++ class to Python, you typically use macros that work with ClassDefinition:
84 *
85 * ```cpp
86 * // Define class in header
87 * class MyClass: public PyObjectPlus
88 * {
89 * PY_HEADER(PyObjectPlus)
90 * public:
91 * MyClass();
92 * MyClass(int value);
93 * MyClass(const std::string& name, double factor);
94 *
95 * void process();
96 * void process(int iterations); // Overload #1 (member)
97 * int calculate(int input) const;
98 * std::string getName() const;
99 * void setName(const std::string& name);
100 * static int getGlobalCount();
101 * std::string toString() const; // For __str__
102 *
103 * int publicValue;
104 * static const int MAX_SIZE = 100;
105 * };
106 *
107 * // Free functions for export as methods
108 * void reset(MyClass* obj);
109 * void process(MyClass* obj, const std::string& mode); // Overload #2 (free function)
110 * double computeScore(const MyClass& obj, double multiplier);
111 * MyClass combine(const MyClass& a, const MyClass& b);
112 *
113 * // Special method implementations
114 * bool operator==(const MyClass& a, const MyClass& b);
115 * bool operator<(const MyClass& a, const MyClass& b);
116 * MyClass operator+(const MyClass& a, const MyClass& b);
117 * MyClass operator-(const MyClass& a, const MyClass& b);
118 * std::string repr(const MyClass& obj); // For __repr__
119 * size_t hash(const MyClass& obj); // For __hash__
120 *
121 * // Register class methods and properties in source
122 * PY_DECLARE_CLASS_NAME_DOC(MyClass, "MyClass", "Demonstration class with various binding types")
123 *
124 * // Multiple constructor overloads
125 * PY_CLASS_CONSTRUCTOR_0(MyClass)
126 * PY_CLASS_CONSTRUCTOR_1(MyClass, int)
127 * PY_CLASS_CONSTRUCTOR_2(MyClass, const std::string&, double)
128 *
129 * // Mixed overloading: regular methods + free functions for same Python method
130 * PY_CLASS_METHOD(MyClass, process) // void process()
131 * PY_CLASS_METHOD_1(MyClass, process, int) // void process(int)
132 * PY_CLASS_FREE_METHOD_NAME(MyClass, process, "process") // void process(MyClass*, const std::string&)
133 *
134 * // Other regular methods
135 * PY_CLASS_METHOD_DOC(MyClass, calculate, "Calculate result from input value")
136 * PY_CLASS_STATIC_METHOD(MyClass, getGlobalCount)
137 *
138 * // Properties (getter/setter pairs)
139 * PY_CLASS_MEMBER_RW_DOC(MyClass, name, getName, setName, "Object name property")
140 *
141 * // Public member access
142 * PY_CLASS_MEMBER_R(MyClass, publicValue)
143 *
144 * // Free functions as methods (various forms)
145 * PY_CLASS_FREE_METHOD(MyClass, reset)
146 * PY_CLASS_FREE_METHOD_NAME_DOC(MyClass, computeScore, "compute_score", "Calculate score with multiplier")
147 * PY_CLASS_FREE_METHOD_DOC(MyClass, combine, "Combine two objects")
148 *
149 * // Python special methods via member and free functions
150 * PY_CLASS_METHOD_NAME(MyClass, toString, methods::_str_) // Member function for __str__
151 * PY_CLASS_FREE_METHOD_NAME(MyClass, repr, methods::_repr_) // Free function for __repr__
152 * PY_CLASS_FREE_METHOD_NAME(MyClass, hash, methods::_hash_) // Free function for __hash__
153 *
154 * // Comparison and arithmetic operators
155 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator==, methods::_eq_)
156 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator<, methods::_lt_)
157 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator+, methods::_add_)
158 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator-, methods::_sub_)
159 *
160 * // Static constants and values
161 * PY_CLASS_STATIC_CONST(MyClass, "MAX_SIZE", MyClass::MAX_SIZE)
162 * PY_CLASS_STATIC_CONST(MyClass, "VERSION", "1.2.3")
163 *
164 * // Add to module
165 * PY_MODULE_CLASS(mymodule, MyClass)
166 * ```
167 *
168 * @ingroup Python
169 */
170 class EnumDefinitionBase;
171
172 namespace impl
173 {
174 LASS_PYTHON_DLL PyMethodDef LASS_CALL createPyMethodDef(
175 const char *ml_name, PyCFunction ml_meth, int ml_flags,
176 const char *ml_doc);
177 LASS_PYTHON_DLL PyGetSetDef LASS_CALL createPyGetSetDef(
178 const char* name, getter get, setter set, const char* doc, void* closure);
179
180 LASS_PYTHON_DLL void LASS_CALL dealloc(PyObject* obj);
181 LASS_PYTHON_DLL PyObject* LASS_CALL repr(PyObject* obj);
182 LASS_PYTHON_DLL PyObject* LASS_CALL str(PyObject* obj);
183
184 class NamePredicate
185 {
186 public:
187 NamePredicate(const char* name): name_(name) {}
188 template <typename T> bool operator()(const T& x) const { return cmp(x.name()); }
189 bool operator()(const PyMethodDef& x) const { return cmp(x.ml_name); }
190 template <typename T> bool operator()(T* x) const { return (*this)(*x); }
191 private:
192 bool cmp(const char* name) const { return name && strcmp(name, name_) == 0; }
193 const char* name_;
194 };
195
196 class StaticMemberHelper
197 {
198 public:
199 virtual ~StaticMemberHelper() {}
200 virtual TPyObjPtr build() const = 0;
201 };
202 typedef util::SharedPtr<StaticMemberHelper> TStaticMemberHelperPtr;
203
204 template <typename T>
205 class StaticMemberHelperObject: public StaticMemberHelper
206 {
207 public:
208 StaticMemberHelperObject(const T& obj): obj_(obj) {}
209 TPyObjPtr build() const override { return TPyObjPtr(PyExportTraits<const T>::build(obj_)); }
210 private:
211 const T obj_;
212 };
213 template <typename T, size_t N>
214 class StaticMemberHelperObject<T[N]>: public StaticMemberHelper
215 {
216 public:
217 StaticMemberHelperObject(const T obj[N]): obj_(obj) {}
218 TPyObjPtr build() const override { return TPyObjPtr(PyExportTraits<const T*>::build(obj_)); }
219 private:
220 const T* obj_;
221 };
222 template <>
223 class StaticMemberHelperObject<PyObject*>: public StaticMemberHelper
224 {
225 public:
226 StaticMemberHelperObject(PyObject* obj): obj_(obj) {}
227 TPyObjPtr build() const override { return obj_; }
228 private:
229 TPyObjPtr obj_;
230 };
231 template <typename T>
232 inline TStaticMemberHelperPtr staticMemberHelperObject(const T& obj)
233 {
234 return TStaticMemberHelperPtr(new StaticMemberHelperObject<T>(obj));
235 }
236
237 class StaticMember
238 {
239 public:
240 StaticMember(const char* name, const TStaticMemberHelperPtr member): member_(member), name_(name) {}
241 const TStaticMemberHelperPtr& member() const { return member_; }
242 const char* name() const { return name_; }
243 private:
244 TStaticMemberHelperPtr member_;
245 const char* name_;
246 };
247
248 struct LASS_PYTHON_DLL CompareFunc
249 {
250 PyCFunction dispatcher;
251 int op;
252 CompareFunc(PyCFunction dispatcher, int op): dispatcher(dispatcher), op(op) {}
253 };
254
255 template <typename CppClass> PyObject* richCompareDispatcher(PyObject* self, PyObject* other, int op)
256 {
257 return CppClass::_lassPyClassDef.callRichCompare(self, other, op);
258 }
259
260 /** Definition of a Python class.
261 *
262 * Collects all information required to define a Python type that wraps a C++ class.
263 * The definition accumulates constructors, methods (including Python slot methods),
264 * properties, static members, and nested types. When ready, call freezeDefinition()
265 * to create the underlying `PyTypeObject` and (optionally) inject it into a module
266 * or as a nested type.
267 *
268 * The ClassDefinition class is typically not used directly by user code. Instead, use the
269 * provided macros in pyobject_macros.h that work with ClassDefinition instances.
270 *
271 * @ingroup ClassDefinition
272 */
273 class LASS_PYTHON_DLL ClassDefinition
274 {
275 public:
276 typedef void(*TClassRegisterHook)(); /// Function to call during registration of the class (optional).
277 typedef int TSlotID; ///< Identifier type for indexing into the internal Python slot table.
278
279 /** Construct a class definition.
280 * @param name Python class name
281 * @param doc Class docstring (must outlive the definition unless changed via setDoc())
282 * @param typeSize Size of the Python instance struct (derived from PyObject)
283 * @param richcmp Optional rich-compare function (may be nullptr)
284 * @param parent Optional parent to nest this class into (may be nullptr)
285 * @param registerHook Optional hook called when the class is registered
286 */
287 ClassDefinition(const char* name, const char* doc, Py_ssize_t typeSize,
288 richcmpfunc richcmp, ClassDefinition* parent, TClassRegisterHook registerHook);
289 /** Destructor. */
291
292 /** Get the Python type object (available after freezeDefinition() has been called). */
293 /// @{
294 PyTypeObject* type();
295 const PyTypeObject* type() const;
296 /// @}
297
298 /** Get the class name. */
299 const char* name() const;
300 /** Get the class docstring. */
301 const char* doc() const;
302 /** Set the class docstring.
303 * Note: the provided string must remain valid until another docstring is set.
304 */
305 void setDoc(const char* doc);
306 /** Set the class docstring if non-null (keeps existing one if nullptr). */
307 void setDocIfNotNull(const char* doc);
308
309 /** Add a constructor overload (\_\_init__ dispatcher).
310 * @param dispatcher New-function dispatcher
311 * @param overloadChain Reference to overload chain
312 */
313 void addConstructor(newfunc dispatcher, newfunc& overloadChain);
314
315 /** Add a named method.
316 * @param name, slot Python method name or special slot
317 * @param doc Method docstring
318 * @param dispatcher Dispatcher implementing the method
319 * @param overloadChain Overload chain for this name
320 */
321 /// @{
322 void addMethod(const char* name, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain);
323 /** Add a comparator-slot method (rich compare operator). */
324 void addMethod(const ComparatorSlot& slot, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain);
325 /** Add a unary-slot method (e.g., \_\_neg__, \_\_pos__, ...). */
326 void addMethod(const UnarySlot& slot, const char* doc, unaryfunc dispatcher, OverloadLink& overloadChain);
327 /** Add a binary-slot method (e.g., arithmetic, comparisons). */
328 void addMethod(const BinarySlot& slot, const char* doc, binaryfunc dispatcher, OverloadLink& overloadChain);
329 /** Add a ternary-slot method. */
330 void addMethod(const TernarySlot& slot, const char* doc, ternaryfunc dispatcher, OverloadLink& overloadChain);
331 /** Add a length-slot method (\_\_len__). */
332 void addMethod(const LenSlot& slot, const char* doc, lenfunc dispatcher, OverloadLink& overloadChain);
333 /** Add an ssize-arg-slot method. */
334 void addMethod(const SsizeArgSlot& slot, const char* doc, ssizeargfunc dispatcher, OverloadLink& overloadChain);
335 /** Add an ssize-obj-arg-slot method. */
336 void addMethod(const SsizeObjArgSlot& slot, const char* doc, ssizeobjargproc dispatcher, OverloadLink& overloadChain);
337 /** Add an obj-obj-slot method. */
338 void addMethod(const ObjObjSlot& slot, const char* doc, objobjproc dispatcher, OverloadLink& overloadChain);
339 /** Add an obj-obj-arg-slot method. */
340 void addMethod(const ObjObjArgSlot& slot, const char* doc, objobjargproc dispatcher, OverloadLink& overloadChain);
341 /** Add an iterator-slot method (\_\_iter__). */
342 void addMethod(const IterSlot& slot, const char* doc, getiterfunc dispatcher, OverloadLink& overloadChain);
343 /** Add an iternext-slot method (\_\_next__). */
344 void addMethod(const IterNextSlot& slot, const char* doc, iternextfunc dispatcher, OverloadLink& overloadChain);
345 /** Add an arg+kw-slot method (callable semantics). */
346 void addMethod(const ArgKwSlot& slot, const char* doc, ternaryfunc dispatcher, OverloadLink& overloadChain);
347 /** Add an inquiry-slot method. */
348 void addMethod(const InquirySlot& slot, const char* doc, inquiry dispatcher, OverloadLink& overloadChain);
349 /** Add a property with optional getter/setter. */
350 void addGetSetter(const char* name, const char* doc, getter get, setter set);
351 /** Add a static method with overload support. */
352 void addStaticMethod(const char* name, const char* doc, PyCFunction dispatcher, PyCFunction& overloadChain);
353 /// @}
354
355 template <typename T> void addStaticConst(const char* name, const T& value)
356 {
357 LASS_ASSERT(std::count_if(statics_.begin(), statics_.end(), NamePredicate(name)) == 0);
358 statics_.push_back(StaticMember(name, staticMemberHelperObject(value)));
359 }
360
361 /// @{
362 /** Add a nested class definition (inner class). */
363 void addInnerClass(ClassDefinition& innerClass);
364 /** Add a nested enum definition (inner enum). */
365 void addInnerEnum(EnumDefinitionBase* enumDefinition);
366 /// @}
367
368 /// @{
369 /** Get raw pointer from a given slot id. */
370 void* getSlot(TSlotID slotId);
371 /** Set raw pointer for a given slot id. */
372 void* setSlot(TSlotID slotId, void* value);
373 template <typename Ptr> Ptr setSlot(TSlotID slotId, Ptr value)
374 {
375 return reinterpret_cast<Ptr>(setSlot(slotId, reinterpret_cast<void*>(value)));
376 }
377 /// @}
378
379 /** Finalize the definition and create the Python type.
380 * If a module is provided, the type is added to that module.
381 */
382 PyObject* freezeDefinition(PyObject* module = nullptr);
383
384 /** Dispatch rich-compare for this class (used by operator slots). */
385 PyObject* callRichCompare(PyObject* self, PyObject* other, int op);
386
387 private:
388
389 friend LASS_PYTHON_DLL PyObject* LASS_CALL establishMagicalBackLinks(PyObject* result, PyObject* self);
390
391 template <typename T> friend struct ShadowTraits; // to have acces to implicitConvertersSlot_, see below.
392
393 typedef std::vector<PyMethodDef> TMethods;
394 typedef std::vector<PyGetSetDef> TGetSetters;
395 typedef std::vector<CompareFunc> TCompareFuncs;
396 typedef std::vector<StaticMember> TStaticMembers;
397 typedef std::vector<ClassDefinition*> TClassDefs;
398 typedef std::vector<EnumDefinitionBase*> TEnumDefs;
399 typedef std::vector<PyType_Slot> TSlots;
400
401 PyObject* freezeDefinition(PyObject* module, const char* scopeName);
402
403 PyType_Spec spec_;
404 TPyObjPtr type_;
405 TSlots slots_;
406 TMethods methods_;
407 TGetSetters getSetters_;
408 TCompareFuncs compareFuncs_;
409 TStaticMembers statics_;
410 TClassDefs innerClasses_;
411 TClassDefs subClasses_;
412 TEnumDefs innerEnums_;
413 ClassDefinition* parent_;
414 TClassRegisterHook classRegisterHook_;
415 const char* className_;
416 const char* doc_;
417
418 /** Typeless slot for ExportTraits to store implicit converters.
419 * This used to be a static in the templated ExportTraits, but since
420 * the initial value is then defined in a header (PY_SHADOW_CASTERS
421 * is usually invoked in a header), this causes a static variable
422 * to be defined _per_ DLL, if that type is used in multiple DLLs.
423 * To avoid that, we moved it to header, which we know will be
424 * defined in a source file, and will thus exist only once.
425 */
426 void* implicitConvertersSlot_;
427
428 bool isFrozen_;
429 };
430 }
431 }
432}
433
434#endif
Base class of all enum definitions.
Definition of a Python class.
void addGetSetter(const char *name, const char *doc, getter get, setter set)
Add a property with optional getter/setter.
void setDocIfNotNull(const char *doc)
Set the class docstring if non-null (keeps existing one if nullptr).
void addMethod(const char *name, const char *doc, PyCFunction dispatcher, OverloadLink &overloadChain)
Add a named method.
void * setSlot(TSlotID slotId, void *value)
Set raw pointer for a given slot id.
ClassDefinition(const char *name, const char *doc, Py_ssize_t typeSize, richcmpfunc richcmp, ClassDefinition *parent, TClassRegisterHook registerHook)
Construct a class definition.
Ptr setSlot(TSlotID slotId, Ptr value)
Get raw pointer from a given slot id.
int TSlotID
Function to call during registration of the class (optional).
PyTypeObject * type()
Get the Python type object (available after freezeDefinition() has been called).
const char * doc() const
Get the class docstring.
void addStaticMethod(const char *name, const char *doc, PyCFunction dispatcher, PyCFunction &overloadChain)
Add a static method with overload support.
void setDoc(const char *doc)
Set the class docstring.
void addConstructor(newfunc dispatcher, newfunc &overloadChain)
Add a constructor overload (__init__ dispatcher).
const char * name() const
Get the class name.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53
PyObject * establishMagicalBackLinks(PyObject *result, PyObject *self)
Here, we try to fix some lifetime issues to guarantee some lifetime requirements on self.