Theron Luhn Theron Luhn - 5 months ago 16
Python Question

Python: Generic getters and setters

TL;DR: Having to define a unique set of getters and setters for each property()'d variable sucks. Can I define generic getters and setters and use them with whatever variable I want?

Let's say I make a class with some nice getters and setters:

class Foo
def getter(self):
return _bar+' sasquatch'

def setter(self, value):
_bar = value+' unicorns'

bar = property(getter, setter)


Pretty great, right?

Now let's say I put in another variable called "baz" and I don't want it to be left out from this sasquatch/unicorn fun. Well, I guess I could make another set of getters and setters:

class Foo
def bar_getter(self):
return _bar+' sasquatch'

def bar_setter(self, value):
_bar = value+' unicorns'

bar = property(bar_getter, bar_setter)

def baz_getter(self):
return _baz+' sasquatch'

def baz_setter(self, value):
_baz = value+' unicorns'

baz = property(baz_getter, baz_setter)


But that's not very DRY and needlessly clutters my code. I guess I could make it a bit DRYer:

class Foo
def unicornify(self, value):
return value+' unicorns'

def sasquatchify(self, value):
return value+' sasquatch'

def bar_getter(self):
return self.sasquatchify(_bar)

def bar_setter(self, value):
_bar = self.unicornify(_bar)

bar = property(bar_getter, bar_setter)

def baz_getter(self):
return self.sasquatchify(_baz)

def baz_setter(self, value):
_baz = self.unicornify(_baz)

baz = property(baz_getter, baz_setter)


Although that might make my code DRYer, it's not ideal. If I wanted to unicornify and sasquatchify two more variables, I would have to add four more functions!

There must be a better way to do this. Can I use a single generic getter and/or setter across multiple variables?

Unicorn-less and sasquatch-less real-world implementation: I'm using SQLAlchemy ORM, and want to transform some of the data when storing and retrieving it from the database. Some of the transformations are applicable to more than one variable, and I don't want to clutter my classes with getters and setters.

Answer

How about just:

def sasquatchicorn(name):
    return property(lambda self: getattr(self, name) + ' sasquatch',
                    lambda self, val: setattr(self, name, val + ' unicorns'))

class Foo(object):
    bar = sasquatchicorn('_bar')
    baz = sasquatchicorn('_baz')

Somewhat more generically:

def sasquatchify(val):
    return val + ' sasquatch'

def unicornify(val):
    return val + ' unicorns'

def getset(name, getting, setting):
    return property(lambda self: getting(getattr(self, name)),
                    lambda self, val: setattr(self, name, setting(val)))

class Foo(object):
    bar = getset('_bar', sasquatchify, unicornify)
    baz = getset('_baz', sasquatchify, unicornify)

Or, with barely more work, you can use the full descriptor protocol, as described in agf's answer.

Comments