Понимание args и kwargs в python
Содержание:
- Советы в написании кода
- Long descriptions and proper wrapping and listing
- Understanding *args
- Customizing parser prefixes
- I want the doxygen documentation locally
- How do I use it?
- Group validation is weird. How do I get more helpful output for failed validation?
- Is it developed with regression tests?
- Unpacking With the Asterisk Operators: * & **#
- Refactoring commands
- Custom type parsers (here we use std::tuple)
- How to use double asterisks (**) to unpack dictionaries
- Using the Python kwargs Variable in Function Definitions#
- *args и **kwargs
- Commands
- Concepts¶
- Ключевые аргументы
Советы в написании кода
Одна из самых больших проблем для молодых программистов – это усвоить правило «не повторяй сам себя». Суть в том, что вы не должны писать один и тот же код несколько раз. Когда вы это делаете, вы знаете, что кусок кода должен идти в функцию. Одна из основных причин для этого заключается в том, что вам, вероятно, придется снова изменить этот фрагмент кода в будущем, и если он будет находиться в нескольких местах, вам нужно будет помнить, где все эти местоположения И изменить их.
Копировать и вставлять один и тот же кусок кода – хороший пример спагетти-кода. Постарайтесь избегать этого так часто, как только получится. Вы будете сожалеть об этом в какой-то момент либо потому, что вам придется все это исправлять, либо потому, что вы столкнетесь с чужим кодом, с которым вам придется работать и исправлять вот это вот всё.
Long descriptions and proper wrapping and listing
#include <iostream> #include <args.hxx> int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program with a really long description that is probably going to have to be wrapped across multiple different lines. This is a test to see how the line wrapping works", "This goes after the options. This epilog is also long enough that it will have to be properly wrapped to display correctly on the screen"); args::HelpFlag help(parser, "HELP", "Show this help menu.", {'h', "help"}); args::ValueFlag<std::string> foo(parser, "FOO", "The foo flag.", {'a', 'b', 'c', "a", "b", "c", "the-foo-flag"}); args::ValueFlag<std::string> bar(parser, "BAR", "The bar flag. This one has a lot of options, and will need wrapping in the description, along with its long flag list.", {'d', 'e', 'f', "d", "e", "f"}); args::ValueFlag<std::string> baz(parser, "FOO", "The baz flag. This one has a lot of options, and will need wrapping in the description, even with its short flag list.", {"baz"}); args::Positional<std::string> pos1(parser, "POS1", "The pos1 argument."); args::PositionalList<std::string> poslist1(parser, "POSLIST1", "The poslist1 argument."); args::Positional<std::string> pos2(parser, "POS2", "The pos2 argument."); args::PositionalList<std::string> poslist2(parser, "POSLIST2", "The poslist2 argument."); args::Positional<std::string> pos3(parser, "POS3", "The pos3 argument."); args::PositionalList<std::string> poslist3(parser, "POSLIST3", "The poslist3 argument."); args::Positional<std::string> pos4(parser, "POS4", "The pos4 argument."); args::PositionalList<std::string> poslist4(parser, "POSLIST4", "The poslist4 argument."); args::Positional<std::string> pos5(parser, "POS5", "The pos5 argument."); args::PositionalList<std::string> poslist5(parser, "POSLIST5", "The poslist5 argument."); args::Positional<std::string> pos6(parser, "POS6", "The pos6 argument."); args::PositionalList<std::string> poslist6(parser, "POSLIST6", "The poslist6 argument."); args::Positional<std::string> pos7(parser, "POS7", "The pos7 argument."); args::PositionalList<std::string> poslist7(parser, "POSLIST7", "The poslist7 argument."); args::Positional<std::string> pos8(parser, "POS8", "The pos8 argument."); args::PositionalList<std::string> poslist8(parser, "POSLIST8", "The poslist8 argument."); args::Positional<std::string> pos9(parser, "POS9", "The pos9 argument."); args::PositionalList<std::string> poslist9(parser, "POSLIST9", "The poslist9 argument."); try { parser.ParseCLI(argc, argv); } catch (args::Help) { std::cout << parser; return ; } catch (args::ParseError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } catch (args::ValidationError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } return ; }
% ./test -h ./test {OPTIONS} This is a test program with a really long description that is probably going to have to be wrapped across multiple different lines. This is a test to see how the line wrapping works OPTIONS: -h, --help Show this help menu. -a FOO, -b FOO, -c FOO, --a FOO, --b FOO, --c FOO, --the-foo-flag FOO The foo flag. -d BAR, -e BAR, -f BAR, --d BAR, --e BAR, --f BAR The bar flag. This one has a lot of options, and will need wrapping in the description, along with its long flag list. --baz FOO The baz flag. This one has a lot of options, and will need wrapping in the description, even with its short flag list. POS1 The pos1 argument. POSLIST1 The poslist1 argument. POS2 The pos2 argument. POSLIST2 The poslist2 argument. POS3 The pos3 argument. POSLIST3 The poslist3 argument. POS4 The pos4 argument. POSLIST4 The poslist4 argument. POS5 The pos5 argument. POSLIST5 The poslist5 argument. POS6 The pos6 argument. POSLIST6 The poslist6 argument. POS7 The pos7 argument. POSLIST7 The poslist7 argument. POS8 The pos8 argument. POSLIST8 The poslist8 argument. POS9 The pos9 argument. POSLIST9 The poslist9 argument. "--" can be used to terminate flag options and force all following arguments to be treated as positional options This goes after the options. This epilog is also long enough that it will have to be properly wrapped to display correctly on the screen %
Understanding *args
In Python, the single-asterisk form of can be used as a parameter to send a non-keyworded variable-length argument list to functions. It is worth noting that the asterisk () is the important element here, as the word is the established conventional idiom, though it is not enforced by the language.
Let’s look at a typical function that uses two arguments:
lets_multiply.py
In the code above, we built the function with and as arguments, and then when we call the function, we need to use numbers to correspond with and . In this case, we will pass the integer in for and the integer in for :
lets_multiply.py
Now, we can run the above code:
We’ll receive the following output, showing that the integers 5 and 4 were multiplied as per the function:
What if, later on, we decide that we would like to multiply three numbers rather than just two? If we try to add an additional number to the function, as shown below, we’ll receive an error.
lets_multiply.py
So, if you suspect that you may need to use more arguments later on, you can make use of as your parameter instead.
We can essentially create the same function and code that we showed in the first example, by removing and as function parameters, and instead replacing them with :
lets_multiply.py
When we run this code, we’ll receive the product for each of these function calls:
Because we used to send a variable-length argument list to our function, we were able to pass in as many arguments as we wished into the function calls.
With you can create more flexible code that accepts a varied amount of non-keyworded arguments within your function.
Customizing parser prefixes
dd-style
#include <iostream> #include <args.hxx> int main(int argc, char **argv) { args::ArgumentParser parser("This command likes to break your disks"); parser.LongPrefix(""); parser.LongSeparator("="); args::HelpFlag help(parser, "HELP", "Show this help menu.", {"help"}); args::ValueFlag<long> bs(parser, "BYTES", "Block size", {"bs"}, 512); args::ValueFlag<long> skip(parser, "BYTES", "Bytes to skip", {"skip"}, ); args::ValueFlag<std::string> input(parser, "BLOCK SIZE", "Block size", {"if"}); args::ValueFlag<std::string> output(parser, "BLOCK SIZE", "Block size", {"of"}); try { parser.ParseCLI(argc, argv); } catch (args::Help) { std::cout << parser; return ; } catch (args::ParseError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } catch (args::ValidationError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } std::cout << "bs = " << args::get(bs) << std::endl; std::cout << "skip = " << args::get(skip) << std::endl; if (input) { std::cout << "if = " << args::get(input) << std::endl; } if (output) { std::cout << "of = " << args::get(output) << std::endl; } return ; }
% ./test help ./test {OPTIONS} This command likes to break your disks OPTIONS: help Show this help menu. bs= Block size skip= Bytes to skip if= Block size of= Block size % ./test bs=1024 skip=7 if=/tmp/input bs = 1024 skip = 7 if = /tmp/input
Windows style
The code is the same as above, but the two lines are replaced out:
parser.LongPrefix("/"); parser.LongSeparator(":");
% ./test /help ./test {OPTIONS} This command likes to break your disks OPTIONS: /help Show this help menu. /bs: Block size /skip: Bytes to skip /if: Block size /of: Block size % ./test /bs:72 /skip:87 /if:/tmp/test.txt bs = 72 skip = 87 if = /tmp/test.txt %
I want the doxygen documentation locally
doxygen Doxyfile
Your docs are now in doc/html
How do I use it?
Create an ArgumentParser, modify its attributes to fit your needs, add
arguments through regular argument objects (or create your own), and match them
with an args::Matcher object (check its construction details in the doxygen
documentation.
Then you can either call it with args::ArgumentParser::ParseCLI for the full
command line with program name, or args::ArgumentParser::ParseArgs with
just the arguments to be parsed. The argument and group variables can then be
interpreted as a boolean to see if they’ve been matched.
All variables can be pulled (including the boolean match status for regular
args::Flag variables) with args::get.
Group validation is weird. How do I get more helpful output for failed validation?
This is unfortunately not possible, given the power of the groups available.
For instance, if you have a group validation that works like
, how is this library going to be able to
determine what exactly when wrong when it fails? It only knows that the
entire expression evaluated false, not specifically what the user did wrong
(and this is doubled over by the fact that validation operations are ordinary
functions without any special meaning to the library). As you are the only one
who understands the logic of your program, if you want useful group messages,
you have to catch the ValidationError as a special case and check your own
groups and spit out messages accordingly.
Is it developed with regression tests?
Yes. tests.cxx in the git repository has a set of standard tests (which are
still relatively small in number, but I would welcome some expansion here), and
thanks to Travis CI and AppVeyor, these tests run with every single push:
% make runtests g++ test.cxx -o test.o -I. -std=c++11 -O2 -c -MMD g++ -o test test.o -std=c++11 -O2 ./test =============================================================================== All tests passed (74 assertions in 15 test cases) %
The testing library used is Catch.
Unpacking With the Asterisk Operators: * & **#
You are now able to use and to define Python functions that take a varying number of input arguments. Let’s go a little deeper to understand something more about the unpacking operators.
The single and double asterisk unpacking operators were introduced in Python 2. As of the 3.5 release, they have become even more powerful, thanks to PEP 448. In short, the unpacking operators are operators that unpack the values from iterable objects in Python. The single asterisk operator can be used on any iterable that Python provides, while the double asterisk operator can only be used on dictionaries.
Let’s start with an example:
This code defines a list and then prints it to the standard output:
Note how the list is printed, along with the corresponding brackets and commas.
Now, try to prepend the unpacking operator to the name of your list:
Here, the operator tells to unpack the list first.
In this case, the output is no longer the list itself, but rather the content of the list:
Can you see the difference between this execution and the one from ? Instead of a list, has taken three separate arguments as the input.
Another thing you’ll notice is that in , you used the unpacking operator to call a function, instead of in a function definition. In this case, takes all the items of a list as though they were single arguments.
You can also use this method to call your own functions, but if your function requires a specific number of arguments, then the iterable you unpack must have the same number of arguments.
To test this behavior, consider this script:
Here, explicitly states that , , and are required arguments.
If you run this script, you’ll get the sum of the three numbers in :
The 3 elements in match up perfectly with the required arguments in .
Now look at the following script, where has 4 arguments instead of 3:
In this example, still expects just three arguments, but the operator gets 4 items from the list. If you try to execute this script, you’ll see that the Python interpreter is unable to run it:
When you use the operator to unpack a list and pass arguments to a function, it’s exactly as though you’re passing every single argument alone. This means that you can use multiple unpacking operators to get values from several lists and pass them all to a single function.
To test this behavior, consider the following example:
If you run this example, all three lists are unpacked. Each individual item is passed to , resulting in the following output:
There are other convenient uses of the unpacking operator. For example, say you need to split a list into three different parts. The output should show the first value, the last value, and all the values in between. With the unpacking operator, you can do this in just one line of code:
In this example, contains 6 items. The first variable is assigned to , the last to , and all other values are packed into a new list . If you run the script, will show you that your three variables have the values you would expect:
Another interesting thing you can do with the unpacking operator is to split the items of any iterable object. This could be very useful if you need to merge two lists, for instance:
The unpacking operator is prepended to both and .
If you run this script, you’ll see that the result is a merged list:
You can even merge two different dictionaries by using the unpacking operator :
Here, the iterables to merge are and .
Executing this code outputs a merged dictionary:
Remember that the operator works on any iterable object. It can also be used to unpack a string:
In Python, strings are iterable objects, so will unpack it and place all individual values in a list :
The previous example seems great, but when you work with these operators it’s important to keep in mind the seventh rule of The Zen of Python by Tim Peters: Readability counts.
To see why, consider the following example:
There’s the unpacking operator , followed by a variable, a comma, and an assignment. That’s a lot packed into one line! In fact, this code is no different from the previous example. It just takes the string and assigns all the items to the new list , thanks to the unpacking operator .
The comma after the does the trick. When you use the unpacking operator with variable assignment, Python requires that your resulting variable is either a list or a tuple. With the trailing comma, you have actually defined a tuple with just one named variable .
While this is a neat trick, many Pythonistas would not consider this code to be very readable. As such, it’s best to use these kinds of constructions sparingly.
Refactoring commands
#include <iostream> #include "args.hxx" args::Group arguments("arguments"); args::ValueFlag<std::string> gitdir(arguments, "path", "", {"git-dir"}); args::HelpFlag h(arguments, "help", "help", {'h', "help"}); args::PositionalList<std::string> pathsList(arguments, "paths", "files to commit"); void CommitCommand(args::Subparser &parser) { args::ValueFlag<std::string> message(parser, "MESSAGE", "commit message", {'m'}); parser.Parse(); std::cout << "Commit"; for (auto &&path : pathsList) { std::cout << ' ' << path; } std::cout << std::endl; if (message) { std::cout << "message: " << args::get(message) << std::endl; } } int main(int argc, const char **argv) { args::ArgumentParser p("git-like parser"); args::Group commands(p, "commands"); args::Command add(commands, "add", "add file contents to the index", (args::Subparser &parser) { parser.Parse(); std::cout << "Add"; for (auto &&path : pathsList) { std::cout << ' ' << path; } std::cout << std::endl; }); args::Command commit(commands, "commit", "record changes to the repository", &CommitCommand); args::GlobalOptions globals(p, arguments); try { p.ParseCLI(argc, argv); } catch (args::Help) { std::cout << p; } catch (args::Error& e) { std::cerr << e.what() << std::endl << p; return 1; } return ; }
% ./test -h ./test COMMAND {OPTIONS} git-like parser OPTIONS: commands add add file contents to the index commit record changes to the repository arguments --git-dir= -h, --help help paths... files "--" can be used to terminate flag options and force all following arguments to be treated as positional options % ./test add 1 2 Add 1 2 % ./test commit -m "my commit message" 1 2 Commit 1 2 message: my commit message
Custom type parsers (here we use std::tuple)
#include <iostream> #include <tuple> std::istream& operator>>(std::istream& is, std::tuple<int, int>& ints) { is >> std::get<>(ints); is.get(); is >> std::get<1>(ints); return is; } #include <args.hxx> struct DoublesReader { void operator()(const std::string &name, const std::string &value, std::tuple<double, double> &destination) { size_t commapos = ; std::get<>(destination) = std::stod(value, &commapos); std::get<1>(destination) = std::stod(std::string(value, commapos + 1)); } }; int main(int argc, char **argv) { args::ArgumentParser parser("This is a test program."); args::Positional<std::tuple<int, int>> ints(parser, "INTS", "This takes a pair of integers."); args::Positional<std::tuple<double, double>, DoublesReader> doubles(parser, "DOUBLES", "This takes a pair of doubles."); try { parser.ParseCLI(argc, argv); } catch (args::Help) { std::cout << parser; return ; } catch (args::ParseError e) { std::cerr << e.what() << std::endl; std::cerr << parser; return 1; } if (ints) { std::cout << "ints found: " << std::get<>(args::get(ints)) << " and " << std::get<1>(args::get(ints)) << std::endl; } if (doubles) { std::cout << "doubles found: " << std::get<>(args::get(doubles)) << " and " << std::get<1>(args::get(doubles)) << std::endl; } return ; }
% ./test -h Argument could not be matched: 'h' ./test This is a test program. OPTIONS: INTS This takes a pair of integers. DOUBLES This takes a pair of doubles. % ./test 5 ints found: 5 and 0 % ./test 5,8 ints found: 5 and 8 % ./test 5,8 2.4,8 ints found: 5 and 8 doubles found: 2.4 and 8 % ./test 5,8 2.4, terminate called after throwing an instance of 'std::invalid_argument' what(): stod zsh: abort ./test 5,8 2.4, % ./test 5,8 2.4 terminate called after throwing an instance of 'std::out_of_range' what(): basic_string::basic_string: __pos (which is 4) > this->size() (which is 3) zsh: abort ./test 5,8 2.4 % ./test 5,8 2.4-7 ints found: 5 and 8 doubles found: 2.4 and 7 % ./test 5,8 2.4,-7 ints found: 5 and 8 doubles found: 2.4 and -7
As you can see, with your own types, validation can get a little weird. Make
sure to check and throw a parsing error (or whatever error you want to catch)
if you can’t fully deduce your type. The built-in validator will only throw if
there are unextracted characters left in the stream.
How to use double asterisks (**) to unpack dictionaries
We apply on any dict-like object, by placing it to the left of that object. This produces the individual key-value pairs of the iterable. The order of the key-value pairs produced is in the order you would get as if you iterated the dict.
my_dict = {"oranges":4, "mangoes":5, "tomatoes":7, "bananas":6}unpacked_dict = {**my_dict}# print unpacked dictionaryprint(unpacked_dict)
NB: The single and double asterisk can also be used to combine lists and dictionaries respectively. Run the code below to see how this is achieved.
Merging lists
list_1 = list_2 = final_list = print(final_list)
Merging dictionaries
dict_1 = {"mangoes":4, "apples":5, "bananas":6}dict_2 = {"lemons":7, "carrots":"None"}dict_3 = {"bread":2, "tomatoes":15}final_dict = {**dict_1, **dict_2, **dict_3}print(final_dict)
Using the Python kwargs Variable in Function Definitions#
Okay, now you’ve understood what is for, but what about ? works just like , but instead of accepting positional arguments it accepts keyword (or named) arguments. Take the following example:
When you execute the script above, will iterate through the Python kwargs dictionary and concatenate all the values it finds:
Like , is just a name that can be changed to whatever you want. Again, what is important here is the use of the unpacking operator ().
So, the previous example could be written like this:
Note that in the example above the iterable object is a standard . If you iterate over the dictionary and want to return its values, like in the example shown, then you must use .
In fact, if you forget to use this method, you will find yourself iterating through the keys of your Python kwargs dictionary instead, like in the following example:
Now, if you try to execute this example, you’ll notice the following output:
*args и **kwargs
Вы также можете настроить функцию на прием любого количества аргументов, или ключевых аргументов, при помощи особого синтаксиса. Чтобы получить бесконечное количество аргументов, мы используем *args, а чтобы получить бесконечное количество ключевых аргументов, мы используем *kwargs. Сами слова “args” и “kwargs” не так важны. Это просто сокращение. Вы можете назвать их *lol и *omg, и они будут работать таким же образом. Главное здесь – это количество звездочек
Обратите внимание: в дополнение к конвенциям *args и *kwargs, вы также, время от времени, будете видеть andkw. Давайте взглянем на следующий пример:
Python
def many(*args, **kwargs):
print( args )
print( kwargs )
many(1, 2, 3, name=»Mike», job=»programmer»)
# Результат:
# (1, 2, 3)
# {‘job’: ‘programmer’, ‘name’: ‘Mike’}
1 |
defmany(*args,**kwargs) print(args) print(kwargs) many(1,2,3,name=»Mike»,job=»programmer») |
Сначала мы создали нашу функцию, при помощи нового синтаксиса, после чего мы вызвали его при помощи трех обычных аргументов, и двух ключевых аргументов. Функция показывает нам два типа аргументов. Как мы видим, параметр args превращается в кортеж, а kwargs – в словарь. Вы встретите такой тип кодинга, если взгляните на исходный код Пайтона, или в один из сторонних пакетов Пайтон.
Commands
#include <iostream> #include <args.hxx> int main(int argc, char **argv) { args::ArgumentParser p("git-like parser"); args::Group commands(p, "commands"); args::Command add(commands, "add", "add file contents to the index"); args::Command commit(commands, "commit", "record changes to the repository"); args::Group arguments(p, "arguments", args::Group::Validators::DontCare, args::Options::Global); args::ValueFlag<std::string> gitdir(arguments, "path", "", {"git-dir"}); args::HelpFlag h(arguments, "help", "help", {'h', "help"}); args::PositionalList<std::string> pathsList(arguments, "paths", "files to commit"); try { p.ParseCLI(argc, argv); if (add) { std::cout << "Add"; } else { std::cout << "Commit"; } for (auto &&path : pathsList) { std::cout << ' ' << path; } std::cout << std::endl; } catch (args::Help) { std::cout << p; } catch (args::Error& e) { std::cerr << e.what() << std::endl << p; return 1; } return ; }
% ./test -h ./test COMMAND {OPTIONS} git-like parser OPTIONS: commands add add file contents to the index commit record changes to the repository arguments --git-dir= -h, --help help paths... files "--" can be used to terminate flag options and force all following arguments to be treated as positional options % ./test add 1 2 Add 1 2
Concepts¶
Let’s show the sort of functionality that we are going to explore in this
introductory tutorial by making use of the ls command:
$ ls cpython devguide prog.py pypy rm-unused-function.patch $ ls pypy ctypes_configure demo dotviewer include lib_pypy lib-python ... $ ls -l total 20 drwxr-xr-x 19 wena wena 4096 Feb 18 18:51 cpython drwxr-xr-x 4 wena wena 4096 Feb 8 12:04 devguide -rwxr-xr-x 1 wena wena 535 Feb 19 00:05 prog.py drwxr-xr-x 14 wena wena 4096 Feb 7 00:59 pypy -rw-r--r-- 1 wena wena 741 Feb 18 01:01 rm-unused-function.patch $ ls --help Usage: ls ... ... List information about the FILEs (the current directory by default). Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. ...
A few concepts we can learn from the four commands:
Ключевые аргументы
Функции также могут принимать ключевые аргументы. Более того, они могут принимать как регулярные, так и ключевые аргументы. Это значит, что вы можете указывать, какие ключевые слова будут ключевыми, и передать их функции. Это было в примере выше.
Python
def keyword_function(a=1, b=2):
return a+b
print( keyword_function(b=4, a=5) ) # 9
1 |
defkeyword_function(a=1,b=2) returna+b print(keyword_function(b=4,a=5))# 9 |
Вы также можете вызвать данную функцию без спецификации ключевых слов. Эта функция также демонстрирует концепт аргументов, используемых по умолчанию. Каким образом? Попробуйте вызвать функцию без аргументов вообще!
Python
keyword_function() # 3
1 | keyword_function()# 3 |
Функция вернулась к нам с числом 3. Почему? Причина заключается в том, что а и b по умолчанию имеют значение 1 и 2 соответственно. Теперь попробуем создать функцию, которая имеет обычный аргумент, и несколько ключевых аргументов:
Python
def mixed_function(a, b=2, c=3):
return a+b+c
mixed_function(b=4, c=5)
Traceback (most recent call last):
File «<string>», line 1, in <fragment>
TypeError: mixed_function() takes at least 1 argument (2 given)
1 |
defmixed_function(a,b=2,c=3) returna+b+c mixed_function(b=4,c=5) Traceback(most recent call last) File»<string>»,line1,in<fragment> TypeErrormixed_function()takes at least1argument(2given) |
Python
print( mixed_function(1, b=4, c=5) ) # 10
print( mixed_function(1) ) # 6
1 |
print(mixed_function(1,b=4,c=5))# 10 print(mixed_function(1))# 6 |
Выше мы описали три возможных случая. Проанализируем каждый из них. В первом примере мы попробовали вызвать функцию, используя только ключевые аргументы. Это дало нам только ошибку. Traceback указывает на то, что наша функция принимает, по крайней мере, один аргумент, но в примере было указано два аргумента. Что же произошло? Дело в том, что первый аргумент необходим, потому что он ни на что не указывает, так что, когда мы вызываем функцию только с ключевыми аргументами, это вызывает ошибку. Во втором примере мы вызвали смешанную функцию, с тремя значениями, два из которых имеют название. Это работает, и выдает нам ожидаемый результат: 1+4+5=10. Третий пример показывает, что происходит, если мы вызываем функцию, указывая только на одно значение, которое не рассматривается как значение по умолчанию. Это работает, если мы берем 1, и суммируем её к двум значениям по умолчанию: 2 и 3, чтобы получить результат 6! Удивительно, не так ли?