Python Properties & Swig

后端 未结 6 895
日久生厌
日久生厌 2021-01-30 09:24

I am attempting to create python bindings for some C++ code using swig. I seem have run into a problem trying to create python properties from some accessor functions I have for

6条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-30 10:02

    Ooh, this is tricky (and fun). SWIG doesn't recognize this as an opportunity to generate @property: I imagine it'd be all too easy to slip up and recognize lots of false positives if it weren't done really carefully. However, since SWIG won't do it in generating C++, it's still entirely possible to do this in Python using a small metaclass.

    So, below, let's say we have a Math class that lets us set and get an integer variable named "pi". Then we can use this code:

    example.h

    #ifndef EXAMPLE_H
    #define EXAMPLE_H
    
    class Math {
     public:
        int pi() const {
            return this->_pi;
        }
    
        void pi(int pi) {
            this->_pi = pi;
        }
    
     private:
        int _pi;
    };
    
    #endif
    

    example.i

    %module example
    
    %{
        #define SWIG_FILE_WITH_INIT
        #include "example.h"
    %}
    
    [essentially example.h repeated again]
    

    example.cpp

    #include "example.h"
    

    util.py

    class PropertyVoodoo(type):
        """A metaclass. Initializes when the *class* is initialized, not
        the object. Therefore, we are free to muck around the class
        methods and, specifically, descriptors."""
    
        def __init__(cls, *a):
            # OK, so the list of C++ properties using the style described
            # in the OP is stored in a __properties__ magic variable on
            # the class.
            for prop in cls.__properties__:
    
                # Get accessor.
                def fget(self):
                    # Get the SWIG class using super. We have to use super
                    # because the only information we're working off of is
                    # the class object itself (cls). This is not the most
                    # robust way of doing things but works when the SWIG
                    # class is the only superclass.
                    s = super(cls, self)
    
                    # Now get the C++ method and call its operator().
                    return getattr(s, prop)()
    
                # Set accessor.
                def fset(self, value):
                    # Same as above.
                    s = super(cls, self)
    
                    # Call its overloaded operator(int value) to set it.
                    return getattr(s, prop)(value)
    
                # Properties in Python are descriptors, which are in turn
                # static variables on the class. So, here we create the
                # static variable and set it to the property.
                setattr(cls, prop, property(fget=fget, fset=fset))
    
            # type() needs the additional arguments we didn't use to do
            # inheritance. (Parent classes are passed in as arguments as
            # part of the metaclass protocol.) Usually a = [] right now.
            super(PropertyVoodoo, cls).__init__(*a)
    
            # One more piece of work: SWIG selfishly overrides
            # __setattr__. Normal Python classes use object.__setattr__,
            # so that's what we use here. It's not really important whose
            # __setattr__ we use as long as we skip the SWIG class in the
            # inheritance chain because SWIG's __setattr__ will skip the
            # property we just created.
            def __setattr__(self, name, value):
                # Only do this for the properties listed.
                if name in cls.__properties__:
                    object.__setattr__(self, name, value)
                else:
                    # Same as above.
                    s = super(cls, self)
    
                    s.__setattr__(name, value)
    
            # Note that __setattr__ is supposed to be an instance method,
            # hence the self. Simply assigning it to the class attribute
            # will ensure it's an instance method; that is, it will *not*
            # turn into a static/classmethod magically.
            cls.__setattr__ = __setattr__
    

    somefile.py

    import example
    from util import PropertyVoodoo
    
    class Math(example.Math):
        __properties__ = ['pi']
        __metaclass__  = PropertyVoodoo
    
    m = Math()
    print m.pi
    m.pi = 1024
    print m.pi
    m.pi = 10000
    print m.pi
    

    So the end result is just that you have to create a wrapper class for every SWIG Python class and then type two lines: one to mark which methods should be converted in properties and one to bring in the metaclass.

提交回复
热议问题