Context Interface
pykx.ctx
The context interface has been temporarily disabled for pykx.QConnection
instances.
Due to the discovery of issues with the use of the context interface over IPC, it has been temporarily disabled to prevent misleading behaviour and unexpected errors from occuring. It will be re-enabled in a future version of PyKX.
Interface to q contexts and scripts which define a namespace.
The context interface provides an easy to way access q contexts (also known as namespaces when at the top level). For more information about contexts/namespaces in q please refer to Chapter 12 of Q for Mortals.
Both the local q instance at pykx.q
, and pykx.Connection
instances, have attributes for q
namespaces, which are exposed in Python as pykx.QContext
objects. These context objects have
attributes for their members, which can either be sub-contexts or K objects. For example:
pykx.q.Q
is aKdbContext
instance for the builtin.Q
context/namespacepykx.q.ctxA.ctxB
is aKdbContext
instance for the.ctxA.ctxB
contextpykx.q.ctxA.ctxB.kObject
is apykx.K
instance for the.ctxA.ctxB.kObject
K object
Just as in q, the .q
context is accessible at the top-level, so for instance instead of accessing
pykx.q.q.md5
, you can access it as pykx.q.md5
. Some q builtins cannot be accessed like this
such as or
and not
as these result in Python syntax errors. Such functions can instead be
accessed either with getattr
, or by evaluating them as q code (e.g. pykx.q('not')
).
Accessing attributes of pykx.q
(or a pykx.ipc.Connection
instance) which do not correspond to
a context that has been loaded in memory in q will result in it
trying to find a script with a matching name. This process is detailed in
the flowchart below:
graph LR
A([`pykx.q.ctxA`<br>is accessed.]);
B{Is `.ctxA` <br>defined in<br>memory in q?};
C{Is `.ctxA` <br>defined in<br>memory in q?};
D["Search for a<br>matching script<br>(details below)."];
E{Has a<br>matching script<br>been found?};
F([`AttributeError`<br>is raised.]);
G([`QContext`<br>is returned.]);
H["Switch to the<br>context `.ctxA`<br>(`system "d .ctxA"`),
<br>execute the script,<br>then switch back."];
A --> B;
B --No--> D;
B --Yes--> G;
C --No--> F;
C --Yes--> G;
E --No--> F;
E --Yes--> H;
D --> E;
H --> C;
The fact that the context might not be defined even after the context interface changes the context and executes the script might be confusing. This can happen because the script can switch into other contexts, which overrides the context switch done by the context interface. Additionally the script might use fully qualified names for its definitions, which can bypass the effect of switching contexts.
Note that context switches persists across pykx.q
calls (but not pykx.QConnection(...)
calls).
One should take care when switching contexts, as unexpectedly being in an different context can
result in undesirable behaviour. QContext
objects are Python context managers, which means they
can be used with the with
statement like so:
# q code here executes in the global context
with q.myctx:
# q code here executes in the `.myctx` context
pass
# q code here executes in the global context
If you would like to switch into a q context using a string for the context name, use getattr
like so:
# q code here executes in the global context
with getattr(q, 'myctx'):
# q code here executes in the `.myctx` context
pass
# q code here executes in the global context
Script Search Logic
When the context interface cannot find a namespace (i.e. a top-level context) that is being
accessed it attempts to find a q/k script that has a matching name. This process is done via a
depth first search of a tree where each node corresponds to part of the path, and each leaf
corresponds to a possible file. Only the first file found that exists is executed. If none of the
files exist then an AttributeError
is raised.
The layers of the tree are as follows:
- Each of the paths in
pykx.q.paths
/pykx.ipc.Connection(...).paths
(which defaults topykx.ctx.default_paths
) .
prefix or not- The name of the attribute accessed (i.e.
pykx.q.script
->script
) .q
or.k
- No trailing
_
or a trailing_
(n.b. why a q/k script path would end with an underscore)
So for example if pykx.q.script
was accessed, the context .script
was not defined in memory in
q, and paths
was set to ['.', pykx.QHOME]
(where pykx.QHOME == pathlib.Path('/opt/kdb')
), then the following paths would be checked
in order until one is found to exist, or they have all been checked:
./.script.q
./.script.q_
./.script.k
./.script.k_
./script.q
./script.q_
./script.k
./script.k_
/opt/kdb/.script.q
/opt/kdb/.script.q_
/opt/kdb/.script.k
/opt/kdb/.script.k_
/opt/kdb/script.q
/opt/kdb/script.q_
/opt/kdb/script.k
/opt/kdb/script.k_
Best Practices
To take full advantage of the automatic script loading one should ensure that every q/k script
defines at most one public context. Ideally every q/k script should define exactly one context,
and the name of the context should be equivalent to the name of the file without the file
extension. For instance, script.q
should place its definitions within the .script
namespace.
This ensures that when the context interface executes a script to load a context, it doesn't load
in more contexts than intended. Furthermore the context name matching the file name ensures that
when that file is executed because its name matches the desired context, that context will actually
be defined.
When these best practices cannot be followed it may be impossible to use the automatic loading of
scripts via the context interface. In that case we can resort to manually loading scripts either
by executing the q code system "l <path to script>"
, or by calling pykx.q._register
with the
path to the script.
When switching contexts within a script, one should always save the context they were in prior to their context switch, and then switch back into it afterwards, rather than explicitly switching into the global context.
Execution Contexts for Functions
Functions returned by the context interface are provided as pykx.SymbolicFunction
instances.
These objects are symbol atoms whose symbol is a named function (with a fully-qualified name). They
can be called like regular pykx.Function
objects, but unlike regular pykx.Function
objects, they will execute in the pykx.Q
instance (also known as its "execution context") in
which it was defined.
Refer to the IPC docs around execution contexts for more information.
CurrentDirectory
CurrentDirectory()
QContext
QContext(q, name, parent, no_ctx=False)
Interface to a q context.
Members of the context be accessed as if the QContext
object was a dictionary, or by
dotting off of the QContext
object.
q: The q instance in which the context exists.
name: The name of the context.
parent: The parent context as a `QContext`, or `None` in the case of the global context.
_invalidate_cache
_invalidate_cache()
Clears the cached context, forcing it to be reloaded the next time it is accessed.
ZContext
ZContext(global_context)
Bases: QContext
Special interface to handle the .z context.
The .z context in q is not a normal context; it lacks a dictionary. To access it one must access its attributes directly.
_fully_qualified_name
_fully_qualified_name(name, parent)
Constructs the fully qualified name of a context given its name and parent.