1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
==============
How-Do-I Guide
==============
Working with Object and PyObject
================================
The CPython API is only to be invoked directly from within :file:`src/nrnpython`.
The way to support using Python from outside this folder without introducing a dependency on Python is to declare a function
pointer outside the folder, then have that function pointer be set by something inside e.g. :file:`src/nrnpython/nrnpy_hoc.cpp`.
All uses outside the folder must first check to see if the funciton pointer is not NULL; if it is NULL then Python is not
available. This is done for a number of functions in the ``nrnpy_hoc()`` function in :file:`src/nrnpython/nrnpy_hoc.cpp`.
To check if a NEURON object is wrapping a PyObject
--------------------------------------------------
To see if an Object* obj is wrapping a PyObject, check if
.. code-block:: c
obj->ctemplate->sym == nrnpy_pyobj_sym_
The variable ``nrnpy_pyobj_sym_`` here is a ``Symbol*`` and it is shared and available at least within ``nrnpython``.
(See object types below for more about detecting the type of an ``Object*``.)
To check if a PyObject is wrapping a NEURON object
--------------------------------------------------
Use ``PyObject_TypeCheck(po, hocobject_type)`` where ``po`` is the ``PyObject``.
To encapsulate a NEURON Object in a PyObject
--------------------------------------------
Use ``nrnpy_ho2po``. If the NEURON object is wrapping a ``PyObject`` it returns the original ``PyObject`` with the reference count increased by one.
Otherwise it returns a ``PyHocObject``. Note: this function does not convert NEURON strings or floats to ``PyObject`` instances as those are
not internally stored with ``Object`` instances. Those can be converted directly using the CPython API e.g. ``PyFloat_FromDouble``, ``PyLong_FromLong``.
To encapsule a PyObject in a NEURON Object
------------------------------------------
Use ``nrnpy_po2ho``. If the object is wrapping a NEURON Object, it increments the NEURON object's reference count by 1 and returns the original
NEURON object. Otherwise it returns a new NEURON Object* wrapping the ``PyObject`` and increments the ``PyObject`` reference count (remember, this
can be checked using the macro ``Py_REFCNT``). In particular, this function *always* returns a NEURON object, even for
strings and floats that would be more naturally represented in NEURON as their respective datatypes.
To check if a PyObject is a number
----------------------------------
Use ``nrnpy_numbercheck(po)`` where ``po`` is the ``PyObject*`` to check.
This is built-on but has modified semantics from the CPython API ``PyNumber_Check`` to detect things that NEURON cannot treat directly as numbers,
like complex numbers.
To reference a NEURON Object
----------------------------
Use ``hoc_obj_ref(obj)`` where ``obj`` is an ``Object*``. This is defined in :file:`src/oc/hoc_oop.c`. Include :file:`src/oc/oc_ansi.h` to define the function prototype.
To dereference a NEURON Object
------------------------------
Use ``hoc_obj_unref(obj)`` where ``obj`` is an ``Object*``. This is defined in :file:`src/oc/hoc_oop.c`. Include :file:`src/oc/oc_ansi.h` to define the function prototype.
Arguments
=========
Only positional arguments (and sec=) are allowed in NEURON *except* for functions with no HOC
equivalent that are handled separately.
Note: NEURON argument indices are always numbered from 1.
Check for the existence of an argument
--------------------------------------
Use ``ifarg(n)`` to check to see if there is an ``n``th argument (the arguments are numbered from 1).
Many existing functions and methods currently do not check for too many arguments, however doing
so is recommended as it indicates a probable user error.
Check the type of an argument
-----------------------------
Relevant functions include:
- ``hoc_is_object_arg(n)``
- ``hoc_is_pdouble_arg(n)``
- ``hoc_is_str_arg(n)``
- ``hoc_is_double_arg(n)``
Get the value of an argument
----------------------------
Relevant functions include:
- ``hoc_obj_getarg(n)``
May want to combine this with ``nrnpy_ho2po`` if you know the argument is a ``PyObject``; e.g.
.. code-block:: c
PyObject* obj = nrnpy_ho2po(*hoc_objgetarg(n))
- ``vector_arg(n)`` -- returns a ``Vect*``
- ``hoc_pgetarg(n)`` -- returns a ``double**``
- ``gargstr(n)``
- ``getarg(n)`` -- returns a ``double*``. Python bools, ints, and floats are all valid inputs.
Note: attempting to get the wrong type of an argument displays a "bad stack access" message and
a Python ``RuntimeError`` exception gets raised. If multiple types of arguments are possible,
you must check the type of the argument first.
Classes
=======
Declaring classes
-----------------
Classes are declared using the ``class2oc`` function, e.g.
.. code-block:: c
class2oc("ClassName", cons, destruct, members, NULL, retobj_members, NULL)
Here ``cons`` is the constructor, which must take an ``Object*`` and return a ``void*``.
``destruct`` is the destructor, which takes a ``void*`` and has no return.
``members`` is a null-terminated array of ``Member_func`` of methods that in Python could return float,
integer, or bool. In HOC, these all return doubles.
- To specify the return type as seen by Python, set ``hoc_return_type_code``. A value of 0 indicates
the function is returning a float; 1 indicates an integer; a value of 2 indicates a bool.
- Each of these methods must take a ``void*`` and return a double.
``retobj_methods`` is a null-terminated array of ``Member_ret_obj_func`` of methods that return objects.
(The actual functions implementing them take a ``void*`` and return an ``Object**``.)
Object types
------------
The type of every NEURON ``Object* obj`` is determined by it's ``ctemplate->sym``. This is a ``Symbol*``.
The pointers can be directly compared to see if two objects are of the same type. In particular,
a ``Symbol`` has a ``char*`` field ``name``. That is, to print the name of the type
that ``obj`` is an instance of, one can use:
.. code-block:: c
printf("The type of obj is: %s\n", obj->ctemplate->sym->name);
The ``hoc_lookup`` function takes a NEURON class name and returns the associated ``Symbol*``.
For example:
.. code-block:: c
Symbol* vector_sym = hoc_lookup("Vector");
NEURON provides internal convenience functions ``is_obj_type(Object* obj, const char* type_name)``
and ``check_obj_type(Object* obj, const char* type_name)`` that check to see if ``obj`` is of the
type specified by ``type_name``. The former returns a 1 (true) or 0 (false); the latter has no
return and raises an error if the type is wrong. These work by doing a ``strcmp``. If the
``Symbol*`` is known, it is more efficient to directly compare the ``Symbol*``.
For example, to see if ``obj`` is an instance of ``Vector`` and the ``Symbol*`` is not already
known, use ``is_obj_type(obj, "Vector")``.
These convenience functions are defined in ``src/oc/hoc_oop.cpp``.
HocCommand objects
==================
The ``HocCommand`` class, defined in ``src/ivoc/objcmd.cpp``, provides a consistent interface
for calling Python or HOC code. The constructor accepts a ``const char*`` for HOC, or a
``const char*`` and an ``Object*`` also for HOC (in this case the HOC string is executed in
the context of the object), or an ``Object*`` that wraps a Python callable.
Each ``HocCommand`` object has a ``pyobject()`` method that returns the underlying Python
object if any, else NULL. This can be used to distinguish between HOC and Python calls.
The ``execute()`` method runs the underlying HOC or Python code. No value is returned in
this case.
The ``func_call(int narg, int* perr)`` method returns a double from invoking the HOC or Python.
The value pointed to by ``perr`` is set to 1 if the ``HocCommand`` is to run Python but running
Python failed. Otherwise ``perr`` is unchanged. In particular, note that if ``perr`` originally
points to a 1, then it will still point to a 1 even upon success. The number of arguments is
indicated with ``narg``. The arguments themselves must have already been pushed onto NEURON's
stack, e.g. with ``pushx`` for doubles, ``hoc_push_object`` for ``Object*``, ``hoc_push_str``
for ``char**``, or ``hoc_pushpx`` for pointers to doubles (stack manipulation functions are
defined in ``src/oc/code.cpp``).
Miscellaneous tips
==================
Raising a NEURON error
----------------------
Use ``hoc_execerror`` which takes two ``char*`` arguments (which can be NULL). e.g.
.. code-block:: c
hoc_execerror("Message part 1", "Message part 2");
Note: all NEURON errors currently are received by Python as a ``RuntimeError`` exception, and all errors
print their error messages before returning to Python, meaning that they will always print out, even
inside a try/except block.
Checking if the name of an internal symbol
------------------------------------------
``hoc_table_lookup(name, hoc_built_in_symlist)`` returns NULL if ``name`` not in the symlist; otherwise
it returns the ``Symbol*``
|