Yarin
Yarin

Reputation: 183839

Need help understanding some Python code with @, *args and **kwargs

I'm new to Python, and stumped by this piece of code from the Boto project:

class SubdomainCallingFormat(_CallingFormat):
    @assert_case_insensitive
    def get_bucket_server(self, server, bucket):
        return '%s.%s' % (bucket, server)

def assert_case_insensitive(f):
    def wrapper(*args, **kwargs):
        if len(args) == 3 and not (args[2].islower() or args[2].isalnum()):
            raise BotoClientError("Bucket names cannot contain upper-case " \
            "characters when using either the sub-domain or virtual " \
        "hosting calling format.")
        return f(*args, **kwargs)
    return wrapper

Trying to understand what's going on here.

  1. What is the '@' symbol in @assert_case_sensitive ?
  2. What do the args *args, **kwargs mean?
  3. What does 'f' represent?

Thanks!

Upvotes: 3

Views: 507

Answers (4)

Omnifarious
Omnifarious

Reputation: 56068

In this particular case assert_case_sensitive is a function wrapping function, otherwise known as a decorator.

A decorator takes an existing function and returns a different function. Usually it returns a new function that calls the original function in some way. In this case assert_case_insensitive always returns wrapper which is a function that's defined within it who's name is only known inside assert_case_insensitive.

When a function is declared in the body of another like that, the enclosed function is sort of created anew every time the outer function is called. This means the inner function has access to the variables from the function it was in.

This technique is called a closure, and Python's support of closures is incomplete because the variables from the enclosing scopes cannot be modified. But that's not terribly relevant to this question.

The goal of assert_case_insensitive is to enforce some conditions on the arguments to the functions it's passed. It returns a new function that enforces those conditions and calls the original function. In order for assert_case_insensitive to be able to be used to wrap any function, it needs to work for functions with different numbers of arguments, and functions that have keyword arguments. This is where *args and **kargs come in.

An argument of the form *something gets a tuple of any remaining non-keyword (aka positional) arguments that are not accounted for by the previous positional arguments. An argument of the form **something gets a dictionary any remaining keyword arguments that are not accounted for by the previous positional arguments. Arguments of the form *something or **something must occur after all other arguments.

A similar syntax is used for calling functions with argument lists that aren't know. An argument of the form *(iterable sequence) is turned into a series of positional arguments, and an argument of the form **(dictionary) is turned into a set of keyword arguments.

So wrapper is using these constructs so it can work very generally for any function. Decorators in general are expected to work for any function, so doing something like this is highly recommended when writing them.

Upvotes: 4

Lie Ryan
Lie Ryan

Reputation: 64913

What is the '@' symbol in @assert_case_sensitive ?

@ is decorator syntax, basically:

@decor
def func(arg):
    pass

is equivalent to:

def func(arg):
    pass
func = decor(func)

What do the args *args, **kwargs mean?

The *args and **kwargs are argument unpacking. It is similar to C's varargs, in that any excess unnamed arguments will go to *args tuple, and unrecognized named arguments will go to **kwargs dict.

def foo(a, *arg, **kwarg):
    print a      # prints 1
    print arg    # prints (2, 3)
    print kwarg  # prints {'foo': 4}

foo(1, 2, 3, foo=4)

What does 'f' represent?

It is python's higher-level function. Basically in python, everything is an object including functions. Since function is an object, you can pass function as an argument to another function. If this is C, it's similar to passing a function pointer.

Upvotes: 4

Sologoub
Sologoub

Reputation: 5352

For @assert_case_sensitive here's the explanation of decorators from wikipedia:

A decorator is a Python object that can be called with a single argument, and that modifies functions or methods. Python decorators were inspired in part by Java annotations, and have a similar syntax; the decorator syntax is pure syntactic sugar, using @ as the keyword:

@viking_chorus
def menu_item():
    print "spam"

is equivalent to

def menu_item():
    print "spam"
menu_item = viking_chorus(menu_item)

Upvotes: 1

hao
hao

Reputation: 10238

The @ symbol is used to indicate the application of a decorator.

And those asterisks indicate the parameters are excess positional/keyword arguments put into a list/dictionary.

The "f" represents the function passed in, as a first-class object, into the decorator. When someone writes

@decorate
def whizbang(): pass

it's really equivalent to

def whizbang(): pass
whizbang = decorate(whizbang)

The manual goes into more detail, but decorators are basically a way to surround an existing piece of code with more code that can execute before and after it without having to modify the code you're decorating. All with the magic of first-class functions.

Upvotes: 6

Related Questions