Handling Types in Python – Enforcing Types with Properties

February 18, 2016

Building on my thoughts from Handling Types in Python – Ignore it, Convert it, or Check it, I’m continuing to build up to my talk at PyCaribbean in Santa Domingo Dominican Republic. My title will be “Python: As much typing as you want,” obviously this means that I’ve been doing some thinking quite a bit about Python typing of late.

Previously I looked at the three basic strategies Python has for dealing with types; ignoring them, converting them, or checking types. All of these are very practical in their own right, but basically limited to working inside a given function call. However Python is an object oriented language and we do have the option to work with that and create an object attribute which has a type enforced on it.

Getters and Setters

Getters and setters are a common way to handle this. We could write these in Python, it would look something like this:

def get_age(self):
    return self.__age
def set_age(self, age):
    self.__age = int(age)
norm.set_age('23')
assert norm.get_age() == 23

Of course this is combining with a converting strategy to ensure that age is always an integer, set_age(‘Green’) will cause a ValueError, obviously we could do type check instead.

Properties via @property

Of course in Python we don’t do it that way. In Python we can create a property using the @property decorator like this:

@property
def name(self):
    return self.__dict__['name']
@name.setter
def name(self, value):
    self.__dict__['name'] = str(value)

norm.name = "Andy"
assert norm.name == "Andy"
norm.name = 42
assert norm.name == "42"

Again we can enforce any amount of typecasting either within the getter or the setter

Properties via property()

Since decorators were added to Python in 2.4 this has been by far the preferred way to declare properties. However, before decorators we already had properties the syntax we used then is still usable and for enforcing types it’s really handy. Combining our getter and setter from the age with the alternate syntax we can create an age property:

age = property(get_age,set_age)
norm.age = '28'
assert norm.age == 28

in and of itself the age property is like any other. This will however allow us to get some much better code reuse. Let’s assume we want to have a few properties with the same constraint, we’ll enforce them as Decimals so we can store money. Let’s write a function that creates a set of getter, setter, and deleter for this purpose:

def create_gsd_convert_Decimal(name):
   def getter(self):
       return self.__dict__[name]
   def setter(self, value):
       self.__dict__[name] = Decimal(value)
   def deleter(self):
       del self.__dict__[name]
   return getter, setter, deleter

now we can use it like this:

price = property(*create_gsd_convert_Decimal('price'))
price = 380.23
norm.price = price
assert isinstance(price, float)
assert not isinstance(price, Decimal)
assert isinstance(norm.price, Decimal)
assert not isinstance(norm.price, float)

using a function to return a tuple is a bit clunky, so lets clean that up a touch, I’ll continue to use the function creator function above adding only the call to property:

def enforce_Decimal(name):
   return property(*create_gsd_convert_Decimal(name))

much cleaner to use now

tax = enforce_Decimal('tax')

The interface here is pretty much as clean as it’s going to get without introducing a metaclass, but in my talk and a future blog post I’m going to explore how to use the descriptor protocol to simplify the mechanics behind this.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: