Why not…¶
…tuples?¶
Readability¶
What makes more sense while debugging:
<Point(x=1, x=2)>
or:
(1, 2)
?
Let’s add even more ambiguity:
<Customer(id=42, reseller=23, first_name="Jane", last_name="John")>
or:
(42, 23, "Jane", "John")
?
Why would you want to write customer[2]
instead of customer.first_name
?
Don’t get me started when you add nesting. If you’ve never ran into mysterious tuples you had no idea what the hell they meant while debugging, you’re much smarter then I am.
Using proper classes with names and types makes program code much more readable and comprehensible. Especially when trying to grok a new piece of software or returning to old code after several months.
Extendability¶
Imagine you have a function that takes or returns a tuple.
Especially if you use tuple unpacking (eg. x, y = get_point()
), adding additional data means that you have to change the invocation of that function everywhere.
Adding an attribute to a class concerns only those who actually care about that attribute.
…namedtuples?¶
The difference between namedtuples and classes decorated by characteristic
is that the latter are type-sensitive and less typing aside regular classes:
>>> from characteristic import Attribute, attributes
>>> @attributes([Attribute("a", instance_of=int)])
... class C1(object):
... def __init__(self):
... if self.a >= 5:
... raise ValueError("'a' must be smaller 5!")
... def print_a(self):
... print self.a
>>> @attributes([Attribute("a", instance_of=int)])
... class C2(object):
... pass
>>> c1 = C1(a=1)
>>> c2 = C2(a=1)
>>> c1.a == c2.a
True
>>> c1 == c2
False
>>> c1.print_a()
1
>>> C1(a=5)
Traceback (most recent call last):
...
ValueError: 'a' must be smaller 5!
…while namedtuple’s purpose is explicitly to behave like tuples:
>>> from collections import namedtuple
>>> NT1 = namedtuple("NT1", "a")
>>> NT2 = namedtuple("NT2", "b")
>>> t1 = NT1._make([1,])
>>> t2 = NT2._make([1,])
>>> t1 == t2 == (1,)
True
This can easily lead to surprising and unintended behaviors.
Other than that, characteristic
also adds nifty features like type checks or default values.
…hand-written classes?¶
While I’m a fan of all things artisanal, writing the same nine methods all over again doesn’t qualify for me. I usually manage to get some typos inside and there’s simply more code that can break and thus has to be tested.
To bring it into perspective, the equivalent of
>>> @attributes(["a", "b"])
... class SmartClass(object):
... pass
>>> SmartClass(a=1, b=2)
<SmartClass(a=1, b=2)>
is
>>> class ArtisinalClass(object):
... def __init__(self, a, b):
... self.a = a
... self.b = b
...
... def __repr__(self):
... return "<ArtisinalClass(a={}, b={})>".format(self.a, self.b)
...
... def __eq__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) == (other.a, other.b)
... else:
... return NotImplemented
...
... def __ne__(self, other):
... result = self.__eq__(other)
... if result is NotImplemented:
... return NotImplemented
... else:
... return not result
...
... def __lt__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) < (other.a, other.b)
... else:
... return NotImplemented
...
... def __le__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) <= (other.a, other.b)
... else:
... return NotImplemented
...
... def __gt__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) > (other.a, other.b)
... else:
... return NotImplemented
...
... def __ge__(self, other):
... if other.__class__ is self.__class__:
... return (self.a, self.b) >= (other.a, other.b)
... else:
... return NotImplemented
...
... def __hash__(self):
... return hash((self.a, self.b))
>>> ArtisinalClass(a=1, b=2)
<ArtisinalClass(a=1, b=2)>
which is quite a mouthful and it doesn’t even use any of characteristic
‘s more advanced features like type checks or default values
Also: no tests whatsoever.
And who will guarantee you, that you don’t accidentally flip the <
in your tenth implementation of __gt__
?
If you don’t care and like typing, I’m not gonna stop you.
But if you ever get sick of the repetitiveness, characteristic
will be waiting for you. :)