Question

So I am new to learning decorators and I have gone through countless tutorials and while I understand and can mostly follow all of the examples, I think the best way to learn, would be to implement a decorator myself. So I am going to use this example below. I realize a decorator is not at all necessary to do this, but for the sake of learning, I would like to add a decorator that filters the strings like dog name and breed and turns them into lowercase. Any ideas or pointers in the right direction would be appreciated.

class Dogs:
    totalDogs = 0
    dogList=[]

    def __init__(self, breed, color, age):
        self.breed=breed
        self.color=color
        self.age=age
        Dogs.dogList.append(self.breed)
        Dogs.totalDogs += 1

    def displayDogs(self):
        print "breed: ", self.breed
        print "color: ",self.color
        print "age: ",self.age
        print "list of breeds:", Dogs.dogList
        print "total dogs: ", Dogs.totalDogs

def somedecorator(*args):
    #now what

terrier=Dogs("TeRrIer", "white", 5)
terrier.displayDogs()
retriever=Dogs("goldenRETRIEVER", "brown", 10)
retriever.displayDogs()
Was it helpful?

Solution

A decorator is really just a function that takes a function as an argument and returns another function.

def lower_output(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).lower()
    return wrapper


class Dogs(object):
    totalDogs = 0
    dogList=[]

    def __init__(self, breed, color, age):
        self.breed=breed
        self.color=color
        self.age=age
        Dogs.dogList.append(self.breed)
        Dogs.totalDogs += 1

    @lower_output
    def get_breed(self):
        return self.breed




>>> terrier=Dogs("TeRrIer", "white", 5)
>>> terrier.get_breed()
terrier

OTHER TIPS

Well, to simplify it, let's just deal with functions, first. Let's say you have a function that prints something about its arguments:

def print_info(breed, name):
    print "The doggie %s's breed is %s." % (name, breed)

As such:

>>> print_info("Labrador", "Spike")
The doggie Spike's breed is Labrador.
>>> print_info("Pit Bull", "Spot")
The doggie Spot's breed is Pit Bull.

Now you want that function instead to always lowercase the breed. So in a sane way you'd just do this:

def manually_lowered_print_info(breed, name):
    print "The doggie %s's breed is %s." % (name, breed.lower())

Output is:

>>> manually_lowered_print_info("Labrador", "Spike")
The doggie Spike's breed is labrador.

But let's say for some reason you very often had to lowercase the first string argument of functions you write so you wanted to abstract this away as a decorator. We want it to look like this and have the same output:

@lower_first_arg
def dec_lowered_print_info(breed, name):
    print "The doggie %s's breed is %s." % (name, breed)

This is just syntax sugar for this:

def tmp_func(breed, name):
    print "The doggie %s's breed is %s." % (name, breed)
dec_lowered_print_info = lower_first_arg(tmp_func)

So we want lower_first_arg to return the transformed print_info function. Let's first make a function specifically tailored for the print_info function.

def lower_first_arg(print_info_func_arg):
    def inner_print_info(breed, name):
        return print_info_func_arg(breed.lower(), name)
    return inner_print_info

Does it work? Let's see:

>>> transformed_print_info = lower_first_arg(print_info)
>>> print_info("Pit Bull", "Spot")
The doggie Spot's breed is Pit Bull.
>>> transformed_print_info("Pit Bull", "Spot")
The doggie Spot's breed is pit bull.

Great! Notice that we are passing print_info as an argument to the lower_first_arg function, where it gets referred to by the local variable print_info_func_arg.

If we use the decorator syntax it works identically:

@lower_first_arg
def dec_lowered_print_info(breed, name):
    print "The doggie %s's breed is %s." % (name, breed)

Outstanding:

>>> dec_lowered_print_info("Pit Bull", "Spot")
The doggie Spot's breed is pit bull.

Cool, so that's it, really. Now to make the decorator more generic, let's first generalize the names:

def generic_lower_first_arg(f):
    def wrapped(arg1, arg2):
        return f(arg1.lower(), arg2)
    return wrapped

Now the problem here is this decorator only works on functions of 2 args. Ideally we'd want it to work on any function of 1 arg or more, e.g.:

@generic_lower_first_arg
def failed_whatnow(first, last, middle):
    print "%s %s %s" % (first, middle, last)

Now, that code will run, but we get an error if we try to call it:

>>> failed_whatnow("Bob", "Jones", "The Killer")

Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    failed_whatnow("Bob", "Jones", "The Killer")
TypeError: wrapped() takes exactly 2 arguments (3 given)

What's going on? Well, the decorator took failed_whatnow and returned the function it defined, wrapped, but that wrapped function only takes two arguments! The fix here is to use the varargs syntax. It's also generally a good idea to pass along any keyword args that might be given to the wrapped function.

def proper_lower_first_arg(f):
    def wrapped(arg1, *args, **kwargs):
        return f(arg1.lower(), *args, **kwargs)
    return wrapped

And now it works on all sorts of functions:

@proper_lower_first_arg
def proper_whatnow(first, last, middle):
    print "%s %s %s" % (first, middle, last)

@proper_lower_first_arg
def multiplyit(mm, n=3):
    return mm * n

Proof:

>>> proper_whatnow("Bob", "Jones", "The Killer")
bob The Killer Jones
>>> multiplyit("HaHa.Fool!")
'haha.fool!haha.fool!haha.fool!'
>>> multiplyit("HaHa.Fool!", n=5)
'haha.fool!haha.fool!haha.fool!haha.fool!haha.fool!'

A decorator usually is used to modify input arguments or returned values of a function/method.

Method Dogs.displayDogs does not return any data (except None), so it doesn't make sense saying you want to make strings lowercase. Which strings? You just print the values. So you would do:

class Dogs:
    totalDogs = 0
    dogList=[]

    def __init__(self, breed, color, age):
        self.breed=breed
        self.color=color
        self.age=age
        Dogs.dogList.append(self.breed)
        Dogs.totalDogs += 1

    def displayDogs(self):
        print "breed: ", self.breed.lower()
        print "color: ",self.color.lower()
        ...

Otherwise you should refactor your code:

def make_lower(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        result = [value.lower() for value in result]
        return result
    return wrapper

class Dogs:
    totalDogs = 0
    dogList=[]

    def __init__(self, breed, color, age):
        self.breed=breed
        self.color=color
        self.age=age
        Dogs.dogList.append(self.breed)
        Dogs.totalDogs += 1


    @make_lower
    def getDogs(self):
        return [
            "breed: %s" % self.breed,
            "color: %s" % self.color.lower(),
            ...
        ]

The you do

terrier = Dogs("TeRrIer", "white", 5)
print terrier.getDogs()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top