# Day 17: Exercise Solutions Here are our solutions for the day 17 exercises in the 30 Days of Python series. Make sure you try the exercises yourself before checking out the solutions!

### 1) Create a function that accepts any number of numbers as positional arguments and prints the sum of those numbers.

Before we get started it's very important that we remember not to call our new function `sum`. Why? Because there's already a `sum` function as part of Python's built-ins, and we don't want to make that function inaccessible by reusing the name!

One common convention when we want to use the same name as a built in function, or a keyword, is to put an underscore in the name, like this: `sum_`. You could also go for a totally different name like `multi_add` if you like.

Let's go got `multi_add` because it sounds more fun.

``````def multi_add():
pass``````

Since we want to take in any number of values here, we know we need the `*` operator for our parameter, but we need to decide what to call that parameter.

We could certainly used `*args`, but here we know what the arguments are. They're values or numbers. Either of the following would be fine:

``````def multi_add(*values):
pass

pass``````

I'm going to use the second option, because it's even more specific, in my opinion.

Now that we have this tuple of numbers in our `numbers` parameter, we can just pass this value to the build in `sum` function and print the result.

``````def multi_add(*numbers):
print(sum(numbers))``````

Be careful not to write `print(sum(*numbers))`, as `sum` expects an iterable, not lots of values.

### 2) Create a function that accepts any number of positional and keyword arguments, and that prints them back to the user. Your output should indicate which values were provided as positional arguments, and which were provided as keyword arguments.

Unlike in the last exercise, we really have no idea what the data we're receiving is, so this is a case for `*args` and `**kwargs`. We need both here because we want to accept both any number of positional arguments, and any number of keyword arguments.

I'm going to call my function `arg_printer`, but feel free to choose whatever name you like, as long as it describes the function well.

``````def arg_printer(*args, **kwargs):
pass``````

Now that we have the parameters set up, printing them is fairly simple. We really only need to do this:

``````def arg_printer(*args, **kwargs):
print(f"Positional arguments are: {args}")
print(f"Keyword arguments are: {kwargs}")``````

Now if we call the function with a range of random values as arguments:

``arg_printer(1,  "blue",  [1,  23,  3], height=184, key=lambda x: x ** 2)``

We get this:

``````Positional arguments are: (1, 'blue', [1, 23, 3])
Keyword arguments are: {'height': 184, 'key': <function <lambda> at 0x7f1b7c44f1f0>}``````

I think we can do a little better than this though. There are two things I want to accomplish here:

1. I don't want the brackets to show up around the positional arguments. I just want a series of comma separated values.
2. I want the keyword arguments to be a series of comma separated values in this format: `height=184`.

Let's tackle the positional arguments first. I'm going to use `join` here, but this means all of our arguments need to be strings, so we need to do a bit of processing of the argument list. I'm going to do this with a comprehension:

``````def arg_printer(*args, **kwargs):
args = [str(arg) for arg in args]
print(f"Positional arguments are: {', '.join(args)}")``````

This is good, but not perfect, because there's no difference between something like `"1"` and `1` in the output. That's potentially misleading.

Instead of using `str`, I'm going to use the `repr` function, which is going to give us a different kind of string representation that aligns more with how we actually define the values. This is going to preserve the difference between `1` and `"1"`.

You can find more information on `repr` in the documentation.

``````def arg_printer(*args, **kwargs):
args = [repr(arg) for arg in args]
print(f"Positional arguments are: {', '.join(args)}")``````

Now let's take care of the keyword arguments.

I want to use join again, so I'm going to create a list of strings, just like before. This time I'm going to iterate over the dictionary using the `items` method, and I'm going to interpolate the key and value into the string.

``````def arg_printer(*args, **kwargs):
args = [repr(arg) for arg in args]
print(f"Positional arguments are: {', '.join(args)}")

kwargs = [f"{key}={repr(value)}" for key, value in kwargs.items()]``````

Once again I'm using `repr` here, but only for the values. This makes the output more in line with how we pass in keyword arguments.

Now we can just join the new `kwargs` list while printing:

``````def arg_printer(*args, **kwargs):
args = [repr(arg) for arg in args]
print(f"Positional arguments are: {', '.join(args)}")

kwargs = [f"{key}={repr(value)}" for key, value in kwargs.items()]
print(f"Keyword arguments are: {', '.join(kwargs)}")``````

Using our old set of arguments, we now get much nicer output like this:

``````Positional arguments are: 1, 'blue', [1, 23, 3]
Keyword arguments are: height=184, key=<function <lambda> at 0x7f2deaeed700>``````

### 3) Print the following dictionary using the `format` method and `**` unpacking.

``````country = {
"name": "Germany",
"population": "83 million",
"capital": "Berlin",
"currency": "Euro"
}``````

We have a lot of freedom in how to actually output the data here, so I'm just going to largely mimic the dictionary format.

My template is going to look like this:

``````country_template = """Name: {name}
Population: {population}
Capital: {capital}
Currency: {currency}"""``````

Because we're planning to use `**` unpacking, we need to use named placeholders, because that's how `format` maps keywords to the placeholders.

Now we can use our template like this:

``````country = {
"name": "Germany",
"population": "83 million",
"capital": "Berlin",
"currency": "Euro"
}

country_template = """Name: {name}
Population: {population}
Capital: {capital}
Currency: {currency}"""

print(country_template.format(**country))``````

The output in my case looks like this:

``````Name: Germany
Population: 83 million
Capital: Berlin
Currency: Euro``````

If you want to try this out with more values, create a list of country dictionaries and pass your template to `print` in a `for` loop.

### 4) Using `*` unpacking and `range`, print the numbers `1` to `20`, separated by commas.

One thing we have to be careful to remember here, is that the stop value when defining a `range` is not inclusive. That means we need `range(1, 21)`.

We can actually do this entire exercise on one line, which shows some of the power of this approach:

``print(*range(1, 21), sep=", ")``

Here I've unpacked the `range` with `*`, turning it into 20 individual integers, and I've defined the character to go between the values using the `sep` parameter. The value I chose was a comma followed by a space.

If we run the code, we get output like this:

``1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20``

### 5) Modify your code from exercise `4` so that each number prints on a different line. You can only use a single `print` call.

The only change we need to make to our code is changing the separator string that we pass to `sep`. Instead a comma and a space, I'm going to use `"\n"`, which means that we'll put a line break between each value.

``print(*range(1, 21), sep="\n")``

Now all of the numbers are on a different line when we print them.