Library of Assembled Shared Sources
 
Loading...
Searching...
No Matches
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-2026 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 * - @ref ClassConstructors "Constructors": Multiple overloads via \_\_new__ dispatchers
70 * - @ref ClassMethods "Methods": Regular instance methods with overload support
71 * - @ref ClassMethods "Free Methods": Functions that operate on the instance but are not members
72 * - @ref ClassStaticMethods "Static Methods": Class-level methods accessible without instances
73 * - @ref SpecialMethods "Operators": Python special methods (\_\_add__, \_\_eq__, etc.)
74 * - @ref ClassMembers "Properties": Getter/setter pairs for attribute access
75 * - @ref ClassMembers "Public Members": Direct access to public member variables
76 * - @ref ClassAttributes "Static Constants": Class-level constant values
77 * - @ref ClassAttributes "Nested Classes": Inner classes defined within the outer class
78 * - @ref EnumDefinition "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 on a Python class.
84 * Macro names follow a consistent grammar, see @ref PythonMacroName.
85 *
86 * ```cpp
87 * // Define class in header
88 * class MyClass: public PyObjectPlus
89 * {
90 * PY_HEADER(PyObjectPlus)
91 * public:
92 * MyClass();
93 * MyClass(int value);
94 * MyClass(const std::string& name, double factor);
95 *
96 * void process();
97 * void process(int iterations); // Overload #1 (member)
98 * int calculate(int input) const;
99 * std::string getName() const;
100 * void setName(const std::string& name);
101 * static int getGlobalCount();
102 * std::string toString() const; // For __str__
103 *
104 * int publicValue;
105 * static const int MAX_SIZE = 100;
106 * };
107 *
108 * // Free functions for export as methods
109 * void reset(MyClass* obj);
110 * void process(MyClass* obj, const std::string& mode); // Overload #2 (free function)
111 * double computeScore(const MyClass& obj, double multiplier);
112 * MyClass combine(const MyClass& a, const MyClass& b);
113 *
114 * // Special method implementations
115 * bool operator==(const MyClass& a, const MyClass& b);
116 * bool operator<(const MyClass& a, const MyClass& b);
117 * MyClass operator+(const MyClass& a, const MyClass& b);
118 * MyClass operator-(const MyClass& a, const MyClass& b);
119 * std::string repr(const MyClass& obj); // For __repr__
120 * size_t hash(const MyClass& obj); // For __hash__
121 *
122 * // Register class methods and properties in source
123 * PY_DECLARE_CLASS_NAME_DOC(MyClass, "MyClass", "Demonstration class with various binding types")
124 *
125 * // Multiple constructor overloads
126 * PY_CLASS_CONSTRUCTOR_0(MyClass)
127 * PY_CLASS_CONSTRUCTOR_1(MyClass, int)
128 * PY_CLASS_CONSTRUCTOR_2(MyClass, const std::string&, double)
129 *
130 * // Mixed overloading: regular methods + free functions for same Python method
131 * PY_CLASS_METHOD(MyClass, process) // void process()
132 * PY_CLASS_METHOD_1(MyClass, process, int) // void process(int)
133 * PY_CLASS_FREE_METHOD_NAME(MyClass, process, "process") // void process(MyClass*, const std::string&)
134 *
135 * // Other regular methods
136 * PY_CLASS_METHOD_DOC(MyClass, calculate, "Calculate result from input value")
137 * PY_CLASS_STATIC_METHOD(MyClass, getGlobalCount)
138 *
139 * // Properties (getter/setter pairs)
140 * PY_CLASS_MEMBER_RW_DOC(MyClass, name, getName, setName, "Object name property")
141 *
142 * // Public member access
143 * PY_CLASS_MEMBER_R(MyClass, publicValue)
144 *
145 * // Free functions as methods (various forms)
146 * PY_CLASS_FREE_METHOD(MyClass, reset)
147 * PY_CLASS_FREE_METHOD_NAME_DOC(MyClass, computeScore, "compute_score", "Calculate score with multiplier")
148 * PY_CLASS_FREE_METHOD_DOC(MyClass, combine, "Combine two objects")
149 *
150 * // Python special methods via member and free functions
151 * PY_CLASS_METHOD_NAME(MyClass, toString, methods::_str_) // Member function for __str__
152 * PY_CLASS_FREE_METHOD_NAME(MyClass, repr, methods::_repr_) // Free function for __repr__
153 * PY_CLASS_FREE_METHOD_NAME(MyClass, hash, methods::_hash_) // Free function for __hash__
154 *
155 * // Comparison and arithmetic operators
156 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator==, methods::_eq_)
157 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator<, methods::_lt_)
158 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator+, methods::_add_)
159 * PY_CLASS_FREE_METHOD_NAME(MyClass, operator-, methods::_sub_)
160 *
161 * // Static constants and values
162 * PY_CLASS_STATIC_CONST(MyClass, "MAX_SIZE", MyClass::MAX_SIZE)
163 * PY_CLASS_STATIC_CONST(MyClass, "VERSION", "1.2.3")
164 *
165 * // Add to module
166 * PY_MODULE_CLASS(mymodule, MyClass)
167 * ```
168 *
169 * @ingroup Python
170 */
171 class EnumDefinitionBase;
172
173 namespace impl
174 {
175 LASS_PYTHON_DLL PyMethodDef LASS_CALL createPyMethodDef(
176 const char *ml_name, PyCFunction ml_meth, int ml_flags,
177 const char *ml_doc);
178 LASS_PYTHON_DLL PyGetSetDef LASS_CALL createPyGetSetDef(
179 const char* name, getter get, setter set, const char* doc, void* closure);
180
181 LASS_PYTHON_DLL void LASS_CALL dealloc(PyObject* obj);
182 LASS_PYTHON_DLL PyObject* LASS_CALL repr(PyObject* obj);
183 LASS_PYTHON_DLL PyObject* LASS_CALL str(PyObject* obj);
184
185 class NamePredicate
186 {
187 public:
188 NamePredicate(const char* name): name_(name) {}
189 template <typename T> bool operator()(const T& x) const { return cmp(x.name()); }
190 bool operator()(const PyMethodDef& x) const { return cmp(x.ml_name); }
191 template <typename T> bool operator()(T* x) const { return (*this)(*x); }
192 private:
193 bool cmp(const char* name) const { return name && strcmp(name, name_) == 0; }
194 const char* name_;
195 };
196
197 class StaticMemberHelper
198 {
199 public:
200 virtual ~StaticMemberHelper() {}
201 virtual TPyObjPtr build() const = 0;
202 };
203 typedef util::SharedPtr<StaticMemberHelper> TStaticMemberHelperPtr;
204
205 template <typename T>
206 class StaticMemberHelperObject: public StaticMemberHelper
207 {
208 public:
209 StaticMemberHelperObject(const T& obj): obj_(obj) {}
210 TPyObjPtr build() const override { return TPyObjPtr(PyExportTraits<const T>::build(obj_)); }
211 private:
212 const T obj_;
213 };
214 template <typename T, size_t N>
215 class StaticMemberHelperObject<T[N]>: public StaticMemberHelper
216 {
217 public:
218 StaticMemberHelperObject(const T obj[N]): obj_(obj) {}
219 TPyObjPtr build() const override { return TPyObjPtr(PyExportTraits<const T*>::build(obj_)); }
220 private:
221 const T* obj_;
222 };
223 template <>
224 class StaticMemberHelperObject<PyObject*>: public StaticMemberHelper
225 {
226 public:
227 StaticMemberHelperObject(PyObject* obj): obj_(obj) {}
228 TPyObjPtr build() const override { return obj_; }
229 private:
230 TPyObjPtr obj_;
231 };
232 template <typename T>
233 inline TStaticMemberHelperPtr staticMemberHelperObject(const T& obj)
234 {
235 return TStaticMemberHelperPtr(new StaticMemberHelperObject<T>(obj));
236 }
237
238 class StaticMember
239 {
240 public:
241 StaticMember(const char* name, const TStaticMemberHelperPtr member): member_(member), name_(name) {}
242 const TStaticMemberHelperPtr& member() const { return member_; }
243 const char* name() const { return name_; }
244 private:
245 TStaticMemberHelperPtr member_;
246 const char* name_;
247 };
248
249 struct LASS_PYTHON_DLL CompareFunc
250 {
251 PyCFunction dispatcher;
252 int op;
253 CompareFunc(PyCFunction dispatcher, int op): dispatcher(dispatcher), op(op) {}
254 };
255
256 template <typename CppClass> PyObject* richCompareDispatcher(PyObject* self, PyObject* other, int op)
257 {
258 return CppClass::_lassPyClassDef.callRichCompare(self, other, op);
259 }
260
261 /** Definition of a Python class.
262 *
263 * Collects all information required to define a Python type that wraps a C++ class.
264 * The definition accumulates constructors, methods (including Python slot methods),
265 * properties, static members, and nested types. When ready, call freezeDefinition()
266 * to create the underlying `PyTypeObject` and (optionally) inject it into a module
267 * or as a nested type.
268 *
269 * The ClassDefinition class is typically not used directly by user code. Instead, use the
270 * provided macros in pyobject_macros.h that work with ClassDefinition instances.
271 *
272 * @ingroup ClassDefinition
273 */
274 class LASS_PYTHON_DLL ClassDefinition
275 {
276 public:
277 typedef void(*TClassRegisterHook)(); /// Function to call during registration of the class (optional).
278 typedef int TSlotID; ///< Identifier type for indexing into the internal Python slot table.
279
280 /** Construct a class definition.
281 * @param name Python class name
282 * @param doc Class docstring (must outlive the definition unless changed via setDoc())
283 * @param typeSize Size of the Python instance struct (derived from PyObject)
284 * @param richcmp Optional rich-compare function (may be nullptr)
285 * @param parent Optional parent to nest this class into (may be nullptr)
286 * @param registerHook Optional hook called when the class is registered
287 */
288 ClassDefinition(const char* name, const char* doc, Py_ssize_t typeSize,
289 richcmpfunc richcmp, ClassDefinition* parent, TClassRegisterHook registerHook);
290 /** Destructor. */
292
293 /** Get the Python type object (available after freezeDefinition() has been called). */
294 /// @{
295 PyTypeObject* type();
296 const PyTypeObject* type() const;
297 /// @}
298
299 /** Get the class name. */
300 const char* name() const;
301 /** Get the class docstring. */
302 const char* doc() const;
303 /** Set the class docstring.
304 * Note: the provided string must remain valid until another docstring is set.
305 */
306 void setDoc(const char* doc);
307 /** Set the class docstring if non-null (keeps existing one if nullptr). */
308 void setDocIfNotNull(const char* doc);
309
310 /** Add a constructor overload (\_\_init__ dispatcher).
311 * @param dispatcher New-function dispatcher
312 * @param overloadChain Reference to overload chain
313 */
314 void addConstructor(newfunc dispatcher, newfunc& overloadChain);
315
316 /** Add a named method.
317 * @param name, slot Python method name or special slot
318 * @param doc Method docstring
319 * @param dispatcher Dispatcher implementing the method
320 * @param overloadChain Overload chain for this name
321 */
322 /// @{
323 void addMethod(const char* name, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain);
324 /** Add a comparator-slot method (rich compare operator). */
325 void addMethod(const ComparatorSlot& slot, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain);
326 /** Add a unary-slot method (e.g., \_\_neg__, \_\_pos__, ...). */
327 void addMethod(const UnarySlot& slot, const char* doc, unaryfunc dispatcher, OverloadLink& overloadChain);
328 /** Add a binary-slot method (e.g., arithmetic, comparisons). */
329 void addMethod(const BinarySlot& slot, const char* doc, binaryfunc dispatcher, OverloadLink& overloadChain);
330 /** Add a ternary-slot method. */
331 void addMethod(const TernarySlot& slot, const char* doc, ternaryfunc dispatcher, OverloadLink& overloadChain);
332 /** Add a length-slot method (\_\_len__). */
333 void addMethod(const LenSlot& slot, const char* doc, lenfunc dispatcher, OverloadLink& overloadChain);
334 /** Add an ssize-arg-slot method. */
335 void addMethod(const SsizeArgSlot& slot, const char* doc, ssizeargfunc dispatcher, OverloadLink& overloadChain);
336 /** Add an ssize-obj-arg-slot method. */
337 void addMethod(const SsizeObjArgSlot& slot, const char* doc, ssizeobjargproc dispatcher, OverloadLink& overloadChain);
338 /** Add an obj-obj-slot method. */
339 void addMethod(const ObjObjSlot& slot, const char* doc, objobjproc dispatcher, OverloadLink& overloadChain);
340 /** Add an obj-obj-arg-slot method. */
341 void addMethod(const ObjObjArgSlot& slot, const char* doc, objobjargproc dispatcher, OverloadLink& overloadChain);
342 /** Add an iterator-slot method (\_\_iter__). */
343 void addMethod(const IterSlot& slot, const char* doc, getiterfunc dispatcher, OverloadLink& overloadChain);
344 /** Add an iternext-slot method (\_\_next__). */
345 void addMethod(const IterNextSlot& slot, const char* doc, iternextfunc dispatcher, OverloadLink& overloadChain);
346 /** Add an arg+kw-slot method (callable semantics). */
347 void addMethod(const ArgKwSlot& slot, const char* doc, ternaryfunc dispatcher, OverloadLink& overloadChain);
348 /** Add an inquiry-slot method. */
349 void addMethod(const InquirySlot& slot, const char* doc, inquiry dispatcher, OverloadLink& overloadChain);
350 /** Add a property with optional getter/setter. */
351 void addGetSetter(const char* name, const char* doc, getter get, setter set);
352 /** Add a static method with overload support. */
353 void addStaticMethod(const char* name, const char* doc, PyCFunction dispatcher, PyCFunction& overloadChain);
354 /// @}
355
356 template <typename T> void addStaticConst(const char* name, const T& value)
357 {
358 LASS_ASSERT(std::count_if(statics_.begin(), statics_.end(), NamePredicate(name)) == 0);
359 statics_.push_back(StaticMember(name, staticMemberHelperObject(value)));
360 }
361
362 /// @{
363 /** Add a nested class definition (inner class). */
364 void addInnerClass(ClassDefinition& innerClass);
365 /** Add a nested enum definition (inner enum). */
366 void addInnerEnum(EnumDefinitionBase* enumDefinition);
367 /// @}
368
369 /// @{
370 /** Get raw pointer from a given slot id. */
371 void* getSlot(TSlotID slotId);
372 /** Set raw pointer for a given slot id. */
373 void* setSlot(TSlotID slotId, void* value);
374 template <typename Ptr> Ptr setSlot(TSlotID slotId, Ptr value)
375 {
376 return reinterpret_cast<Ptr>(setSlot(slotId, reinterpret_cast<void*>(value)));
377 }
378 /// @}
379
380 /** Finalize the definition and create the Python type.
381 * If a module is provided, the type is added to that module.
382 */
383 PyObject* freezeDefinition(PyObject* module = nullptr);
384
385 /** Dispatch rich-compare for this class (used by operator slots). */
386 PyObject* callRichCompare(PyObject* self, PyObject* other, int op);
387
388 private:
389
390 friend LASS_PYTHON_DLL PyObject* LASS_CALL establishMagicalBackLinks(PyObject* result, PyObject* self);
391
392 template <typename T> friend struct ShadowTraits; // to have acces to implicitConvertersSlot_, see below.
393
394 typedef std::vector<PyMethodDef> TMethods;
395 typedef std::vector<PyGetSetDef> TGetSetters;
396 typedef std::vector<CompareFunc> TCompareFuncs;
397 typedef std::vector<StaticMember> TStaticMembers;
398 typedef std::vector<ClassDefinition*> TClassDefs;
399 typedef std::vector<EnumDefinitionBase*> TEnumDefs;
400 typedef std::vector<PyType_Slot> TSlots;
401
402 PyObject* freezeDefinition(PyObject* module, const char* scopeName);
403 int freezeType();
404
405 PyType_Spec spec_;
406 TPyObjPtr type_;
407 TSlots slots_;
408 TMethods methods_;
409 TGetSetters getSetters_;
410 TCompareFuncs compareFuncs_;
411 TStaticMembers statics_;
412 TClassDefs innerClasses_;
413 TClassDefs subClasses_;
414 TEnumDefs innerEnums_;
415 ClassDefinition* parent_;
416 TClassRegisterHook classRegisterHook_;
417 const char* className_;
418 const char* doc_;
419
420 /** Typeless slot for ExportTraits to store implicit converters.
421 * This used to be a static in the templated ExportTraits, but since
422 * the initial value is then defined in a header (PY_SHADOW_CASTERS
423 * is usually invoked in a header), this causes a static variable
424 * to be defined _per_ DLL, if that type is used in multiple DLLs.
425 * To avoid that, we moved it to header, which we know will be
426 * defined in a source file, and will thus exist only once.
427 */
428 void* implicitConvertersSlot_;
429
430 bool isFrozen_;
431 };
432 }
433 }
434}
435
436#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.