Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python property factory or descriptor class for wrapping an external library

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.

like image 737
Olaf Avatar asked Apr 12 '16 18:04

Olaf


People also ask

What is a Python descriptor and how to use it?

Normally, Python uses methods like getters and setters to adjust the values on attributes without any special processing. It’s just a basic storage system. Sometimes, You might need to validate the values that are being assigned to a value. A descriptor is a mechanism behind properties, methods, static methods, class methods, and super ().

How do Decorators work in Python?

Let’s understand the syntax, and how its works with an example: In this example, the decorator rebinds the class C to another class Wrapper, which retains the original class in an enclosing scope and creates and embeds an instance (wrap) of the original class when it’s called.

How is property () implemented in Python in terms of descriptor protocol?

To see how property () is implemented in terms of the descriptor protocol, here is a pure Python equivalent: The property () builtin helps whenever a user interface has granted attribute access and then subsequent changes require the intervention of a method.

What is the use of property type method in Python?

This method is used when the same descriptor is needed across many different classes and attributes, for example, for type validation. In this we use the power of property decorators which are a combination of property type method and Python decorators. Writing code in comment?


2 Answers

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.')
like image 76
Olaf Avatar answered Oct 16 '22 08:10

Olaf


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)
like image 41
Greg Avatar answered Oct 16 '22 08:10

Greg