Using Python Objects

From Valve Developer Community
Jump to navigation Jump to search
Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Dead End - Icon.png
This article has no Wikipedia icon links to other VDC articles. Please help improve this article by adding links Wikipedia icon that are relevant to the context within the existing text.
January 2024

Most of the time to get any thing useful in python you will need to be able to get a python object as a c++ pointer so the rest of your code can call into python with out knowing its doing so. This tutorial will take you through the steps needed to do that. It also assumes you have completed the adding python tutorial.

The Interface

The first thing we need to do is define an interface for our class. The reason we do this is so that the non python aware code can use it like a normal c++ class with out having to include the python files. In the example here we going to have a couple of functions that pass strings around.


class pythExampleI
{
public:
	const char* getMsg()=0;
	void setMsg(const char*)=0;
	void sayHallo()=0;
};

Wrapping The Interface

The next thing we need to do is wrap it so that the python functions will get called. In a new file start making the wrapper class.


////////////////////////////////////////////////////////////////////////////
// H
////////////////////////////////////////////////////////////////////////////

class pythExample : public pyExampleI, public bp::wrapper<pythExample>
{
public:
	pythExample();

	void setMsg(const char* msg);
	const char* getMsg();
	void sayHallo();
}

////////////////////////////////////////////////////////////////////////////
// CPP
////////////////////////////////////////////////////////////////////////////

#define TRYFUNCRET( call, def ) try{ return call; }catch(...){HandlePythonException();return def;}
#define TRYFUNC( call ) try{ call; }catch(...){HandlePythonException();}

pythExample::pythExample()
{
}

void pythExample::setMsg(const char* msg)
{
	TRYFUNC( this->get_override("setMsg")(msg));
}

virtual const char* pythExample::getMsg()
{
	TRYFUNCRET( this->get_override("getMsg")(), "NULL");
}

void pythExample::sayHallo()
{
	TRYFUNC( this->get_override("sayHallo")());
}

The reason we use the try macro is that python function calls might throw an exception and thus we need to catch it and handle it. The function HandlePythonException prints the exception out as a warning to the console in game.

Class Python Define

Now we need to define the python interface for our class. More info on this can be found in the boost.python docs.

BOOST_PYTHON_MODULE(HLExample)
{
	 bp::class_<pythExample>("pythExample")
		.def("setMsg", &pythExample::setMsg)
		.def("getMsg", &pythExample::getMsg)
		.def("sayHallo", &pythExample::sayHallo);
}

Note: Make sure you add the HLExample to ge_pymodules.cpp other wise python wont be able to load it.

Extending The Class in Python

Most of the c++ work is done and now we can have a fun coding our python. For this example im going to just have some basic functionality.

Make a new file called example.py in the mod/scripts/python folder.

import HLExample

class MyPyClass(HLExample.pythExample):
	def __init__(self):
		super(MyPyClass, self).__init__()

		self.string = "My Python Class!"
		
	def setMsg(self, inString):
		self.string = inString
			
	def getMsg(self):
		return self.string
		
	def sayHallo(self):
		print "Hallo!\n"


The last thing we need to add to the python file is a function that allows us to return a new object to the c++ side of the code.

def NewPythonClass():
	return MyPyClass()

Putting it all Together

Now that we have done all the leg work we can actually start using the python script. To the MyPyHanel (from adding python tutorial) change the init function to look like this:

void CMYPyHandle::Init()
{
	try
	{
		ExecFile("example.py");
		m_poPYClass = bp::extract<pythExample*>(GetDict()["NewPythonClass"]());
	}
	catch (...)
	{
		HandlePythonException();
	}
}

Add a new function to get a c++ pointer to our class:

pyExampleI* CMYPyHandle::GetMyClass()
{
	return dynamic_cast<pyExampleI*>(m_poPYClass);
}

And finally this to the header file:

public:
	pyExampleI* GetMyClass();

private:
	pythExample* m_poPYClass;

Using the Python Class

Once the above is complete we can actually use it.

#include "ge_pytest.h"

CONCOMMAND(python_test, "Test out python!")
{
	pyExampleI* pyClass = GetPyHandle()->GetMyClass();
	
	pyClass->sayHallo();
	
	Msg(pyClass->getMsg());
	Msg("\n");
	
	pyClass->setMsg("This is a test Message!");
	
	Msg(pyClass->getMsg());
	Msg("\n");
}

Once in game in a loaded game server, open the console up and type in "python_test" with out the quotes and you should see:

Hallo!
My Python Class!
This is a test Message!

Notes

Check out python tips and tricks with pointers of using python in c++.

Example code for this is contained in the second release of the package for adding python tutorial.

  • ge_pyexampletest.cpp
  • ge_pyexample.cpp
  • ge_pyexample.h
  • ge_class.h
  • ge_pyclass.cpp
  • ge_pyclass.h