Skip to content
panzergame edited this page Dec 6, 2016 · 2 revisions

Principle

Python proxies are needed to make an interface with the user through python. They are called proxy because if the targeted data in C++ is freed the user could still see its representation in python but can't access to its content.

The python proxy is created by inheriting a class by PyObjectPlus or any derived type: CValue, CPropValue… Once the class is inheriting, the python proxy type, attributes and methods need to be declared, to do this the macro Py_Header is doing this job.

The python proxy offers attributes and methods, they can both be defined with functions. The only special case is for primitive attributes type like float, int, bool, char *… From header the functions are declared with macro like:

KX_PYMETHOD_DOC
KX_PYMETHOD_VARARGS
KX_PYMETHOD
KX_PYMETHOD_O
KX_PYMETHOD_NOARGS

instead for attributes functions:

static PyObject *pyattr_get_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
static int pyattr_set_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);

Example

An example of different functions types and bool attributes:

#ifndef __EXAMPLE_H__
#define __EXAMPLE_H__

#include "EXP_Value.h"

class Example : public CValue
{
private:
	// Defines python type, attributes and methodes structs.
	Py_Header

	bool m_var1;

public:
	Example();
	virtual ~Example();

	virtual STR_String GetName();

	bool GetVar1() const;
	void SetVar1(bool var);

#ifdef WITH_PYTHON
	static PyObject *pyattr_get_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef);
	static int pyattr_set_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value);

	KX_PYMETHOD_DOC(Example, func1);
	KX_PYMETHOD_VARARGS(Example, func2);
	KX_PYMETHOD(Example, func3);
	KX_PYMETHOD_O(Example, func4);
	KX_PYMETHOD_NOARGS(Example, func5);
#endif
};

#endif /* __BL_SHADER_H__ */
#include "example.h"

Example::Example()
	:m_var1(true)
{
}

Example::~Example()
{
}

bool Example::GetVar1() const
{
	return m_var1;
}

void Example::SetVar1(bool var)
{
	m_var1 = var;
}

#ifdef WITH_PYTHON

PyMethodDef Example::Methods[] = {
	KX_PYMETHODTABLE(Example, func1),
	{"func2", (PyCFunction)Example::sPyfunc2, METH_VARARGS},
	{"func3", (PyCFunction)Example::sPyfunc3, METH_VARARGS | METH_KEYWORDS},
	{"func4", (PyCFunction)Example::sPyfunc4, METH_O},
	{"func5", (PyCFunction)Example::sPyfunc5, METH_NOARGS},
	{NULL, NULL} // Sentinel
};

PyAttributeDef Example::Attributes[] = {
	// An attribute written and read with two functions
	KX_PYATTRIBUTE_RW_FUNCTION("var1", Example, pyattr_get_var1, pyattr_set_var1),
	// Equivalent without functions in case check are not needed and types are primitive.
	KX_PYATTRIBUTE_BOOL_RW("var2", Example, m_var1),
	{NULL} // Sentinel
};

PyTypeObject Example::Type = {
	PyVarObject_HEAD_INIT(NULL, 0)
	"Example", // The name of the class in python.
	sizeof(PyObjectPlus_Proxy),
	0,
	py_base_dealloc,
	0,
	0,
	0,
	0,
	py_base_repr,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
	0, 0, 0, 0, 0, 0, 0,
	Methods,
	0,
	0,
	&PyObjectPlus::Type,
	0, 0, 0, 0, 0, 0,
	py_base_new
};

PyObject *Example::pyattr_get_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef)
{
	Example *self = static_cast<Example *>(self_v);
	return PyBool_FromLong(self->GetVar1());
}

int Example::pyattr_set_var1(void *self_v, const KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
{
	Example *self = static_cast<Example *>(self_v);
	const int param = PyObject_IsTrue(value);

	if (param == -1) {
		PyErr_Format(PyExc_AttributeError, "example.%s = bool: Example, expected True or False", attrdef->m_name);
		return PY_SET_ATTR_FAIL;
	}

	self->SetVar1(param);
	return PY_SET_ATTR_SUCCESS;
}

// Method with documentation and variable arguments.
KX_PYMETHODDEF_DOC(Example, func1, " func1(arg1, arg2)")
{
	char *arg1;
	int arg2 = 0;

	if (!PyArg_ParseTuple(args, "si:func1", &arg1, &arg2)) {
		return NULL;
	}

	// Do work.

	Py_RETURN_NONE;
}

// Method without documentation and variable arguments.
PyObject *Example::Pyfunc2(PyObject *args)
{
	char *arg1;
	int arg2 = 0;

	if (!PyArg_ParseTuple(args, "si:func2", &arg1, &arg2)) {
		return NULL;
	}

	// Do work.

	Py_RETURN_NONE;
}

// Method without documentation and variable arguments and keywords.
PyObject *Example::Pyfunc3(PyObject *args, PyObject *kwds)
{
	char *arg1;
	int arg2 = 0;

	const char *kwlist[] = {"arg1", "arg2", NULL};

	if (!PyArg_ParseTupleAndKeywords(args, kwds, "si:func3", const_cast<char **>(kwlist), &arg1, &arg2)) {
		return NULL;
	}

	// Do work.

	Py_RETURN_NONE;
}

// Method without documentation and only one argument.
PyObject *Example::Pyfunc4(PyObject *value)
{
	int arg2 = PyLong_AsLong(value);

	if (arg2 == -1 && PyErr_Occurred()) {
		return NULL;
	}

	// Do work.

	Py_RETURN_NONE;
}

// Method without documentation and without arguments.
PyObject *Example::Pyfunc5()
{
	// Do work.

	Py_RETURN_NONE;
}

#endif // WITH_PYTHON

Exceptions

All the user input in python must be checked to avoid error more deeper in the code, if an error is detected from the user input an exception is raised. There's two ways to raise an exception for python input: for attributes and methods. The only common point is the way to put the exception message. To put the message two functions are used:

PyErr_SetString: Set a simple message.

PyErr_SetString(PyExc_AttributeError, "gameOb.replacePhysicsShape(obj): function only available for objects with collisions enabled");

PyErr_Format: Set a message and formatting values.

PyErr_Format(PyExc_KeyError, "value = gameOb[key]: KX_GameObject, key \"%s\" does not exist", attr_str);

If the exception is raise in a function, it need to return NULL just after setting the message, and for attributes return PY_SET_ATTR_FAIL after the exception message.

Initialization

The python proxy class need to be initialized to be usable by python. To do this, KX_PythonInitTypes.cpp is written for the initialization. When a new class using a python proxy is added, you must add in the loop of the function initGameTypesPythonBinding:

PyType_Ready_Attr(dict, NewClass, init_getset);
Clone this wiki locally