Pages: Welcome | Projects

Python resources

2015/4/10
Tags: [ Ideas ] [ python ]

I hate getters and setters. They are boring and redundant. The thing I appreciate most is the const fields of C++ classes, for those of you who know how they work. It is something you can't really have in languages like Java, where final almost useless and getters are effective only for base types and immutable objects... But don't get me started on Java, or this will turn into a rant. Back on topic now!

Quick intro:

First thing first: in Python everything is pretty much public. Even private instance variables and methods, whose name start with __, can be actually accessed if you know how the name mangling works: Basically the method __foo of class C can be accessed from outside C as _C__foo. This is something you should never do, though.

If you want things to be accessible for reading, but you don't want to put a getter, you can always avoid the __ prefix. That however means your field can be also written from outside. The outside code might therefore mess with your internals, possibly violating some invariant or assumption you do. This is why the private keyword exists in other languages!

With resources you can overcome this problem. An example will give you an immediate idea of this:

class C(object):

    def __init__(self, x):
        self.__foo = x    # Private. Well, actually just mangled

    @property
    def getfoo(self):
        return self.__foo

    @foo.setter
    def setfoo(self, v):
        if not good(v):
            raise YouSuck('Invalid foo: %r' % v)
        self.__foo = v

    @foo.deleter
    def delfoo(self):
        self.__foo = None

c = C(3)
print c.foo   # Calls c.getfoo()
c.foo = 100   # Calls c.setfoo(100)

Besides being an elegant syntax, you can use this technique for a number of things:

Drawbacks? Basically none.

Hacking with syntax

I gave you a quick intro to this simple concept. Now we can hack a bit around.

First I suggest you to have a look to the manual:

pydoc property

As any other decorator, properties can be used without the @ syntax, just as regular functions.

Here I put some ways of using them. Good or bad? It is up to you.

Technique one: backed object.

I often use object orientation as a form of currification, in order to carry around some context. Example:

class C(object):

    def __init__(self):
        self.resource = ...

    def get_something(self):
        return D(self)

class D(object):

    def __init__(self, c):
        self.__c = c

    def do_something(self, y):
        # Using the resource of self.__c
        self.__c.resource.use(self.__x, y)


c = C()
d = c.get_something()
d.do_something(1)   # No need to pass c.resource around
d.do_something(2)   # No need to pass c.resource around
d.do_something(3)   # No need to pass c.resource around

By using properties we can improve this a little:

class C(object):

    def __init__(self):
        self.resource = ...

    @property
    def something(self):
        return D(self)

class D(object):

    def __init__(self, c):
        self.__c = c

    def do_something(self, y):
        self.__c.resource.use(y)

c = C()
d = c.something     # As it was a field
d.do_something(1)
d.do_something(2)
d.do_something(3)

Or have a lazy-evaluated field, backed by the object and instantiated whenever needed:

class C(object):

    def __init__(self):
        self.resource = ...
        self.__d = None

    @property
    def something(self):
        if self.__d is None:
            self.__d = D(self)
        return self.__d

class D(object):

    def __init__(self, c):
        self.__c = c

    def do_something(self, y):
        self.__c.resource.use(self.__x, y)

c = C()
c.something.do_something(1)   # Instantiated and used
c.something.do_something(2)   # Re-used
c.something.do_something(3)   # Re-used again

Non-decorated uses

Need an instance of D which is backed by an instance of C? You do not really need to have a getter...

class D(object):
    def __init__(self, x):
        self.x = x

class C(object):
    d = property(D)

c = C()
assert c.d.x is c

This is because:

Calling c.d() as is like calling D(c) and returning its result. The field x of the instance of D will be initialized with a reference to c.

Write-only fields:

Take another glance at the pydoc property, then look at this:

class E(object):

    lol = property(fset=lambda s, v : s.__setter(v))

    def __setter(self, v):
        print 'Hello', v
        # Here possibly check, assign, whatever...

e = E()
e.lol = 3     # Prints "Hello 3"
#print e.lol  # FAILS. No getter!

The property function accepts three optional functions as actual parameter: the getter, the setter and the deleter respectively. Each of them takes the instance as parameter. The setter also takes a value to be set. We can actually replace it with a lambda expression.

Interestingly, the name mangling works transparently, since we are within the class definition.

I think that's enough for today. Let's take a nap.