10

I am writing a Python wrapper class for a C# API accessed through Pythonnet. As I want to extend the API with my own methods I decided to wrap it using the composition approach outlined here:

The C# API makes heavy use of properties which I want to mimic in my Python code. The following minimal example shows my current approach for the example of the C# Surface class with the two properties width and height:

class MySurface:
    def __init__(api_surface):
        self.api_surface = api_surface
    
    @property
    def width(self):
        return self.api_surface.width

    @width.setter
    def width(self, value):
        self.api_surface.width = value

    @property
    def height(self):
        return self.api_surface.height

    @height.setter
    def height(self, value):
        self.api_surface.height = value

In total, I have to deal with about 50 properties. For several groups of properties I want to add my own error checks, type conversions, etc. What I am looking for is a Pythonic way of defining the properties, e.g. through a factory or using descriptors. Thanks for your help!

Edit: I want to be able to use tab completion within a python shell, i.e. surface. {hit tab} should propose surface.width and surface.height. This does not seem to be possible with the getattr approach outlined by Greg.

Community
  • 1
  • 1
Olaf
  • 371
  • 2
  • 11

2 Answers2

14

I was able to solve the issue using the following property factory:

def surface_property(api_property_name, docstring=None):
    def getter(self):
        return self.api_surface.__getattribute__(api_property_name)

    def setter(self, value):
        self.api_surface.__setattr__(api_property_name, value)

    return property(getter, setter, doc=docstring)

With this function the class definition reduces to:

class MySurface:
    def __init__(api_surface):
        self.api_surface = api_surface

    width = surface_property('Width','Get and set the width.')
    height = surface_property('height', 'Get and set the height.')
Olaf
  • 371
  • 2
  • 11
  • This is a really good example of an alternative composition pattern in Python (rather than using `__getattr__` and `__setattr__` to delegate attribute lookups to a member). – Rick May 10 '17 at 14:03
  • Is there any way that the docstring is copied from the property in api_surface? – Daniel Arteaga Dec 01 '21 at 17:51
  • If you're trying to add a property to a class _outside of the class after the class has been defined_, you can do it but you have to add it to the class, not to an instance. See https://stackoverflow.com/a/1355444/. Use a factory like this, or ```property``` with ```lambda```s like in that answer. Because the getters and setters still take the `self` argument, there's not much to change here; you can just do ```MySurface.width = surface_property('Width','Get and set the width.')``` and so on. – Attila the Fun Jul 27 '23 at 14:49
1

You can just use getattr and setattr if you want to avoid all the manual coding. This answer will work for python2 btw.

class MySurface(object):
    def __init__(self):
        self.props = {"width": 0, "length": 0, ...}

    def __setattr__(self, attr, val):
        if attr in self.props:
            self.props[attr] = val
        else:
            super(MySurface, self).__setattr__(attr, val)

    def __getattr__(self, attr):
        if attr in self.props:
           return self.props[attr]
        else:
           return self.__getattribute__(attr)
Greg
  • 5,422
  • 1
  • 27
  • 32
  • I already experimented with a similar approach. One of my main goals is to have appropriate code completion (tab completion) when using the wrapped class in an interactive Python session. This does not seem to work with the outlined approach where the user has to know the attibute name in advance. – Olaf Apr 12 '16 at 18:39
  • should probably include that in your question. I can't think of much more you can do besides manually defining all your properties otherwise – Greg Apr 12 '16 at 18:59