5
\$\begingroup\$

I wrote the following Author class in Python for a project I'm working on. I wanted to make sure that the rich comparisons and the methods I used to validate the constructor parameters were done properly.

Author class:

class Author:
 def __init__(self, id, firstname, lastname):
 self._validateid(id, "ID")
 self._validate_author(firstname, "First Name")
 self._validate_author(lastname, "Last Name")
 self._id = id
 self._firstname = firstname
 self._lastname = lastname
 def _validateid(self, id, error_message):
 if id < 1:
 raise ValueError(error_message + " is invalid")
 def _validate_author(self, parameter, error_message):
 if not parameter:
 raise TypeError(error_message + " is missing")
 @property
 def iden(self):
 return self._id
 @property
 def first_name(self):
 return self._firstname
 @property
 def last_name(self):
 return self._lastname

Rich Comparisons:

 def __lt__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) < (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __le__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) <= (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __eq__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) == (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __ne__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) != (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __ge__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) >= (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __gt__(self, other):
 if isinstance(other, Author):
 return (self.iden, self.first_name.lower(), self.last_name.lower()) > (other.iden, other.first_name.lower(), other.last_name.lower())
 return NotImplemented
 def __hash__(self):
 return hash((self.iden, self.first_name.lower(), self.last_name.lower()))
asked Oct 12, 2017 at 18:39
\$\endgroup\$
6
  • \$\begingroup\$ Why do you want your authors to sort by id first and only then by name? Also, can two authors have the same id? If not, __hash__ could just return the id... \$\endgroup\$ Commented Oct 13, 2017 at 8:08
  • \$\begingroup\$ @Graipher - What would be a better approach to sorting. My plan is to have them sorted by last name, like you see at a library \$\endgroup\$ Commented Oct 13, 2017 at 14:17
  • \$\begingroup\$ Then compare tuples of (lastname, firstname, id), instead of (id, lastname, firstname), which will always sort by id first. \$\endgroup\$ Commented Oct 13, 2017 at 14:19
  • \$\begingroup\$ @Graipher - Would it be a problem is I just used last name? \$\endgroup\$ Commented Oct 13, 2017 at 14:20
  • 1
    \$\begingroup\$ Depends, what do you want to do if you have two authors with the same last name? Python's sort is stable, so they would sort in the order of being inserted into the list/whatever you are sorting. Most people would expect it to be sorted by last name first and then by first name. I just added the id there for the rare cases where some authors have the same first and last name. \$\endgroup\$ Commented Oct 13, 2017 at 14:24

1 Answer 1

3
\$\begingroup\$
  • Personally using a namedtuple is the best way to strip most of your code.

    If you wish to be able to mutate your data, then inheriting from a list, rather than namedtuple, could be a better idea.

  • I'm not a fan of _validateid or _validate_author, just write them out in the constructor.

And so I'd personally change your code to:

from collection import namedtuple
class Author(namedtuple('AuthorBase', 'id first_name last_name')):
 def __new__(cls, id, first, last):
 if id < 1:
 raise ValueError('id is not a valid id')
 if not first:
 raise ValueError('first_name has not been set')
 if not last:
 raise ValueError('last_name has not been set')
 return super().__new__(cls, id, first, last)
answered Oct 13, 2017 at 8:27
\$\endgroup\$
5
  • \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$ Commented Oct 13, 2017 at 14:40
  • \$\begingroup\$ I was the OP for this question, thanks for answering it. Quick question, when inheriting and overriding the __new__ method in this case, are you supposed to use return super() or return namedtuple? Explained here: python.org/download/releases/2.2/descrintro/#__new__ \$\endgroup\$ Commented Jun 20, 2018 at 15:19
  • \$\begingroup\$ @Sveta That document is about Python 2.2 which is very old. In the current documentation it says using super is the common way \$\endgroup\$ Commented Jun 20, 2018 at 15:28
  • \$\begingroup\$ Inheriting from namedtuple is a common way to achieve immutability? \$\endgroup\$ Commented Jun 20, 2018 at 15:31
  • 1
    \$\begingroup\$ @Sveta Depends on usage, here I'd use a namedtuple, other places I may not. \$\endgroup\$ Commented Jun 20, 2018 at 15:34

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.