Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fudging the line between classes and functions

Tags:

python

I have a series of functions that serve to classify data. Each function is passed the same input. The goal of this system is to be able to drop in new classification functions at will without having to adjust anything.

To do this, I make use of a classes_in_module function lifted from here. Then, every classifier in one python file will be ran on each input.

However, I am finding that implementing the classifier as either a class or a function is kludgy. Classes mean instantiating and executing, while functions lack clean introspection to allow me to query the name or use inheritance to define common values.

Here is an example. First, the class implementation:

class AbstractClassifier(object):
    @property
    def name(self):
        return self.__class__.__name__

class ClassifierA(AbstractClassifier):
    def __init__(self, data):
        self.data = data
    def run(self):
        return 1

This can then be used in this fashion, assuming that classifier_list is the output of classes_in_module on a file containing ClassifierA among others:

result = []
for classifier in classifier_list:
    c = classifier(data)
    result.append(c.run())

However, this seems a bit silly. This class is obviously static, and doesn't really need to maintain its own state, as it is used once and discarded. The classifier is really a function, but then I lose the ability to have a shared name property -- I would have to use the ugly introspection technique sys._getframe().f_code.co_name and replicate that code for each classifier function. And any other shared properties between classifiers would also be lost.

What do you think? Should I just accept this mis-use of classes? Or is there a better way?

like image 400
Ian Fiddes Avatar asked Sep 25 '22 08:09

Ian Fiddes


3 Answers

Functions can have member data. You can also find the name of a function using the func_name attribute.

def classifier(data):
    return 1
classifier.name = classifier.func_name

print(classifier.name) #classifier

If you want multiple functions to behave the same way, you can use a decorator.

function_tracker = []

def add_attributes(function):
    function.name = function.func_name
    function.id = len(function_tracker)
    function_tracker.append(function)
    return function

@add_attributes
def classifier(data):
    return 1

print(classifier.name, classifier.id) # 'classifier', 0

Would this work to avoid classes in your specific case?

like image 83
Jared Goguen Avatar answered Nov 15 '22 09:11

Jared Goguen


If you don't need several instances of the class (and it seems you don't) make one instance of the class and change the run to __call__:

class AbstractClassifier(object):
    @property
    def name(self):
        return self.__class__.__name__

class ClassifierA(AbstractClassifier):
    def __call__(self, data):
        return 1
ClassifierA = ClassifierA()       # see below for alternatives

and then in your other code:

result = []
for classifier in classifier_list:
    result.append(classifier(data))

Instead of having ClassifierA = ClassifierA() (which isn't very elegant), one could do:

classifier_list = [c() for c in (ClassifierA, ClassifierB, ...)]

This method allows you to keep your classes handy should you need to create more instances of them; if you don't ever need to have more than one instance you could use a decorator to IAYG (instantiate as you go ;) :

def instantiate(cls):
    return cls()

@instantiate
class ClassifierZ(object):
    def __call__(self, data):
        return some_classification
like image 27
Ethan Furman Avatar answered Nov 15 '22 09:11

Ethan Furman


To use a class instance as a function:

class ClassifierA(AbstractClassifier):
    def __init__(self, data):
        self.data = data
    def __call__(self):
        return 1

result = []
for classifier in classifier_list:
    c = classifier(data)
    result.append(c())

Or to just use functions:

classifier_list = []
def my_decorator(func):
    classifier_list.append(func)
    return func

@my_decorator
def classifier_a(data):
    return 1

result = []
for classifier in classifier_list:
    c = classifier(data)
    result.append(c)
like image 40
Seán Hayes Avatar answered Nov 15 '22 09:11

Seán Hayes