2

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()
stephan
  • 2,293
  • 4
  • 31
  • 55
  • What exactly is the output you are looking for? Is it just lower case like the string.lower() function but you want to implement it using ascii values? – RMcG Oct 22 '13 at 03:27
  • Um, I'm not really sure what either of those things are that you just asked. To clarify, none of that matters to me, I'm really just trying to implement a decorator of some sort to learn more about decorators. – stephan Oct 22 '13 at 03:31
  • If you want to go to the bit level this post should help http://stackoverflow.com/questions/3569874/how-do-uppercase-and-lowercase-letters-differ-by-only-one-bit/3570520#3570520 – RMcG Oct 22 '13 at 03:31
  • I'm not really asking for help on how to output lowercase strings, I'm asking for help implementing a decorator. I don't know where to start with the decorator itself, and I'm confused about what to call it on. For example do I put it above displayDogs() or terrier.displayDogs() – stephan Oct 22 '13 at 03:41
  • 1
    Decorators are used to modify a function. For example, if you had "getDogInfo(self)" that returned a string which described all the dogs, you could have a decorator that would modify it's return value and make it lower case or something. The easiest things to do in a decorator are 1) add code before a the original function, 2) add code that runs after the original function, and 3) Monkey with the arguments of the function. So modifying the print statemetns within the function is a little tougher. – Murph Oct 22 '13 at 04:26

3 Answers3

5

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
aychedee
  • 24,871
  • 8
  • 79
  • 83
2

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!'
Claudiu
  • 224,032
  • 165
  • 485
  • 680
0

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()
warvariuc
  • 57,116
  • 41
  • 173
  • 227