Let's explore some python attributes, shall we? (note: this is python 2.7.x)

## Attributes

Let's consider a simple class, `N`

, with a single attribute,
`numbers`

containing values 0 - 9.

```
class N(object):
numbers = range(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```

We can create an instance of this class, then perform some operations on the attribute (like accessing or setting its values).

```
>>> n = N()
>>> n.numbers # get the value of the attribute
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> n.numbers = range(10, 20) # Change the value of the attribute
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
```

Simple enough, right? This is usually what you expect with attributes on a python class. Just for the sake of completeness, let's look at another way to create an attribute on a class:

```
class N(object):
def __init__(self, *args, **kwargs):
self.numbers = range(10)
```

This is analagous to our first example. The `__init__`

method is
called when we instantiate (or create an object from) the `N`

class. We can still access and change the `numbers`

attribute.

In either case, you can list the attributes of the `n`

object,
and you should see a list containing `'numbers'`

:

```
>>> dir(n)
['__class__', '__delattr__', '__dict__', ..., 'numbers']
```

## Properties

In the examples above, the `numbers`

attribute is simply a
variable referencing some value. Python also allows you to create a
*property* (one of python's built-in
functions). It's essentially a method that behaves as if it were an
attribute. Think of it as an attribute who's value is *calculated* every
time it's accessed.

What if we wanted an `even_numbers`

attribute?

```
class N(object):
numbers = range(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
@property
def even_numbers(self):
return [num for num in self.numbers if num % 2 == 0]
```

The `@property`

decorator converts our `even_numbers`

method into a property. We can now access it like so:

```
>>> n = N()
>>> n.numbers # get the value of the attribute
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> n.even_numbers
[0, 2, 4, 6, 8]
```

Pretty cool! But what happens when we try to set the value of
`even_numbers`

?

```
>>> n.even_numbers = range(10, 20)
---------------------------------------------------------------------------
Traceback (most recent call last)
----> 1 n.even_numbers = range(10, 20)
AttributeError: can't set attribute
```

We've got to define a property's `setter`

if we want to do this.

```
class N(object):
numbers = range(10)
@property
def even_numbers(self):
return [num for num in self.numbers if num % 2 == 0]
@even_numbers.setter
def even_numbers(self, values):
# Just assign the input values to the ``numbers`` attribute.
# You *could* do something more interesting here if you wanted.
self.numbers = values
```

We can now set the value of our `even_numbers`

property.

```
>>> n.even_numbers = range(10, 20)
>>> n.even_numbers
[10, 12, 14, 16, 18]
>>> n.numbers
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
```

You can read more about properties at Official Python Doc's section on built-in functions.

## __getattr__

Python has a number of *magic methods* (aka *dunder methods*), and
`__getattr__`

is one that defines the behavior of accessing
*non-existing* attributes.

Yes, you can access attributes that *don't exist*!

If you're interested in more about python's magic methods, see the excellent A Guide to Python's Magic Methods.

Let's implement an `odd_numbers`

attribute using
`__getattr__`

. (This is probably a bad idea, but it illustrates the
point).

Add the following method to our `N`

class:

```
def __getattr__(self, name):
if name == "odd_numbers":
return [num for num in self.numbers if num % 2 != 0]
```

Let's try it out:

```
>>> n = N()
>>> n.numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> n.even_numbers
[0, 2, 4, 6, 8]
>>> n.odd_numbers
[1, 3, 5, 7, 9]
```

Pretty cool! However, there are a couple of caveats.

```
>>> dir(n)
['__class__', '__delattr__', ..., 'even_numbers', 'numbers']
```

Notice anything missing? That's right. There's no `'odd_numbers'`

attribute available. So much for *self-documenting code*!

What about this?

```
>>> n.whoopsies
>>> type(n.whoopsies)
NoneType
```

Aren't we supposed to get an `AttributeError`

if we access
an attribute that doesn't exist!? Yes. We are.

Be careful implementing your own `__getattr__`

method. Make sure
it does what you want, but be sure to raise an appropriate exception if you
don't want non-existing attributes to silently return `None`

.

Here's an example:

```
def __getattr__(self, name):
if name == "odd_numbers":
return [num for num in self.numbers if num % 2 != 0]
# Raise an AttributeError, for all other attribute names.
raise AttributeError("'N' object has no attribute '{0}'".format(name))
```

So, now we'd get:

```
>>> n.whoopsies
---------------------------------------------------------------------------
Traceback (most recent call last)
AttributeError: 'N' object has no attribute 'asdf'
```

Now, you might also be thinking, "how would I assign a value to `odd_numbers`

?"
Well, there *is* a `__setattr__`

method, but be careful!
**Here be dragons**!

Unless you already know what you're doing and you have a good reason, it's
probably not a good idea to start changing the behavior of `__setattr__`

.

If you really need to customize the behavior of assignment, you probably want
to use a *descriptor*.

## Descriptors

A descriptor is a class that defines behavior for getting and setting an attribute.

Let's keep building on our `N`

class. What if we only wanted to
access numbers that were primes? What if we wanted to be able to easily
store prime numbers in the `numbers`

attribute?

First of all, lets write a little function to determine if a number is prime. This is not the most efficient way to do it, but it's simple and concise:

```
def is_prime(number):
"""Determine if a number is prime. Shamelessly adapted from:
http://stackoverflow.com/a/4117879/182778
Returns True or False
"""
return number > 1 and all(number % i for i in xrange(2, number))
```

Now, create a class called `PrimeNumbers`

. An instance of this
class will eventually be attached to our `N`

class.
We add a `__get__`

method that defines the behavior we want when
we access a value, and we define a `__set__`

method that defines
the behavior we want when we set a value.

```
class PrimeNumbers(object):
"""This class implements a descriptor (ie. a property or attribute) that
will only store Prime Numbers. The class on which it is attached must have
a ``numbers`` attribute."""
def filter_primes(self, numbers):
"""Use the ``is_prime`` function to pluck only primes from a list of
numbers."""
return filter(is_prime, numbers)
def __get__(self, instance, owner):
"""Get only the prime numbers from the ``numbers`` attribute on the
``instance`` object (an N object).
Note:
* ``instance`` will be an instance of our N class.
* ``owner`` will be a reference to the N class (not an instance
of it)
"""
return self.filter_primes(instance.numbers)
def __set__(self, instance, values):
"""Set the value of ``instance.numbers``, but *only* store primes.
``values`` is just a list of numbers.
"""
instance.numbers = self.filter_primes(values)
```

Now, we need to update our `N`

class so it contains a
`prime_numbers`

attribute:

```
class N(object):
numbers = range(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
prime_numbers = PrimeNumbers()
# ...
```

Now, lets play around with this.

```
>>> n = N()
>>> n.numbers
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> n.prime_numbers
[2, 3, 5, 7]
```

Great! Our `PrimeNumbers.__get__`

method removes all non-prime
numbers from the list. Let's try setting some values:

```
>>> n.prime_numbers = range(0,30)
>>> n.prime_numbers
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
>>> n.numbers
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
```

Interesting! Our `PrimeNumbers.__set__`

also removes all non-prime
numbers before storing the result in `n.numbers`

!

Note that our `even_numbers`

method still works as expected:

```
>>> n.even_numbers
[2]
```

Descriptors are powerful, and give you the tools to build re-usable properties for your classes. For even more on Descriptors, see the Descriptor HowTo Guide and the excellent Python Descriptors Demystified.

## Wrapping Up

Well, that's it for now. I hope you've enjoyed this short tour of python
*attributes*. If you want to see all the code at once, you can grab the
full example from
https://gist.github.com/bradmontgomery/6432860.

Thanks for reading!

comments powered by Disqus