Reputation: 183839
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.
@assert_case_sensitive
?*args, **kwargs
mean?f
' represent?Thanks!
Upvotes: 3
Views: 507
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
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
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
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