Functors en Python

Un ‘functor’ es u objeto el cual puede ser llamado como si fuera una función. En Python simplemente hay que implementar el método ‘__call__’. En este ejemplo, vamos a tomar a los candidatos presidenciales del articulo anterior y vamos a escribir una pequeña clase (llamada SortKey) la cual nos va a permitir ordenar por cualquier attributo o combinación de attributos (en nuestro ejemplo vamos a ordendar por sexo y luego por edad):

#!/usr/bin/env python3
# A little fun with the candidates for the US presidency for 2016 election year
# Revisited versions with functors, slots, abstract classes
# @author josevnz@kodegeek.com
#
import abc

class SortKey:
    
    def __init__(self, *attributes):
        self.attributes = attributes
    def __call__(self, instance):
        return [getattr(instance, attribute) for attribute in self.attributes]

class Candidate(metaclass=abc.ABCMeta):
    
    __slots__ = ("__name", "__sex", "__age")

    @abc.abstractmethod    
    def __init__(self, name, sex="F", age=21):
        self.__name = name
        self.__sex = "F" if sex not in ["M", "F"] else sex
        self.__age = 21 if not (21 < = age <= 120) else age
    
    @property
    def name(self):
        return self.__name
    
    def get_party(self):
        raise NotImplemented()
    
    party = abc.abstractproperty(get_party)
    
    @property
    def sex(self):
        return self.__sex
    
    @property
    def age(self):
        return self.__age
    
    def __hash__(self):
        return hash(id(self))
    
    def __str__(self):
        return "{}=[name={}, sex={}, age={}]".format(self.__class__.__name__,
                                                            self.__name,
                                                            self.__sex,
                                                            self.__age
                                                            )
    
    def __repr__(self):
        return "{}({}{}{}{})".format(
                                 self.__class__.__name__, 
                                 self.__name, 
                                 self.__sex, 
                                 self.__age
                                )
    
    @abc.abstractmethod
    def __bool__(self):
        raise NotImplemented()
    
    def __lt__(self, other):
        return self.__age < other.__age
    
    def __gt__(self, other):
        return self.__age > other.__age
    
    def __eq__(self, other):
        return (
                self.__age == other.__age and 
                self.__name == other.__name and
                self.__sex == other.__sex
                )

class Republican(Candidate):
    
    def __init__(self, name, sex="F", age=21):
        return super().__init__(name, sex, age)
    
    party = "Replublican Party (GOP)"
    
    def __bool__(self):
        return False
            
class Democrat(Candidate):
    
    def __init__(self, name, sex="F", age=21):
        return super().__init__(name, sex, age)
    
    party = "Democratic Party"
    
    def __bool__(self):
        return True
    
if __name__ == "__main__":
        candidates = []
        candidates.append(Democrat("Hillary Clinton", "F", 68))
        candidates.append(Republican("Donald Trump", "M", 70))
        candidates.append(Democrat("Bernie Sanders", "M", 75))
        candidates.append(Republican("Marco Rubio", "M", 45))
        candidates.sort(key=SortKey("sex", "age"), reverse=False)
        for candidate in candidates:
            isDemocrat = "yes" if candidate else "no"
            print("Candidate: {}, democrat? {}".format(candidate, isDemocrat))        

Y la salida (fíjense que definimos el orden en la linea 103, ademas de que ordenamos la lista primero para después utilizarla):

Candidate: Democrat=[name=Hillary Clinton, sex=F, age=68], democrat? yes
Candidate: Republican=[name=Marco Rubio, sex=M, age=45], democrat? no
Candidate: Republican=[name=Donald Trump, sex=M, age=70], democrat? no
Candidate: Democrat=[name=Bernie Sanders, sex=M, age=75], democrat? yes