diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md index 0f2ab5dddda..02d8bd58893 100644 --- a/concepts/function-arguments/about.md +++ b/concepts/function-arguments/about.md @@ -10,108 +10,90 @@ Parameter names should not contain spaces or punctuation. ## Positional Arguments Positional arguments are values passed to a function in the same order as the parameters which bind to them. -Positional arguments can optionally be passed by using their parameter name. +Positional arguments can optionally be passed by using their parameter name: -Following is an example of positional arguments being passed by position and by their parameter name: ```python ->>> def concat(greeting, name): -... return f"{greeting}{name}" -... +def concat(greeting, name): + return f"{greeting}{name}" + # Passing data to the function by position. ->>> print(concat("Hello, ", "Bob")) +print(concat("Hello, ", "Lilly")) +#-> Hello, Lilly -Hello, Bob -... # Passing data to the function using the parameter name. ->>> print(concat(name="Bob", greeting="Hello, ")) - -Hello, Bob - +print(concat(name="Glenn", greeting="Hello, ")) +#-> Hello, Glenn ``` The first call to `concat` passes the arguments by position. The second call to `concat` passes the arguments by name, allowing their positions to be changed. -Note that positional arguments cannot follow keyword arguments. +Note that positional arguments cannot follow arguments passed by name (_also called [keyword arguments][keyword-arguments]. **Not** to be confused with var-positional parameters or [**kwargs][kwargs]_). -This +This set of arguments: ```python ->>> print(concat(greeting="Hello, ", "Bob")) +print(concat(greeting="Hello, ", "Gregor")) ``` -results in this error: +Results in this error: ``` SyntaxError: positional argument follows keyword argument ``` -Requiring positional-only arguments for function calls can be done through the use of the `/` operator in the parameter list. - - -Following is an example of positional-only arguments: +Requiring positional-only arguments for function calls can be done through the use of the `/` operator in the parameter list: ```python # Parameters showing a position-only operator. ->>> def concat(greeting, name, /): -... return f"{greeting}{name}" +def concat(greeting, name, /): + return f"{greeting}{name}" + +print(concat("Hello, ", "Ginnie")) +#-> Hello, Ginnie -... ->>> print(concat("Hello, ", "Bob")) -Hello, Bob -... # Call to the function using keyword arguments. ->>> print(concat(name="Bob", greeting="Hello, ")) +print(concat(name="Franklin", greeting="Hello, ")) Traceback (most recent call last): - print(concat(name="Bob", greeting="Hello, ")) + print(concat(name="Franklin", greeting="Hello, ")) TypeError: concat() got some positional-only arguments passed as keyword arguments: 'greeting, name' - - ``` ## Keyword Arguments Keyword arguments use the parameter name when calling a function. -Keyword arguments can optionally be referred to by position. - -Following is an example of keyword arguments being referred to by their parameter name and by position: +They can optionally be referred to by position: ```python ->>> def concat(greeting, name): -... return f"{greeting}{name}" -... +def concat(greeting, name): + return f"{greeting}{name}" + # Function call using parameter names as argument keywords. ->>> print(concat(name="Bob", greeting="Hello, ")) -Hello, Bob -... -# Function call with positional data as arguments. ->>> print(concat("Hello, ", "Bob")) -Hello, Bob +print(concat(name="Eliza", greeting="Hello, ")) +#-> Hello, Eliza +# Function call with positional data as arguments. +print(concat("Hello, ", "Tim")) +#-> Hello, Tim ``` -Requiring keyword-only arguments for function calls can be done through the use of the `*` operator in the parameter list. - - -Following is an example of keyword-only arguments: +Requiring keyword-only arguments for function calls can be done through the use of the `*` operator in the parameter list: ```python # Function definition requiring keyword-only arguments. ->>> def concat(*, greeting, name): -... return f"{greeting}{name}" -... +def concat(*, greeting, name): + return f"{greeting}{name}" + # Keyword arguments can be in an arbitrary order. ->>> print(concat(name="Bob", greeting="Hello, ")) -Hello, Bob -... +print(concat(name="Kimmie", greeting="Hello, ")) +#-> Hello, Kimmie + # Calling the function with positional data raises an error. ->>> print(concat("Hello, ", "Bob")) +print(concat("Hello, ", "Coleen")) Traceback (most recent call last): - print(concat("Hello, ", "Bob")) + print(concat("Hello, ", "Coleen")) TypeError: concat() takes 0 positional arguments but 2 were given - - ``` ## Default Argument Values @@ -122,41 +104,41 @@ Default values can be overridden by calling the function with a new argument val ```python # Function with default argument values. ->>> def concat(greeting, name="you", punctuation="!"): -... return f"{greeting}, {name}{punctuation}" -... ->>> print(concat("Hello")) -Hello, you! +def concat(greeting, name="you", punctuation="!"): + return f"{greeting}, {name}{punctuation}" + +print(concat("Hello")) +#-> Hello, you! # Overriding the default values ->>> print(concat("Hello", name="Bob", punctuation=".")) -Hello, Bob. +print(concat("Hello", name="Polly", punctuation=".")) +#-> Hello, Polly. ``` + ## Positional or Keyword Arguments Arguments can be positional or keyword if neither the `/` nor `*` operators are used in the parameter definitions. -Alternately, the positional-or-keyword arguments can be placed between the positional-only parameters on the left and the keyword-only parameters on the right. - -Following is an example of positional-only, positional-or-keyword, and keyword-only arguments: +Alternately, the positional-or-keyword arguments can be placed between the positional-only parameters on the left and the keyword-only parameters on the right: ```python # Position-only argument followed by position-or-keyword, followed by keyword-only. ->>> def concat(greeting, /, name, *, ending): -... return f"{greeting}{name}{ending}" -... ->>> print(concat("Hello, ", "Bob", ending="!")) -Hello, Bob! ->>> print(concat("Hello, ", name="Bob", ending="!")) -Hello, Bob! -... ->>> print(concat(greeting="Hello, ", name="Bob", ending="!")) +def concat(greeting, /, name, *, ending): + return f"{greeting}{name}{ending}" + +print(concat("Hello, ", "Mark", ending="!")) +#-> Hello, Mark! + +print(concat("Hello, ", name="Rachel", ending="!")) +#-> Hello, Rachel! + +print(concat(greeting="Hello, ", name="JoJo", ending="!")) Traceback (most recent call last): - print(concat(greeting="Hello, ", name="Bob", ending="!")) + print(concat(greeting="Hello, ", name="JoJo", ending="!")) TypeError: concat() got some positional-only arguments passed as keyword arguments: 'greeting' - ``` + ## `*args` Code examples will often use a function definition something like the following: @@ -164,132 +146,126 @@ Code examples will often use a function definition something like the following: ```python def my_function(*args, **kwargs): # code snipped - ``` `*args` is a two-part name that represents a `tuple` with an indefinite number of separate positional arguments, also known as a [`variadic argument`][variadic argument]. -`args` is the name given to the `tuple` of arguments, but it could be any other valid Python name, such as `my_args`, `arguments`, etc. +`args` is the name given to the `tuple` of arguments, but it could be any other valid Python name, such as `my_args`, `arguments`, etc. The `*` is the operator which transforms the group of separate arguments into a [`tuple`][tuple]. ~~~~exercism/note -If you have ever [unpacked a tuple][unpack a tuple] you may find the `*` in `*args` to be confusing. +If you have ever [unpacked a tuple][unpack-a-tuple] you may find the `*` in `*args` to be confusing. The `*` in a _parameter_ definition, instead of unpacking a tuple, converts one or more positional arguments _into_ a tuple. -We say that the `*` operator is [overloaded], as it has different behavior in different contexts. +We say that the `*` operator is [_overloaded_][overloading], as it has different behavior in different contexts. For instance, `*` is used for multiplication, it is used for unpacking, and it is used to define an arbitrary number of positional parameters. + +[overloading]: https://therenegadecoder.com/code/abusing-pythons-operator-overloading-feature/ +[unpack-a-tuple]: https://www.geeksforgeeks.org/unpacking-a-tuple-in-python/ ~~~~ -Since a tuple can be iterated, `args` can be passed to any other function which takes an iterable. -Although `*args` is commonly juxtaposed with `**kwargs`, it doesn't have to be. +Since a `tuple` can be iterated over, `args` can be passed to any other function which takes an iterable. +Although `*args` is commonly juxtaposed with `**kwargs`, it doesn't have to be: -Following is an example of an arbitrary number of values being passed to a function: ```python +def add(*args): + # args is passed to the sum function, which iterates over it. + return sum(args) ->>> def add(*args): -# args is passed to the sum function, which takes an iterable -... return sum(args) -... ->>> print(add(1, 2, 3)) -6 +print(add(1, 2, 3)) +#-> 6 ``` -If `*args` follows one or more positional arguments, then `*args` will be what is left over after the positional arguments. +If `*args` follows one or more positional arguments, then `*args` will be what is left over after the positional arguments: -Following is an example of an arbitrary number of values being passed to a function after a positional argument: ```python +def add(first, *args): + # first will be 1, leaving the values 2 and 3 in *args + return first + sum(args) ->>> def add(first, *args): -# first will be 1, leaving the values 2 and 3 in *args -... return first + sum(args) -... ->>> print(add(1, 2, 3)) -6 +print(add(1, 2, 3)) +#-> 6 ``` -If one or more default arguments are defined after `*args` they are separate from the `*args` values. +If one or more [default arguments][default arguments] are defined after `*args`, they are separate from the `*args` values: -To put it all together is an example of an arbitrary number of values being passed to a function that also has a positional argument and a default argument: ```python ->>> def add(first, *args, last=0): -... return first + sum(args) + last -... ->>> print(add(1, 2, 3)) -6 ->>> print(add(1, 2, 3, last=4)) -10 -# This uses the unpacking operator * to separate the list elements into positional arguments. -# It does not have the same behavior as the * in *args. ->>> print(add(*[1, 2, 3])) -6 +def add(first, *args, last=0): + return first + sum(args) + last + +print(add(1, 2, 3)) +#-> 6 + +print(add(1, 2, 3, last=4)) +#-> 10 +# This uses the unpacking operator * to separate the list elements into positional arguments. +# It does not have the same behavior as the * (packing) in *args. +print(add(*[1, 2, 3])) +#-> 6 ``` -Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary number of separate arguments. +Note that when an argument is already inside an `iterable` (_such as a `tuple` or `list`_), it needs to be [_unpacked_][unpacking-and-multiple-assignment] before being passed to a function that takes an arbitrary number of separate arguments. This is accomplished by using `*`, which is the [unpacking operator][unpacking operator]. -`*` in this context _unpacks_ the container into its separate elements which are then transformed by `*args` into a tuple. -Where there are only positional arguments, the unpacking action must result in the same number of arguments as there are formal parameters. +`*` in this context _unpacks_ the container into its separate elements which are then transformed by `*args` into a `tuple`. +Where there are only positional arguments, the unpacking action must result in the same number of arguments as there are formal parameters defined. -Without unpacking the list passed into `add`, the program would error. +Without unpacking the list passed into `add`, the program would error: ```python ->>>> def add(first, *args, last=0): -... return first + sum(args) + last -... ->>>> print(add([1, 2, 3])) +def add(first, *args, last=0): + return first + sum(args) + last + +print(add([1, 2, 3])) Traceback (most recent call last): print(add([1, 2, 3])) return first + sum(args) + last TypeError: can only concatenate list (not "int") to list - ``` + ## `**kwargs` `**kwargs` is a two-part name that represents an indefinite number of separate [key-value pair][key-value] arguments. `kwargs` is the name of the group of arguments and could be any other name, such as `my_args`, `arguments`, etc. The `**` transforms the group of named arguments into a [`dictionary`][dictionary] of `{argument name: argument value}` pairs. -Since a dictionary can be iterated, `kwargs` can be passed to any other function which takes an iterable. -Although `**kwargs` is commonly juxtaposed with `*args`, it doesn't have to be. +Since a dictionary can be iterated over, `kwargs` can be passed to any other function which takes an iterable. +Although `**kwargs` is commonly juxtaposed with `*args`, it doesn't have to be: -Following is an example of an arbitrary number of key-value pairs being passed to a function: ```python ->>> def add(**kwargs): -... return sum(kwargs.values()) -... ->>> print(add(one=1, two=2, three=3)) -6 -``` - -Note that the `dict.values()` method is called to iterate through the `kwargs` dictionary values. +def add(**kwargs): + return sum(kwargs.values()) -When iterating a dictionary the default is to iterate the keys. +print(add(one=1, two=2, three=3)) +#-> 6 +``` -Following is an example of an arbitrary number of key-value pairs being passed to a function that then iterates over `kwargs.keys()`: +Note that the `dict.values()` method is called to iterate through the `kwargs` dictionary `values`. +When iterating over a dictionary, the default is to iterate through the _`keys`_, so `dict.values()` needs to be specified explicitly: ```python ->>> def concat(**kwargs): - # Join concatenates the key names from `kwargs.keys()` -... return " ".join(kwargs) -... ->>> print(concat(one=1, two=2, three=3)) -one two three +def concat(**kwargs): + # Join concatenates the tuples from `kwargs.items()` + return " ".join((str(item) for item in kwargs.items())) +print(concat(one=1, two=2, three=3)) +#-> ('one', 1) ('two', 2) ('three', 3) ``` - [default arguments]: https://www.geeksforgeeks.org/default-arguments-in-python/ -[dictionary]: https://www.w3schools.com/python/python_dictionaries.asp -[function concept]: ../functions/about.md +[dictionary]: https://exercism.org/tracks/python/concepts/dicts +[function concept]: https://exercism.org/tracks/python/concepts/functions [key-value]: https://www.pythontutorial.net/python-basics/python-dictionary/ -[overloaded]: https://www.geeksforgeeks.org/operator-overloading-in-python/ [tuple]: https://www.w3schools.com/python/python_tuples.asp -[unpack a tuple]: https://www.geeksforgeeks.org/unpacking-a-tuple-in-python/ [unpacking operator]: https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists +[unpacking-and-multiple-assignment]: https://exercism.org/tracks/python/concepts/unpacking-and-multiple-assignment [variadic argument]: https://en.wikipedia.org/wiki/Variadic_function +[keyword-arguments]: https://docs.python.org/3/glossary.html#term-argument +[kwargs]: https://docs.python.org/3/glossary.html#term-parameter + diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md index 07b885f332e..e9d3da4d14d 100644 --- a/concepts/function-arguments/introduction.md +++ b/concepts/function-arguments/introduction.md @@ -4,70 +4,89 @@ For the basics on function arguments, please see the [function concept][function ## Parameter Names -Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. +[Parameter names][parameters], like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers. Parameter names should not contain spaces or punctuation. + ## Positional Arguments Positional arguments are values passed to a function in the same order as the parameters which bind to them. -Positional arguments can optionally be passed by using their parameter name. +They can optionally be passed by using their parameter name: -Following is an example of positional arguments being passed by position and by their parameter name: ```python ->>> def concat(greeting, name): -... return f"{greeting}{name}" -... +def concat(greeting, name): + return f"{greeting}{name}" + # Passing data to the function by position. ->>> print(concat("Hello, ", "Bob")) -Hello, Bob -... -# Passing data to the function using the parameter name. ->>> print(concat(name="Bob", greeting="Hello, ")) -Hello, Bob +print(concat("Hello, ", "Judy")) +#-> Hello, Judy +# Passing data to the function using the parameter name. +print(concat(name="Sally", greeting="Hello, ")) +#-> Hello, Sally ``` The first call to concat passes the arguments by position. -The second call to concat passes the arguments by name, allowing their positions to be changed. +The second call to concat passes the arguments by _name_, allowing their positions to be changed. -Note that positional arguments cannot follow keyword arguments. +Note that positional arguments cannot follow arguments passed by name (_also called [keyword arguments][keyword-arguments]. **Not** to be confused with var-positional parameters or [**kwargs][kwargs]_). -This +This set of arguments: ```python ->>> print(concat(greeting="Hello, ", "Bob")) +>>> print(concat(greeting="Hello, ", "Zed")) ``` -results in this error: +will result in this error: ``` SyntaxError: positional argument follows keyword argument ``` +## Default Argument Values + +[Default values][default arguments] for one or more arguments can be supplied in the parameter list. +This allows the function to be called with _fewer_ arguments if needed. +Default values can be overridden by calling the function with new arguments in place of the defaults: + + +```python +# Note the default arguments for both greeting and name. +def concat(greeting="Hello, ", name="you"): + return f"{greeting}{name}" + +# Function call overriding the defaults +print(concat(name="Jerry", greeting="Hello, ")) +#-> Hello, Jerry + +# Function call without arguments resulting in the defaults being used. +print(concat()) +#-> Hello, you +``` + ## Keyword Arguments -Keyword arguments use the parameter name when calling a function. -Keyword arguments can optionally be referred to by position. +Keyword arguments use the parameter name when passing arguments to a function. +They can optionally be referred to by position: -Following is an example of keyword arguments being referred to by their parameter name and by position: ```python ->>> def concat(greeting="Hello, ", name="you"): -... return f"{greeting}{name}" -... +# Note the default arguments for both greeting and name. +def concat(greeting="Hello, ", name="you"): + return f"{greeting}{name}" + # Function call using parameter names as argument keywords. ->>> print(concat(name="Bob", greeting="Hello, ")) -Hello, Bob -... -# Function call with positional data as arguments. ->>> print(concat("Hello, ", name="Bob")) -Hello, Bob ->>> print(concat()) -Hello, you +print(concat(name="Jerry", greeting="Hello, ")) +#-> Hello, Jerry +# Function call with positional data as arguments. +print(concat("Hello, ", "Isaac")) +#-> Hello, Isaac ``` [default arguments]: https://www.geeksforgeeks.org/default-arguments-in-python/ -[function concept]: ../functions/about.md +[function concept]: https://exercism.org/tracks/python/concepts/functions +[keyword-arguments]: https://docs.python.org/3/glossary.html#term-argument +[kwargs]: https://docs.python.org/3/glossary.html#term-parameter [parameters]: https://www.codecademy.com/learn/flask-introduction-to-python/modules/learn-python3-functions/cheatsheet