Python
The page reviews the Python bindings for OpenCog.
Build Requirements
- Python3
- Cython3 - The AtomSpace python bindings are written using Cython, http://www.cython.org/
- Nosetests3 - Running python unit tests, to see if the "smell bad".
Both Cython3 and Nosetests3 can be installed with easy_install:
sudo pip install cython3 nose3
Currently, the package structure from the atomspace installation looks like this:
opencog.atomspace opencog.execute opencog.logger opencog.scheme_wrapper opencog.type_constructors opencog.utilities
Additional modules can be installed from different repositories, such as the sensory or lg-atomese. These all follow the same pattern:
opencog.sensory opencog.lg_atomese
Tutorial
This tutorial is a first look at the Python bindings. It assumes that you've got a good grasp on the concept of the AtomSpace and the CogServer. Oh, and it helps to know a bit of Python too!
Setting up
Go through the normal process of building OpenCog. Ensure that when you run cmake from the atomspace build dir, the Cython bindings component is built:
The following components will be built: ----------------------------------------------- ... Python bindings - Python (cython) bindings. Python tests - Python bindings nose tests. ...
Python Shell
The python bindings let you interact and instantiate Atoms interactively. The IPython shell is recommended.
sudo pip install ipython[notebook]
Then run IPython Qt Console:
ipython qtconsole
Atoms
Here's how to add a Node Atom:
>>> from opencog.atomspace import AtomSpace, types
>>> atomspace = AtomSpace()
>>> atomspace.add_node(types.ConceptNode, "My first python created node")
(ConceptNode "My first python created node") ; [2][1]
If you get this error:
ImportError Traceback (most recent call last) <ipython-input-1-b2c511b54a3c> in <module>() ----> 1 from opencog.atomspace import AtomSpace, types ImportError: No module named opencog.atomspace
then either you haven't built OpenCog with Cython bindings, or your PYTHONPATH is not correct. Please refer to Setting Up above.
If you are going to use Python functions in callbacks for GroundedSchemaNode or GroundedPredicateNode when running OpenCog from Python, you need to initialize the OpenCog Python system so it has a reference to the AtomSpace object and so it can initialize internal Python callback state. Like:
>>> from opencog.utilities import set_default_atomspace
>>> set_default_atomspace(atomspace)
Once you have initialized Python above, you can use an even more compact method for creating atoms. In parallel with the helper functions in Scheme, the Python module type_constructors defines helper functions with the same name as the underlying type. These functions are auto-generated during the make process. So you can write::
>>> from opencog.type_constructors import *
>>> ConceptNode("Ah, more concise")
(ConceptNode "Ah, more concise") ; [3][1]
You'll notice these return opencog.atomspace.Atom objects, which internally store a reference to the AtomSpace it's connected to:
>>> atom = ConceptNode("handle bar")
>>> atom.atomspace
<opencog.atomspace.AtomSpace object at 0x203220a>
The AtomSpace attached to each Atom is the one that is passed into the initialize_opencog function.
Here are some more functions you can use to get information about specific atoms:
>>> str(atom)
'(ConceptNode "handle bar")\n'
>>> atom.long_string()
'(ConceptNode "handle bar") ; [4][1]\n'
>>> atom.name
u'handle bar'
>>> atom.type
3
>>> atom.type_name
'ConceptNode'
There are over 150 different Atom types in use, including custom types for chemistry, genetics, natural language and other uses. The sheer variety of them can be overwhelming. Stick to the basics, until you need something fancier. An alphabetical index can be found in the Category:Atom Types.
Atoms are immutable; only the association to Values (such as FloatValue) are mutable.
For example, the name of an Atom cannot be directly changed; Atoms can only be created and destroyed. Attempting to change the name will throw an AttributeError exception:
>>> atom.name = 'change your name man, it sucks.'
AttributeError: attribute 'name' of 'opencog.atomspace.Atom' objects is not writable
Links
Lets create our first Link:
>>> node1 = ConceptNode("I can refer to this atom now")
>>> node2 = ConceptNode("this one too")
>>> link = atomspace.add_link(types.SimilarityLink, [node1,node2])
>>> link.out
[(ConceptNode "I can refer to this atom now") ; [5][1]
, (ConceptNode "this one too") ; [6][1]
]
The Python module: type_constructors, also defines construction functions for links, so the above may be written using the more compact:
>>> node1 = ConceptNode("I can refer to this atom now")
>>> node2 = ConceptNode("this one too")
>>> link = SimilarityLink(node1, node2)
or the even more compact:
>>> link = SimilarityLink(ConceptNode("I can refer to this atom now"), ConceptNode("this one too"))
In Python files, where readability is important, you can use indentation to show the relationships like:
link = SimilarityLink(
ConceptNode("I can refer to this atom now"),
ConceptNode("this one too")
)
this is especially helpful when constructing more complicated atom sequences.
Finding Atoms
There are several ways of finding Atoms. The below illustrates finding Atoms by Type. There is also a very powerful query engine, which allows arbitrary templates to be matched. Such queries are themselves written in Atomese, and so no special Python bindings are needed to get access to this function. The StorageNode system provides for a way for find Atoms stored on disk, or in a database, or located on a remote network host. See StorageNode for documentation.
The below shows how to iterate over all the ConceptNodes that we've added so far.
>>> my_nodes = atomspace.get_atoms_by_type(types.ConceptNode)
>>> my_nodes
[(ConceptNode "another new concept") ; [9][1]
, (ConceptNode "a new concept") ; [8][1]
, (ConceptNode "this one too") ; [6][1]
, (ConceptNode "I can refer to this atom now") ; [5][1]
, (ConceptNode "handle bar") ; [4][1]
, (ConceptNode "Ah, more concise") ; [3][1]
, (ConceptNode "My first python created node") ; [2][1]
]
>>> print(my_nodes[3])
(ConceptNode "I can refer to this atom now") ; [5][1]
By default, subtypes of the type specified are also retrieved, but we can exclude subtypes if we want to be specific.
>>> Node("I am the one true Node")
>>> my_specific_nodes = atomspace.get_atoms_by_type(types.Node, subtype=False)
>>> my_specific_nodes
[(Node "I am the one true Node") ; [11][1]
]
There are other queries by type, outgoing set, name etc. See test_atomspace.py for the complete set of functions .
The AtomSpace as a container
The AtomSpace supports some container methods Python expects for a container-like object:
>>> link in atomspace # is this atom in this atomspace
True
>>> len(atomspace) # how many atoms are in the atomspace?
8
Values
TODO: Document how to use Values, in general.
Invoking Scheme code in Python
You can use the Scheme wrapper to evaluate Scheme code in Python when there are functions or examples written in Scheme which you want to execute.
To use the Scheme wrapper, import the scheme_eval function, then call it:
from opencog.scheme import scheme_eval
scheme_eval(atomspace, "(+ 1 1)")
The scheme_eval function has two arguments, the first is the atomspace to manipulate, the second is the scheme code to execute. The scheme_eval function will return the string output of the scheme evaluator (so, just "2" in the example above).
The scheme_eval_h function is similar, except that it returns the Atom (assuming the scheme code returns Atoms). Likewise, scheme_eval_v returns Values and scheme_eval_as returns AtomSpaces. In all three cases, the Atoms, Values and AtomSpaces are converted to python-native format, and so can be accessed directly with python.
Here is an example using the scheme_eval_h function to execute the Scheme function cog-execute!.
from opencog.utilities import initialize_opencog
from opencog.scheme import scheme_eval_h
from opencog.type_constructors import *
atomspace = AtomSpace()
initialize_opencog(atomspace)
scheme_eval(atomspace, "(use-modules (opencog) (opencog exec))")
def add_link(atom1, atom2):
return ListLink(atom1, atom2)
scheme_code = \
"""
(cog-execute!
(ExecutionOutputLink
(GroundedSchemaNode \"py: add_link\")
(ListLink
(ConceptNode \"one\")
(ConceptNode \"two\")
)
)
)
"""
scheme_eval_h(atomspace, scheme_code)
One can write the equivalent Atomese purely in python, since there is a Python binding equivalent to cog-execute! called execute_atom. So the above could also be written as:
from opencog.utilities import initialize_opencog
from opencog.bindlink import execute_atom
from opencog.type_constructors import *
atomspace = AtomSpace()
initialize_opencog(atomspace)
def add_link(atom1, atom2):
return ListLink(atom1, atom2)
execute_atom( atomspace,
ExecutionOutputLink(
GroundedSchemaNode("py: add_link"),
ListLink(
ConceptNode("one"),
ConceptNode("two")
)
)
)
To learn more about Scheme wrapper functions, you can read the code in opencog/cython/opencog/scheme.pyx.
Invoking Python code in Scheme
The converse operation is also possible: you can run python snippets from scheme.
(use-modules (opencog) (opencog python))
(python-eval "print 'ten-four big daddy'")
(python-eval "try:\n do_stuff()\nexcept NameError:\n print 'Oh no, Mr. Bill!'\n")
(python-eval "def do_stuff():\n print 'Roger Wilco'\n")
(python-eval "do_stuff()")
Performance
The performance of the python bindings can be measured with the python benchmark tool, located at https://github.com/opencog/benchmark/python/benchmark.py directory. The https://github.com/opencog/benchmark/python/python_diary.txt file there summarizes current benchmark results.
Improving the bindings
Bugs, fixes and enhancements to the Python bindings can be accomplished by working directly with the cython source code in opencog/cython/opencog. The cython .pxd files are definition files, which expose the C++ classes to cython. The actual implementation is in the .pyx files.
Cython documentation can be found on the Cython website,