Accessing ArcObjects from Python?

Download and install comtypes*, put the Snippets module from Mark Cederholm in PYTHONPATH, and you're all set.

from snippets102 import GetLibPath, InitStandalone
from comtypes.client import GetModule, CreateObject
m = GetModule(GetLibPath() + "esriGeometry.olb")
InitStandalone()
p = CreateObject(m.Point, interface=m.IPoint)
p.PutCoords(2,3)
print p.X, p.Y

For background see Mark Cederholm's presentations for UberPyGeeks on "Using ArcObjects in Python". There are separate ones for VBA and C++ developer perspectives. They use Visual Studio (yes Express is ok) and the Windows SDK, but these aren't required, just ArcGIS, Python and comtypes are sufficient.

Getting the Snippets module

  • 9.3, 9.4 - http://pierssen.com/arcgis/upload/misc/python_arcobjects.zip
  • 10.0 - http://www.pierssen.com/arcgis10/upload/python/snippets100.py
  • 10.1 - http://www.pierssen.com/arcgis10/upload/python/snippets101.py
  • 10.2 - http://www.pierssen.com/arcgis10/upload/python/snippets102.py

* Note for 10.1+ you need to make a small change to automation.py in the comtypes module. See ArcObjects + comtypes at 10.1.


Next steps

...or: brain gone snaky? Looking at the c# code examples makes your eyes swim, and try as you might you just can't think like a crane? Look here:

  • Guidelines for using ArcObjects from Python
  • Getting started with ArcObjects?
  • ArcObjects Resources

Yes, Mark Cederholm's presentation that Matt Wilkie mentions above is a great place to start. The recipe/code that Matt presents is certainly a slick and probably the best way to go about things. I wanted to mention, though, the rather brute force method I am using in ArcGIS 10.0. I have several automation scripts (standalone, outside the application boundary) that I run this way, and they work just fine. If maximum speed is a concern, though, you might just go with Matt's solution and be done with it.

I use the comtypes package to force wrapping of all ArcObjects libraries (.olb). Then Python has access to all of ArcObjects. I got the wrapping code from Frank Perks through an ESRI forum posting. I had my own code that essentially did the same thing, but it was bloated and merely functional, while his is much prettier. So:

import sys, os
if '[path to your Python script/module directory]' not in sys.path:
    sys.path.append('[path to your Python script/module directory]')

import comtypes
#force wrapping of all ArcObjects libraries (OLBs)
import comtypes.client
# change com_dir to whatever it is for you
com_dir = r'C:\Program Files (x86)\ArcGIS\Desktop10.0\com'
coms = [os.path.join(com_dir, x) for x in os.listdir(com_dir) if os.path.splitext(x)[1].upper() == '.OLB']
map(comtypes.client.GetModule, coms)

Then, pretty much straight out of Mark Cederholm's presentation:

import comtypes.gen.esriFramework

pApp = GetApp()

def GetApp():
    """Get a hook into the current session of ArcMap"""
    pAppROT = NewObj(esriFramework.AppROT, esriFramework.IAppROT)
    iCount = pAppROT.Count

    if iCount == 0:
        print 'No ArcGIS application currently running.  Terminating ...'
        return None
    for i in range(iCount):
        pApp = pAppROT.Item(i)  #returns IApplication on AppRef
        if pApp.Name == 'ArcMap':
            return pApp
    print 'No ArcMap session is running at this time.'
    return None

def NewObj(MyClass, MyInterface):
    """Creates a new comtypes POINTER object where\n\
    MyClass is the class to be instantiated,\n\
    MyInterface is the interface to be assigned"""
    from comtypes.client import CreateObject
    try:
        ptr = CreateObject(MyClass, interface=MyInterface)
        return ptr
    except:
        return None

That's it. You should have full access to ArcObjects starting with the pApp object that is IApplication on the AppRef object. In my experience, the wrapping of the ArcObjects libraries on the first run isn't objectionably slow, and for subsequent runs the wrapping doesn't happen. The libraries are already wrapped and compiled, so things are much speedier.

Added: There is a large caution that comes with this. The NewObj function given here assumes the Python script is being run in-process. If not, this function will create objects in the Python process (ie. out-of process), and object references will be wrong. To create in process objects from an external Python script you should use IObjectFactory. See Kirk Kuykendall's comments and tips in this stackexchange post for more info.


How do I access arcobjects from python?

If what you are looking for is specific functionality that exists and is in the C++ Arcobjects code, then your best bet would be to create C++ methods to call them.... and then create a python wrapper to access those C++ methods.

There are quite a few ways to access C++ methods from python, and lots of people that do so use a tool like SWIG to auto-generate the python classes from the C++ method signatures. It's been my experience that these autogenerated APIs get pretty nasty when passing non-native C++ types (int, floats) and are never very 'pythonic'.

My recommended solution would be to use the ctypes API. A great tutorial is here: http://python.net/crew/theller/ctypes/tutorial.html

The basic steps are:

  1. write some of your core logic in C++, the ones where you believe python performance might be an issue
  2. Compile that core logic (in this case using the ArcObject C++ API method calls) from object files into a shared (.so) or dynamic library (.dll) using whatever system compiler (nmake,make, etc.)
  3. Write a ctypes mapping between the python class and the C++ method signature [SWIG tries to automate this, but it's easy, even if using crazy object types]
  4. Import the compiled library into your python program and use the bound class/method bindings like any other python class!

This is probably more general way to reference C/C++ code from within python, it is probably going to be much simpler in the long run if you don't have to deal with COM objects. It also will allow all of the system specific functionality reside in the compilation of the linked library object (so the python will not be system/python implementation specific).