Написання функцій

Огляд

Викладання: 10 хв
Вправи: 15 хв
Питання
  • Як я можу створити власні функції?

Цілі
  • Пояснити і визначитие різницю між визначенням функції та викликом функції.

  • Написати функцію, яка приймає невелику фіксовану кількість аргументів і видає єдиний результат.

Розбийте програми на функції, щоб їх було легше зрозуміти.

Визначте функцію за допомогою def з назвою, параметрами та блоком коду.”

def print_greeting():
    print('Hello!')

Визначення функції не запускає її.

print_greeting()
Hello!

Аргументи у виклику зіставляються з параметрами у визначенні.

def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)

print_date(1871, 3, 19)
1871/3/19

Або ми можемо назвати аргументи під час виклику функції, що дозволяє нам це зробити вказати їх у довільному порядку:

print_date(month=3, day=19, year=1871)
1871/3/19

Функції можуть повертати результат до свого виклику за допомогою return.

def average(values):
    if len(values) == 0:
        return None
    return sum(values) / len(values)
a = average([1, 3, 4])
print('середнє фактичних значень:', a)
середнє фактичних значень: 2.6666666666666665
print('середнє порожнього списку:', average([]))
середнє порожнього списку: None
result = print_date(1871, 3, 19)
print('результат виклику є таким:', result)
1871/3/19
результат виклику є таким: None

Виявлення синтаксичних помилок

  1. Прочитайте наведений нижче код і спробуйте визначити, у чому полягають помилки без запуску.
  2. Запустіть код і прочитайте повідомлення про помилку. Це SyntaxError чи IndentationError?
  3. Виправте помилку.
  4. Повторюйте кроки 2 та 3 доки не виправите всі помилки.
def another_function
print("Syntax errors are annoying.")
print("But at least python tells us about them!")
print("So they are usually not too hard to fix.")

Рішення

def another_function():
print("Синтаксичні помилки дратують.")
print("Але принаймні Python розповідає нам про них!")
print("Тож їх зазвичай не надто важко виправити.")

Визначення та використання

Що друкує наступна програма?

def report(pressure):
    print('тиск', pressure)

print('виклик', report, 22.5)

Рішення

calling <function report at 0x7fd128ff1bf8> 22.5

Для виклику функції завжди потрібні круглі дужки, інакше ви отримаєте адресу пам’яті об’єкта функції. Отже, якщо ми хочемо викликати функцію під назвою report і надати їй значення 22,5 для звіту, ми могли б викликати нашу функцію так

print("виклик")
report(22.5)

Порядок виконання операцій

Приклад вище:

result = print_date(1871, 3, 19)
print('результат виклику:', result)

надруковано:

1871/3/19
результат виклику: None

Поясніть, чому два рядки виводу з’явилися в такому порядку.

Що не так у цьому прикладі?

result = print_date(1871,3,19)

def print_date(year, month, day):
   joined = str(year) + '/' + str(month) + '/' + str(day)
   print(joined)

Рішення

  1. Перший рядок виводу (1871/3/19) є результатом функції друку всередині print_date(), тоді як другий рядок з функції друку під викликом функції. Весь код всередині print_date() виконується спочатку, а потім програма “залишає” функцію та виконує решту коду.
  2. Проблема з прикладом полягає в тому, що функція визначається після виклику функції. Тому Python не розуміє виклик функції.

Інкапсуляція

Заповніть порожні поля, щоб створити функцію, яка приймає одне ім’я файлу як аргумент, завантажує дані у файл, названий аргументом, і повертає мінімальне значення цих даних.

import pandas as pd

def min_in_data(____):
    data = ____
    return ____

Рішення

import pandas as pd

def min_in_data(filename):
    data = pd.read_csv(filename)
    return data.min()

Знайди Перший

Заповніть порожні поля, щоб створити функцію, яка приймає список чисел як аргумент і повертає перше від’ємне значення в списку. Що робить ваша функція, якщо список порожній?

def first_negative(values):
    for v in ____:
        if ____:
            return ____

Рішення

def first_negative(values):
    for v in values:
        if v<0:
            return v

Якщо цій функції передається порожній список, вона повертає None:

my_list = []
print(first_negative(my_list))
None

Виклик по імені

Раніше ми розглядали таку функцію:

def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)

Ми побачили, що можна викликати функцію за допомогою іменованих аргументів, наприклад:

print_date(day=1, month=2, year=2003)
  1. Що друкує print_date(day=1, month=2, year=2003)?
  2. Коли ви раніше бачили подібний виклик функції?
  3. Коли і чому корисно викликати функції таким чином?

Рішення

  1. 2003/2/1
  2. Ми бачили приклади використання іменованих аргументів під час роботи з бібліотекою pandas. Наприклад, під час читання в наборі даних using data = pd.read_csv('data/gapminder_gdp_europe.csv', index_col='country'), останній аргумент index_col є іменованим аргументом.
  3. Використання іменованих аргументів може зробити код більш читабельним, оскільки з виклику функції можна побачити, які імена мають різні аргументи всередині функції. Це також може зменшити ймовірність передачі аргументів у неправильному порядку, оскільки за допомогою іменованих аргументів порядок не має значення.

Інкапсуляція блоку If/Print

Наведений нижче код запускатиметься на принтері етикеток для курячих яєць. Цифрові ваги повідомлять комп’ютеру про масу курячого яйця (у грамах), а потім комп’ютер друкує етикетку.

Будь ласка, перепишіть код так, щоб if-блок був вкладений у функцію.

 import random
 for i in range(10):

    # імітація маси курячого яйця
    # (випадкова) маса становитиме 70 +/- 20 грамів
    mass=70+20.0*(2.0*random.random()-1.0)

    print(mass)
   
    #машини для сортування яєць друкують етикетку
    if(mass>=85):
       print("джамбо")
    elif(mass>=70):
       print("велике")
    elif(mass<70 and mass>=55):
       print("середнє")
    else:
       print("мале")

Далі спрощена програма. Яке визначення функції зробить його функціональним?

 # Адаптована версія
 import random
 for i in range(10):

    # імітація маси курячого яйця
    # (випадкова) маса становитиме 70 +/- 20 грамів
    mass=70+20.0*(2.0*random.random()-1.0)

    print(mass,print_egg_label(mass))    

  1. Створіть визначення функції для print_egg_label(), яка працюватиме з адаптованою програмою вище. Зверніть увагу, що чисельне значення, яке повертає функція, буде важливим. Зразок оформлення результату може мати вигляд 71.23 large.
  2. Брудне яйце може мати масу понад 90 грамів, а зіпсоване чи розбите яйце, ймовірно, матиме масу менше 50 грамів. Змініть свою функцію print_egg_label() для врахування цих умов помилки. Вихідний зразок може бути 25 занадто легке, можливо, зіпсоване.

Рішення

def print_egg_label(mass):
    #egg sizing machinery prints a label
    if(mass>=90):
        return("попередження: яйце може бути брудним")
    elif(mass>=85):
        return("джамбо")
    elif(mass>=70):
        return("велике")
    elif(mass<70 and mass>=55):
        return("середнє")
    elif(mass<50):
        return("занадто легке, можливо, зіпсоване")
    else:
        return("мале")

Інкапсуляція аналізу даних

Припустімо, що наступний код було виконано:

import pandas as pd

df = pd.read_csv('data/gapminder_gdp_asia.csv', index_col=0)
japan = df.loc['Japan']

1.Заповніть наведені нижче твердження, щоб отримати середній ВВП Японії за роками, зазначеними у 1980-х роках.

year = 1983
gdp_decade = 'gdpPercap_' + str(year // ____)
avg = (japan.loc[gdp_decade + ___] + japan.loc[gdp_decade + ___]) / 2

2.Абстрагуйте код вище в одну функцію.

def avg_gdp_in_decade(country, continent, year):
    df = pd.read_csv('data/gapminder_gdp_'+___+'.csv',delimiter=',',index_col=0)
    ____
    ____
    ____
    return avg

3.Як би ви узагальнили цю функцію якщо ви не знали заздалегідь, які конкретні роки зустрічаються як стовпці в даних? Наприклад, що, якби ми також мали дані за роки, що закінчуються на 1 і 9 для кожного десятиліття? (Підказка: використовуйте стовпці, щоб відфільтрувати ті, що відповідають десятиліттям, замість того, щоб перелічувати їх у коді.)

Рішення

1.

year = 1983
gdp_decade = 'gdpPercap_' + str(year // 10)
avg = (japan.loc[gdp_decade + '2'] + japan.loc[gdp_decade + '7']) / 2

2.

def avg_gdp_in_decade(country, continent, year):
    df = pd.read_csv('data/gapminder_gdp_' + continent + '.csv', index_col=0)
    c = df.loc[country]
    gdp_decade = 'gdpPercap_' + str(year // 10)
    avg = (c.loc[gdp_decade + '2'] + c.loc[gdp_decade + '7'])/2
    return avg

3.

Нам потрібно переглянути звітні роки для отримання середнього значення для відповідних у даних.

def avg_gdp_in_decade(country, continent, year):
    df = pd.read_csv('data/gapminder_gdp_' + continent + '.csv', index_col=0)
    c = df.loc[country]
    gdp_decade = 'gdpPercap_' + str(year // 10)
    total = 0.0
    num_years = 0
    for yr_header in c.index: # c's index contains reported years
        if yr_header.startswith(gdp_decade):
            total = total + c.loc[yr_header]
            num_years = num_years + 1
    return total/num_years

Функцію тепер можна викликати:

avg_gdp_in_decade('Japan','asia',1983)
20880.023800000003

Моделювання динамічної системи

У математиці динамічна система це система, у якій функція описує залежність точки в геометричному просторі від часу. Канонічним прикладом динамічної системи є система під назвою [логістичне відображення] (https://en.wikipedia.org/wiki/Logistic_map).

  1. Визначте функцію під назвою logistic_map, яка приймає два входи: x, що представляє стан системи в момент часу t, і параметр r. Ця функція має повертати значення, що представляє стан системи в момент часу t+1.

  2. Використовуючи цикл for, повторіть функцію logistic_map, визначену в частині 1, починаючи з початкової умови 0,5 для періодівt_final=10, 100, і 1000. Зберігайте проміжні результати в списку, щоб після завершення циклу for ви накопичили послідовність значень, що представляють стан logistic_map в моменти часу t=0,1,…,t_final.

  3. Інкапсулюйте логіку вашого циклу for у функцію під назвою iterate, яка приймає початкову умову як перший вхід, параметр t_final як другий вхід і параметр r як третій вхід. Функція має повертати список значень, що представляють стан логістичної карти в момент часу t=0,1,…,t_final.

Рішення

1.

def logistic_map(x, r):
    return r * x * (1 - x)

2.

initial_condition = 0.5
t_final = 10
r = 1.0
trajectory = [initial_condition]
for t in range(1, t_final):
    trajectory.append( logistic_map(trajectory[t-1], r) )

3.

def iterate(initial_condition, t_final, r):
    trajectory = [initial_condition]
    for t in range(1, t_final):
        trajectory.append( logistic_map(trajectory[t-1], r) )
    return trajectory

Ключові моменти

  • Розбийте програми на функції, щоб їх було легше зрозуміти.

  • Визначте функцію за допомогою def з назвою, параметрами та блоком коду.

  • Визначення функції не запускає її.

  • Аргументи у виклику зіставляються з параметрами у визначенні.

  • Функції можуть повертати результат своєго виклику за допомогою return.