Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: Generic getters and setters

Tags:

python

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.

like image 654
Theron Luhn Avatar asked Apr 13 '12 22:04

Theron Luhn


People also ask

Does Python have getters and setters?

Getters and Setters in python are often used when: We use getters & setters to add validation logic around getting and setting a value. To avoid direct access of a class field i.e. private variables cannot be accessed directly or modified by external user.

What are getter and setter methods in Python?

As the name suggests, getters are the methods which help access the private attributes or get the value of the private attributes and setters are the methods which help change or set the value of private attributes.

Can getters and setters have the same name?

A couple things to remember: You can only have one getter or setter per name, on an object. (So you can have both one value getter and one value setter, but not two 'value' getters.)

What is a setter Python?

A setter is a method that sets the value of a property. In OOPs this helps to set the value to private attributes in a class. Basically, using getters and setters ensures data encapsulation.


2 Answers

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.

like image 198
Danica Avatar answered Oct 13 '22 21:10

Danica


This is what the descriptor protocol property is based on is for:

class Sasicorn(object):                              
    def __init__(self, attr):                        
        self.attr = "_" + attr                       
    def __get__(self, obj, objtype):                 
        return getattr(obj, self.attr) + ' sasquatch'
    def __set__(self, obj, value):                   
        setattr(obj, self.attr, value + ' unicorns') 

class Foo(object):                                   
    def __init__(self, value = "bar"):               
        self.bar = value                             
        self.baz = "baz"                             
    bar = Sasicorn('bar')                            
    baz = Sasicorn('baz')                            

foo = Foo()                                          
foo2 = Foo('other')                                  
print foo.bar                                        
# prints bar unicorns sasquatch
print foo.baz                                        
# prints baz unicorns sasquatch
print foo2.bar                                       
# prints other unicorns sasquatch

While property in a factory function may be fine for your toy example, it sounds like maybe you need more control for your real use case.

like image 41
agf Avatar answered Oct 13 '22 20:10

agf