вторник, 8 декабря 2015 г.

Аргументы и возвращаемые значения функций

Некоторые "тонкости" передачи аргументов в функции


Каким образом аргументы передаются в функции, мы рассмотрели в одной из предыдущих тем, но я хочу детализировать некоторые неочевидные моменты.
Дело в том, что передача аргументов в функцию - это присваивание ссылки на объект аргумента для переменной, которая принимает аргумент внутри функции. Давайте вспомним простейший пример присваивания объекта по ссылке:
>>> a = [1,2,3,4,5]
>>> b = a # b ссылается на тотже объект, что и a
>>> a
[1, 2, 3, 4, 5]
>>> b
[1, 2, 3, 4, 5]
>>> a.append(10) # Изменяем объект списка
>>> a
[1, 2, 3, 4, 5, 10]
>>> b
[1, 2, 3, 4, 5, 10]
>>> b.append(20) # Изменяем тотже объект списка
>>> a
[1, 2, 3, 4, 5, 10, 20]
>>> b
[1, 2, 3, 4, 5, 10, 20]
>>>

Это можно представить себе примерно так:
a - ссылается на адрес в памяти (111) - там находится объект списка [1,2,3,4,5]
b = a - присвоить переменной "b", адрес памяти (111) с переменной "a"
a - Ссылается на адрес памяти (111)
b - Ссылается на адрес памяти (111)
Объект списка [1,2,3,4,5], находится по адресу памяти (111)
Мы можем влиять на этот объект через любую из двух переменных: "a", "b", Т.К. Они ссылаются на один и тотже объект.
Примеры упрощены для понимания, но общий принцип именно такой.
В действительности, когда мы передаем аргумент в функцию, переменной аргумента присваивается ссылка на переданный объект:
>>> lst = [1,2,3,4,5]
>>> def addsome(l):
...     l.append(0)
...
>>> lst
[1, 2, 3, 4, 5]
>>> addsome(lst)
>>> lst
[1, 2, 3, 4, 5, 0]
>>> addsome(lst)
>>> lst
[1, 2, 3, 4, 5, 0, 0]
>>>

Мы создали функцию, которой в виде аргумента, передаем список, а функция просто добавляет в список "0".
Это может вызвать недоумение с точки зрения темы областей видимости функций, но насамом деле, тут нет ничего противоречивого. У нас в функции есть локальная переменная "l". Переменная, безусловно, локальная, но ссылается на переданный объект, а значит может его изменять. Если взять наше представление, это будет выглядеть примерно так:
lst - Ссылается на адрес памяти (111)
Объект списка находится по адресу памяти (111)
Мы передаем в виде аргумента в нашу функцию адрес памяти (111)
Переменная аргумента "l" в нашей функции, ссылается на адрес памяти (111)
Все очень просто. Практически мы, присваивая что-то какой-то переменной, не говорим ей, что мы ей присваиваем, а сообщаем, куда ей нужно смотреть, чтобы увидеть необходимые данные.
Основываясь на этом, мы можем утверждать, что если в функцию был передан изменяемый объект, функция может изменять его во время своей работы.

возвращаемые значения функции "return"


Мы утверждали, что возврат значения функции происходит при использовании инструкции "return", а чуть выше, мы увидели, что функция может возвращать какие-то значения при помощи изменения переданных в нее изменяемых объектов. Собственно именно в этом и заключается разница. "return" действительно выполняет возврат какого-то значения из функции, в то время, как изменение переданного изменяемого объекта, не выполняет никакого явного возврата. Также у нас нет возможности по умолчанию отследить какие изменения функция внесла в объект, если оповещение про это не предусмотрено в рамках функции. Чтобы отследить изменения объекта, нам необходимо для начала передать его в функцию, после чего вызвать передаваемый объект, чтобы увидеть изменения, которые внесла в него функция. "return" всегда явно возвращает нам какие-то данные, которые мы можем присвоить какой-то переменной или обработать их, передав в качестве аргумента в другую функцию. Фактически, как мы и говорили, "return" возвращает объект с какими-то данными, а мы уже работаем с этим объектом. Очередной раз напомню вам, что все типы данных, которыми мы оперируем в Python, являются объектами этих типов данных.
чАсто возникает необходимость вернуть несколько значений инструкцией "return". Это  делается очень просто при помощи указания необходимых значений в виде кортежа после инструкции "return":
>>> def myfunc(s):
...     s = s.split('.')
...     return s[0], s[1], s[2]
...
>>> myfunc('19.01.1991')
('19', '01', '1991')
>>> date = myfunc('19.01.1991')
>>> type(date)

>>> date[0]
'19'
>>> date[1]
'01'
>>> date[2]
'1991'
>>>

Наша функция принимает в себя какую-то дату, состоящую из строки с разделителем в виде ".". Методом split(), разбивает по "." строку на список и возвращает в виде отдельных элементов (кортежем).
Еще один пример:
>>> def myfunc(lst, mult):
...     l = []
...     for i in lst:
...             l.append(i*mult)
...     ln = len(l)
...     return l, ln
...
>>> a = [1,2,3,4,5]
>>> myfunc(a, 3)
([3, 6, 9, 12, 15], 5)
>>>

Функция принимает список чисел и число на которое будет умножен каждый элемент списка, затем узнает размерность получившегося списка и возвращает два значения - собственно сам список и его размерность. Сами по себе примеры смысла не несут, но возврат нескольких значений демонстрируют.

Инструкция "global"


Мы уже говорили о том, что все переменные которые мы объявляем внутри функции, являются локальными переменными для функции в которой они были объявлены. Иногда возникает необходимость сделать локальную переменную глобальной. Указав при объявлении функции инструкцию global название_переменной, мы явно объявим переменную глобальной. Как это работает? Есть два следующих варианта:
В глобальной области видимости существует переменная "a";
При объявлении функции, мы указываем "global a";
теперь "a" внутри функции и "a" в глобальном пространстве имен - одна и таже переменная, Т.Е. Если функция будет вносить в эту переменную какие-то изменения, они изменят и глобальную переменную "a".
>>> a = 5 # Глобальная переменная
>>> def myfunc(num1, num2):
...     global a # Теперь "a" внутри функции глобальная переменная
...     a = num1+num2
...
>>> a
5
>>> myfunc(5, 5)
>>> a
10
>>>

Мы объявили переменную "a" в глобальной области видимости. После чего, при объявлении функции, мы указали при помощи инструкции "global", что "a" будет глобальной переменной, Т.Е. наша глобальная "a" и "a" внутри функции, какбы слились в одну переменную. Если бы мы не указали "a" внутри функции, как глобальную переменную, она была бы объявлена внутри функции, как локальная переменная и никак не повлияла бы на глобальную переменную "a".
Есть еще одно свойство у инструкции "global". Если мы объявим какую-то переменную глобальной внутри функции, но переменной с таким именем в глобальной области видимости нет, тогда она будет создана.
>>> res
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'res' is not defined
>>> def myfunc(a,b):
...     global res
...     res = a+b
...
>>> res
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'res' is not defined
>>> myfunc(5,5)
>>> res
10
>>>

Мы не объявляли переменную "res" в глобальной области видимости, но объявили ее при помощи инструкции "global" глобальной при объявлении функции. После непосредственного вызова нашей функции, в глобальной области видимости была создана переменная "res".
Резюмируем: Если мы объявим какую-то переменную глобальной внутри функции и в глобальной области видимости будет существовать переменная с таким же именем, они сольются и функция сможет изменять глобальную переменную. Если же переменной с таким же именем не будет в глобальной области видимости, в таком случае она будет создана.
Используя инструкцию "global" в функциях, мы можем влиять на переменные в глобальной области видимости, но делать это нужно осторожно, чтобы не произвести нежелательных и неявных изменений в структуре нашей программы.

Аргументы со значением по умолчанию


Достаточно часто, возникает необходимость объявить функцию, которая будет принимать несколько аргументов, но какие-то аргументы должны быть заданы изначально. Это то, что мы еще называем опциональными аргументами или необязательными параметрами функции. Чаще всего, это делается в случае, когда есть необходимость принимать какой-то аргумент, но чаще всего, его значение уже известно. При необходимости, явно передав в нашу функцию значение для переменной аргумента, которая имеет значение по умолчанию, мы переопределяем значение по умолчанию. Для того, чтобы задать переменной объявляемой функции какое-то изначальное значение, мы указываем его в круглых скобочках заголовка объявляемой функции в виде "переменная=значение".
Давайте посмотрим на следующий пример:
>>> def myfunc(a, b=2): # b - имеет значение по умолчанию "2"
...     return a*b
...
>>> myfunc(10) # вторым аргументом будет "b", которая имеет значение по умолчанию "2"
20
>>> def(5)
>>> myfunc(5)
10
>>> myfunc(20,3) # Мы явно указали значение для второго аргумента
60
>>> myfunc(3,4)
12
>>>

Аргументов по умолчанию, может быть несколько, но важно помнить, что все аргументы, которые будут иметь значение по умолчанию, должны объявляться последними в списке переменных аргументов функции.
>>> def myfunc(a, b=2, c=10):
...     return a*b+c
...
>>> myfunc(1)
12
>>> myfunc(5,3)
25
>>> myfunc(5,5,5)
30
>>>


Именованные аргументы


Мы всегда имеем возможность при вызове функции, указать каким переменным мы хотим передать значения, просто указав их в виде "переменная=значение":
>>> def myfunc(a, b, c):
...     print u'переменная a = {0}\nПеременная b = {1}\nПеременная c = {2}'.form
at(a,b,c)
...
>>> myfunc(1,2,3)
переменная a = 1
Переменная b = 2
Переменная c = 3
>>> myfunc(b=1, a=2, c=3)
переменная a = 2
Переменная b = 1
Переменная c = 3
>>> myfunc(c=1,b=2,a=3)
переменная a = 3
Переменная b = 2
Переменная c = 1
>>>

Такой способ передачи в функцию, позволяет избегать "перепутывания" передаваемых аргументов или, при наличии нескольких аргументов по умолчанию, указать только одну переменную в которую есть необходимость передать значение. Как было видно из примера, при обращения к именованным аргументам, мы можем указывать их в произвольном порядке.

Передача произвольных коллекции аргументов в функцию


Передача произвольного количества аргументов в виде кортежа


У нас есть замечательная возможность, передавать в функцию произвольное количество аргументов, которые наша функция обернет внутри себя в кортеж. Это удобно, кпримеру, тогда, когда заранее не известно, сколько аргументов будет передаваться в нашу функцию. Для того, чтобы функция приняла аргументы и обернула их в кортеж, перед именем переменной в заголовке объявления функции, нам необходимо поставить звездочку "*". Давайте напишем функцию-аналог функции sum(). Но если функция sum(), непосредственно принимает кортеж или список, мы напишем нашу функцию таким образом, чтобы мы просто передавали произвольное количество аргументов, а функция возвращала их сумму:
>>> def mysum(*args):
...     sum = 0
...     for i in args:
...             sum += i
...     return sum
...
>>> mysum(2,2,2)
6
>>> mysum(1,2,3,4,5)
15
>>> mysum(0)
0
>>>

Мы передаем любое количество аргументов при вызове нашей функции, а функция оборачивает их все в кортеж и работает уже внутри себя с кортежем элементов, где элементы - аргументы, которые мы передали при вызове функции.
При объявлении функции, получение аргументов должно быть составлено следующим образом: (позиционные аргументы, аргументы со значением по умолчанию, *произвольная коллекция аргументов). Тут нужно помнить, что даже если в функции будут аргументы по умолчанию, их всеравно нужно будет указывать явно Т.К. обратиться к имени аргумента, который будет принимать аргументы и сворачивать их в кортеж, нельзя. Если говорить проще, передача именованного аргумента в (*name) функции невозможна:
>>> def myfunc(a,b=2,*c):
...     print a
...     print b
...     print c
...
>>> myfunc(1,2,3,4,5)
1
2
(3, 4, 5)
>>>


Передача произвольного количества именованных аргументов в виде словаря


Также мы можем передавать в функцию произвольную композицию в виде (имя=значение), которую функция свернет внутри себя в словарь, для этого при объявлении функции, перед именем принимающей аргумент переменной, необходимо указать две звездочки "**":
>>> def myfunc(**qwargs):
...     print qwargs
...
>>> myfunc(a=1, b=2, c=3)
{'a': 1, 'c': 3, 'b': 2}
>>>

Также, как и в случае сворачивания функцией аргументов в кортеж, при использовании(**name), функция свернет произвольное количество именованных аргументов в словарь, с которым можно работать внутри функции.
При объявлении функции, необходимо придерживаться следующей последовательности имен аргументов: (позиционные аргументы, аргументы со значением по умолчанию, *аргументы сворачиваемые в кортеж, **аргументы сворачиваемые в словарь):
>>> def myfunc(a,b=2,*args,**qwargs):
...     print a
...     print b
...     print args
...     print qwargs
...
>>> myfunc(1,2,3,4,5,var1=1,var2=2,var3=3,varN='N')
1
2
(3, 4, 5)
{'varN': 'N', 'var1': 1, 'var3': 3, 'var2': 2}
>>>

Как и в случае с (*name), мы не можем передавать аргументы в (**name) по имени переменной, принимающей аргументы.

Комментариев нет:

Отправить комментарий