Welcome to Python-flavored Magnum! Please note that, while already being rather stable, this functionality is still considered experimental and some APIs might get changed without preserving full backwards compatibility.

Python API conventions

Basic rules and good practices for both Python binding developers and users.

API naming

  • mapping is made as clear as possible, the user should not need assistance to know what’s the corresponding name in Python
  • modules lowercase, words not separated with underscores (which means C++ namespaces have to be named clearly and tersely to make this possible)
  • class names CamelCase
  • function names snake_case
  • constants and enums UPPERCASE, again underscores omitted if it doesn’t hurt readability

Preprocessor definitions

Exposed to Python as plain boolean constants, and only those that actually are useful in a Python setting.

C++ Python
CORRADE_BUILD_MULTITHREADED corrade.BUILD_MULTITHREADED
MAGNUM_TARGET_GLES magnum.TARGET_GLES

Namespaces / modules

C++ Python
Magnum::Math magnum.math
Magnum::SceneGraph magnum.scenegraph

Classes

C++ Python
Vector2i Vector2i
GL::Buffer gl.Buffer

Functions

C++ Python
Math::angle() math.angle()
Vector2::xAxis() Vector2.x_axis()
v.isZero() v.is_zero()
m.transformVector(a) m.transform_vector(a)

Enums

Custom construction helpers for enums are converted to functions directly on the Python enum class.

C++ Python
PixelFormat::RGB8Unorm PixelFormat.RGB8_UNORM
MeshPrimitive::TriangleStrip MeshPrimitive.TRIANGLE_STRIP
Trade::meshAttributeCustom() trade.MeshAttribute.CUSTOM()
Trade::isMeshAttributeCustom() trade.MeshAttribute.is_custom

Enum sets

Compared to C++, there’s just one enum type with a plural name, and it contains both the values and binary operators. Additionally, there’s an explicit NONE value for an empty set.

C++ Python
Trade::DataFlag::Mutable trade.DataFlags.MUTABLE
Trade::DataFlags{} trade.DataFlags.NONE

Constants

Apart from Math::Constants, which are exposed directly as members of the magnum.math submodule to mimic Python’s math, most of the constants used throughout the C++ API are related to templates. Those are, where applicable, converted to Python builtins such as len().

C++ Python
Constants::pi() math.pi
Math::Vector::Size len(vec)

Initialization tags

Since overloading based on argument types is not a common thing to do in Python (and it adds extra overhead in pybind11), all initialization tags are converted to static constructors instead:

Matrix4 a{Math::IdentityInit, 5.0f};
GL::Buffer b{NoCreate};

C++

a = Matrix4.identity_init(5.0)
b = gl.Buffer.no_create()

Python

There’s no equivalent for the Math::NoInit tag, as such optimization doesn’t make much sense when instances are copied back and forth between C++ and Python. Similarly, the NoCreate tag makes sense only in C++ which differentiates between stack-allocated and heap-allocated instances. In Python it’s enough to simply set an instance to None to achieve the same effect.

Name import conventions

Similarly to C++, where it’s encouraged to do something like

namespace YourProject {
    using namespace Magnum;
}

and then use Magnum C++ APIs unprefixed from inside that namespace, the recommended Python workflow is similar. Note that importing the root module does not import submodules, so you are expected to import those on an as-needed basis as well.

from magnum import *
from magnum import gl, platform

In particular, both the C++ and the Python API is designed in a way to prevent too generic or confusing names in the root namespace / module and also keeping it relatively clean and small, without too many symbols. On the other hand, the subnamespaces do have generic names. The GL::version() / gl.version() API is one example — it’s tucked in a subnamespace so the generic name isn’t a problem, but you wouldn’t find anything of similar genericity in the root namespace / module.

An exception to this rule is exposed preprocessor definitions — these are not pulled in when doing from magnum import * as this would likely cause conflicts (in particular, BUILD_STATIC is defined by Corrade as well). Instead, you have to access them like this:

import magnum

if magnum.TARGET_GLES2:
    format = gl.TextureFormat.RGBA8
else:
    format = gl.TextureFormat.R8

Handling of alternate implementations

C++ APIs that have alternative implementations (such as Platform::Sdl2Application vs. Platform::GlfwApplication, or SceneGraph::MatrixTransformation3D vs. SceneGraph::TranslationRotationScalingTransformation3D) either provide typedefs based on what header you include or require you to typedef them yourselves:

class MyApplication: Platform::Application {}; // depends on what you include

typedef SceneGraph::Object<SceneGraph::MatrixTransformation3D> Object3D;

In Python, the alternate implementations are tucked in submodules (such as platform.sdl2 vs. platform.glfw, or scenegraph.matrix vs. scenegraph.trs), each submodule providing the same names (such as Application or Object3D) and the designed way to use them is via from ... import:

from magnum.platform.sdl2 import Application
from magnum.scenegraph.trs import Scene3D, Object3D

Basic guarantees

  • All types printable using Utility::Debug implement __repr__() on the Python side, producing the exact same output.