--- language: Python 2 (legacy) lang: uk-ua contributors: - ["Louie Dinh", "http://ldinh.ca"] - ["Amin Bandali", "https://aminb.org"] - ["Andre Polykanine", "https://github.com/Oire"] - ["evuez", "http://github.com/evuez"] - ["asyne", "https://github.com/justblah"] - ["habi", "http://github.com/habi"] translators: - ["Oleh Hromiak", "https://github.com/ogroleg"] filename: learnpython-ua.py --- Мову Python створив Гвідо ван Россум на початку 90-х. Наразі це одна з найбільш популярних мов. Я закохався у Python завдяки простому і зрозумілому синтаксису. Це майже як виконуваний псевдокод. З вдячністю чекаю ваших відгуків: [@louiedinh](http://twitter.com/louiedinh) або louiedinh [at] [поштовий сервіс від Google] Примітка: Ця стаття стосується Python 2.7, проте має працювати і у інших версіях Python 2.x. Python 2.7 підходить до кінця свого терміну, його підтримку припинять у 2020, тож наразі краще починати вивчення Python з версії 3.x. Аби вивчити Python 3.x, звертайтесь до статті по Python 3. ```python # Однорядкові коментарі починаються з символу решітки. """ Текст, що займає декілька рядків, може бути записаний з використанням 3 знаків " і зазвичай використовується у якості вбудованої документації """ #################################################### ## 1. Примітивні типи даних та оператори #################################################### # У вас є числа 3 # => 3 # Математика працює досить передбачувано 1 + 1 # => 2 8 - 1 # => 7 10 * 2 # => 20 35 / 5 # => 7 # А ось з діленням все трохи складніше. Воно цілочисельне і результат # автоматично округлюється у меншу сторону. 5 / 2 # => 2 # Аби правильно ділити, спершу варто дізнатися про числа # з плаваючою комою. 2.0 # Це число з плаваючою комою 11.0 / 4.0 # => 2.75 ох... Так набагато краще # Результат цілочисельного ділення округлюється у меншу сторону # як для додатніх, так і для від'ємних чисел. 5 // 3 # => 1 5.0 // 3.0 # => 1.0 # Працює і для чисел з плаваючою комою -5 // 3 # => -2 -5.0 // 3.0 # => -2.0 # Зверніть увагу, що ми також можемо імпортувати модуль для ділення, # див. розділ Модулі # аби звичне ділення працювало при використанні лише '/'. from __future__ import division 11 / 4 # => 2.75 ...звичне ділення 11 // 4 # => 2 ...цілочисельне ділення # Залишок від ділення 7 % 3 # => 1 # Піднесення до степеня 2 ** 4 # => 16 # Приорітет операцій вказується дужками (1 + 3) * 2 # => 8 # Логічні оператори # Зверніть увагу: ключові слова «and» і «or» чутливі до регістру букв True and False # => False False or True # => True # Завважте, що логічні оператори також використовуються і з цілими числами 0 and 2 # => 0 -5 or 0 # => -5 0 == False # => True 2 == True # => False 1 == True # => True # Для заперечення використовується not not True # => False not False # => True # Рівність — це == 1 == 1 # => True 2 == 1 # => False # Нерівність — це != 1 != 1 # => False 2 != 1 # => True # Ще трохи порівнянь 1 < 10 # => True 1 > 10 # => False 2 <= 2 # => True 2 >= 2 # => True # Порівняння можуть бути записані ланцюжком! 1 < 2 < 3 # => True 2 < 3 < 2 # => False # Рядки позначаються символом " або ' "Це рядок." 'Це теж рядок.' # І рядки також можна додавати! "Привіт " + "світ!" # => "Привіт світ!" # Рядки можна додавати і без '+' "Привіт " "світ!" # => "Привіт світ!" # ... або множити "Привіт" * 3 # => "ПривітПривітПривіт" # З рядком можна працювати як зі списком символів "Це рядок"[0] # => 'Ц' # Ви можете дізнатися довжину рядка len("Це рядок") # => 8 # Символ % використовується для форматування рядків, наприклад: "%s можуть бути %s" % ("рядки", "інтерпольовані") # Новий спосіб форматування рядків — використання методу format. # Це бажаний спосіб. "{} є {}".format("Це", "заповнювач") "{0} можуть бути {1}".format("рядки", "форматовані") # Якщо ви не хочете рахувати, то можете скористатися ключовими словами. "{name} хоче з'істи {food}".format(name="Боб", food="лазанью") # None - це об'єкт None # => None # Не використовуйте оператор рівності '=='' для порівняння # об'єктів з None. Використовуйте для цього «is» "etc" is None # => False None is None # => True # Оператор 'is' перевіряє ідентичність об'єктів. Він не # дуже корисний при роботі з примітивними типами, проте # незамінний при роботі з об'єктами. # None, 0 і порожні рядки/списки рівні False. # Всі інші значення рівні True bool(0) # => False bool("") # => False #################################################### ## 2. Змінні та колекції #################################################### # В Python є оператор print print "Я Python. Приємно познайомитись!" # => Я Python. Приємно познайомитись! # Отримати дані з консолі просто input_string_var = raw_input( "Введіть щось: ") # Повертає дані у вигляді рядка input_var = input("Введіть щось: ") # Працює з даними як з кодом на python # Застереження: будьте обережні при використанні методу input() # Оголошувати змінні перед ініціалізацією не потрібно. some_var = 5 # За угодою використовується нижній_регістр_з_підкресленнями some_var # => 5 # При спробі доступу до неініціалізованої змінної # виникне виняткова ситуація. # Див. розділ Потік управління, аби дізнатись про винятки більше. some_other_var # Помилка в імені # if може використовуватися як вираз # Такий запис еквівалентний тернарному оператору '?:' у мові С "yahoo!" if 3 > 2 else 2 # => "yahoo!" # Списки зберігають послідовності li = [] # Можна одразу створити заповнений список other_li = [4, 5, 6] # Об'єкти додаються у кінець списку за допомогою методу append li.append(1) # li тепер дорівнює [1] li.append(2) # li тепер дорівнює [1, 2] li.append(4) # li тепер дорівнює [1, 2, 4] li.append(3) # li тепер дорівнює [1, 2, 4, 3] # І видаляються з кінця методом pop li.pop() # => повертає 3 і li стає рівним [1, 2, 4] # Повернемо елемент назад li.append(3) # li тепер знову дорівнює [1, 2, 4, 3] # Поводьтесь зі списком як зі звичайним масивом li[0] # => 1 # Присвоюйте нові значення вже ініціалізованим індексам за допомогою = li[0] = 42 li[0] # => 42 li[0] = 1 # Зверніть увагу: повертаємось до попереднього значення # Звертаємось до останнього елементу li[-1] # => 3 # Спроба вийти за границі масиву призводить до помилки в індексі li[4] # помилка в індексі # Можна звертатися до діапазону, використовуючи так звані зрізи # (Для тих, хто любить математику: це називається замкнуто-відкритий інтервал). li[1:3] # => [2, 4] # Опускаємо початок li[2:] # => [4, 3] # Опускаємо кінець li[:3] # => [1, 2, 4] # Вибираємо кожен другий елемент li[::2] # => [1, 4] # Перевертаємо список li[::-1] # => [3, 4, 2, 1] # Використовуйте суміш вищеназваного для більш складних зрізів # li[початок:кінець:крок] # Видаляємо довільні елементи зі списку оператором del del li[2] # li тепер [1, 2, 3] # Ви можете додавати списки li + other_li # => [1, 2, 3, 4, 5, 6] # Зверніть увагу: значення li та other_li при цьому не змінились. # Поєднувати списки можна за допомогою методу extend li.extend(other_li) # Тепер li дорівнює [1, 2, 3, 4, 5, 6] # Видалити перше входження значення li.remove(2) # Тепер li дорівнює [1, 3, 4, 5, 6] li.remove(2) # Помилка значення, оскільки у списку li немає 2 # Вставити елемент за вказаним індексом li.insert(1, 2) # li знову дорівнює [1, 2, 3, 4, 5, 6] # Отримати індекс першого знайденого елементу li.index(2) # => 1 li.index(7) # Помилка значення, оскільки у списку li немає 7 # Перевірити елемент на входження у список можна оператором in 1 in li # => True # Довжина списку обчислюється за допомогою функції len len(li) # => 6 # Кортежі схожі на списки, лише незмінні tup = (1, 2, 3) tup[0] # => 1 tup[0] = 3 # Виникає помилка типу # Все те ж саме можна робити і з кортежами len(tup) # => 3 tup + (4, 5, 6) # => (1, 2, 3, 4, 5, 6) tup[:2] # => (1, 2) 2 in tup # => True # Ви можете розпаковувати кортежі (або списки) у змінні a, b, c = (1, 2, 3) # a == 1, b == 2 и c == 3 d, e, f = 4, 5, 6 # дужки можна опустити # Кортежі створюються за замовчуванням, якщо дужки опущено g = 4, 5, 6 # => (4, 5, 6) # Дивіться, як легко обміняти значення двох змінних e, d = d, e # тепер d дорівнює 5, а e дорівнює 4 # Словники містять асоціативні масиви empty_dict = {} # Ось так описується попередньо заповнений словник filled_dict = {"one": 1, "two": 2, "three": 3} # Значення можна отримати так само, як і зі списку filled_dict["one"] # => 1 # Можна отримати всі ключі у виді списку за допомогою методу keys filled_dict.keys() # => ["three", "two", "one"] # Примітка: збереження порядку ключів у словників не гарантується # Ваші результати можуть не співпадати з цими. # Можна отримати і всі значення у вигляді списку, використовуйте метод values filled_dict.values() # => [3, 2, 1] # Те ж зауваження щодо порядку ключів діє і тут # Отримуйте всі пари ключ-значення у вигляді списку кортежів # за допомогою "items()" filled_dict.items() # => [("one", 1), ("two", 2), ("three", 3)] # За допомогою оператору in можна перевіряти ключі на входження у словник "one" in filled_dict # => True 1 in filled_dict # => False # Спроба отримати значення за неіснуючим ключем викине помилку ключа filled_dict["four"] # помилка ключа # Аби уникнути цього, використовуйте метод get() filled_dict.get("one") # => 1 filled_dict.get("four") # => None # Метод get також приймає аргумент за замовчуванням, значення якого буде # повернуто при відсутності вказаного ключа filled_dict.get("one", 4) # => 1 filled_dict.get("four", 4) # => 4 # Зверніть увагу, що filled_dict.get("four") все ще => None # (get не встановлює значення елементу словника) # Присвоюйте значення ключам так само, як і в списках filled_dict["four"] = 4 # тепер filled_dict["four"] => 4 # Метод setdefault() вставляє пару ключ-значення лише # за відсутності такого ключа filled_dict.setdefault("five", 5) # filled_dict["five"] повертає 5 filled_dict.setdefault("five", 6) # filled_dict["five"] все ще повертає 5 # Множини містять... ну, загалом, множини # (які схожі на списки, проте в них не може бути елементів, які повторюються) empty_set = set() # Ініціалізація множини набором значень some_set = set([1,2,2,3,4]) # some_set тепер дорівнює set([1, 2, 3, 4]) # Порядок не гарантовано, хоча інколи множини виглядають відсортованими another_set = set([4, 3, 2, 2, 1]) # another_set тепер set([1, 2, 3, 4]) # Починаючи з Python 2.7, ви можете використовувати {}, аби створити множину filled_set = {1, 2, 2, 3, 4} # => {1, 2, 3, 4} # Додавання нових елементів у множину filled_set.add(5) # filled_set тепер дорівнює {1, 2, 3, 4, 5} # Перетин множин: & other_set = {3, 4, 5, 6} filled_set & other_set # => {3, 4, 5} # Об'єднання множин: | filled_set | other_set # => {1, 2, 3, 4, 5, 6} # Різниця множин: - {1,2,3,4} - {2,3,5} # => {1, 4} # Симетрична різниця множин: ^ {1, 2, 3, 4} ^ {2, 3, 5} # => {1, 4, 5} # Перевіряємо чи множина зліва є надмножиною множини справа {1, 2} >= {1, 2, 3} # => False # Перевіряємо чи множина зліва є підмножиною множини справа {1, 2} <= {1, 2, 3} # => True # Перевірка на входження у множину: in 2 in filled_set # => True 10 in filled_set # => False #################################################### ## 3. Потік управління #################################################### # Для початку створимо змінну some_var = 5 # Так виглядає вираз if. Відступи у python дуже важливі! # результат: «some_var менше, ніж 10» if some_var > 10: print("some_var набагато більше, ніж 10.") elif some_var < 10: # Вираз elif є необов'язковим. print("some_var менше, ніж 10.") else: # Це теж необов'язково. print("some_var дорівнює 10.") """ Цикли For проходять по спискам Результат: собака — це ссавець кішка — це ссавець миша — це ссавець """ for animal in ["собака", "кішка", "миша"]: # Можете використовувати оператор {0} для інтерполяції форматованих рядків print "{0} — це ссавець".format(animal) """ "range(число)" повертає список чисел від нуля до заданого числа Друкує: 0 1 2 3 """ for i in range(4): print(i) """ "range(нижня_границя, верхня_границя)" повертає список чисел від нижньої границі до верхньої Друкує: 4 5 6 7 """ for i in range(4, 8): print i """ Цикли while продовжуються до тих пір, поки вказана умова не стане хибною. Друкує: 0 1 2 3 """ x = 0 while x < 4: print(x) x += 1 # Короткий запис для x = x + 1 # Обробляйте винятки блоками try/except # Працює у Python 2.6 і вище: try: # Аби створити виняток, використовується raise raise IndexError("Помилка у індексі!") except IndexError as e: pass # pass — оператор, який нічого не робить. Зазвичай тут відбувається # відновлення після помилки. except (TypeError, NameError): pass # Винятки можна обробляти групами, якщо потрібно. else: # Необов'язковий вираз. Має слідувати за останнім блоком except print("Все добре!") # Виконається лише якщо не було ніяких винятків finally: # Виконується у будь-якому випадку print "Тут ми можемо звільнити ресурси" # Замість try/finally для звільнення ресурсів # ви можете використовувати вираз with with open("myfile.txt") as f: for line in f: print line #################################################### ## 4. Функції #################################################### # Використовуйте def для створення нових функцій def add(x, y): print "x дорівнює {0}, а y дорівнює {1}".format(x, y) return x + y # Повертайте результат за допомогою ключового слова return # Виклик функції з аргументами add(5, 6) # => друкує «x дорівнює 5, а y дорівнює 6» і повертає 11 # Інший спосіб виклику функції — виклик з іменованими аргументами add(y=6, x=5) # Іменовані аргументи можна вказувати у будь-якому порядку # Ви можете визначити функцію, яка приймає змінну кількість аргументів, # які будуть інтерпретовані як кортеж, за допомогою * def varargs(*args): return args varargs(1, 2, 3) # => (1,2,3) # А також можете визначити функцію, яка приймає змінне число # іменованих аргументів, котрі будуть інтерпретовані як словник, за допомогою ** def keyword_args(**kwargs): return kwargs # Давайте подивимось що з цього вийде keyword_args(big="foot", loch="ness") # => {"big": "foot", "loch": "ness"} # Якщо хочете, можете використовувати обидва способи одночасно def all_the_args(*args, **kwargs): print(args) print(kwargs) """ all_the_args(1, 2, a=3, b=4) друкує: (1, 2) {"a": 3, "b": 4} """ # Коли викликаєте функції, то можете зробити навпаки! # Використовуйте символ * аби розпакувати позиційні аргументи і # ** для іменованих аргументів args = (1, 2, 3, 4) kwargs = {"a": 3, "b": 4} all_the_args(*args) # еквівалентно foo(1, 2, 3, 4) all_the_args(**kwargs) # еквівалентно foo(a=3, b=4) all_the_args(*args, **kwargs) # еквівалентно foo(1, 2, 3, 4, a=3, b=4) # ви можете передавати довільне число позиційних або іменованих аргументів # іншим функціям, які їх приймають, розпаковуючи за допомогою # * або ** відповідно def pass_all_the_args(*args, **kwargs): all_the_args(*args, **kwargs) print varargs(*args) print keyword_args(**kwargs) # Область визначення функцій x = 5 def set_x(num): # Локальна змінна x - не те ж саме, що глобальна змінна x x = num # => 43 print x # => 43 def set_global_x(num): global x print x # => 5 x = num # глобальна змінна x тепер дорівнює 6 print x # => 6 set_x(43) set_global_x(6) # В Python функції є об'єктами першого класу def create_adder(x): def adder(y): return x + y return adder add_10 = create_adder(10) add_10(3) # => 13 # Також є і анонімні функції (lambda x: x > 2)(3) # => True (lambda x, y: x ** 2 + y ** 2)(2, 1) # => 5 # Присутні вбудовані функції вищого порядку map(add_10, [1, 2, 3]) # => [11, 12, 13] map(max, [1, 2, 3], [4, 2, 1]) # => [4, 2, 3] filter(lambda x: x > 5, [3, 4, 5, 6, 7]) # => [6, 7] # Для зручного відображення і фільтрації можна використовувати # включення у вигляді списків [add_10(i) for i in [1, 2, 3]] # => [11, 12, 13] [x for x in [3, 4, 5, 6, 7] if x > 5] # => [6, 7] # Ви також можете скористатися включеннями множин та словників {x for x in 'abcddeef' if x in 'abc'} # => {'a', 'b', 'c'} {x: x ** 2 for x in range(5)} # => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} #################################################### ## 5. Класи #################################################### # Аби отримати клас, ми наслідуємо object. class Human(object): # Атрибут класу. Він розділяється всіма екземплярами цього класу. species = "H. sapiens" # Звичайний конструктор, буде викликаний при ініціалізації екземпляру класу # Зверніть увагу, що подвійне підкреслення на початку та наприкінці імені # використовується для позначення об'єктів та атрибутів, # які використовуються Python, але знаходяться у просторах імен, # якими керує користувач. Не варто вигадувати для них імена самостійно. def __init__(self, name): # Присвоєння значення аргумента атрибуту класу name self.name = name # Ініціалізуємо властивість self.age = 0 # Метод екземпляру. Всі методи приймають self у якості першого аргументу def say(self, msg): return "%s: %s" % (self.name, msg) # Методи класу розділяються між усіма екземплярами # Вони викликаються з вказанням викликаючого класу # у якості першого аргументу @classmethod def get_species(cls): return cls.species # Статичний метод викликається без посилання на клас або екземпляр @staticmethod def grunt(): return "*grunt*" # Властивість. # Перетворює метод age() в атрибут тільки для читання # з таким же ім'ям. @property def age(self): return self._age # Це дозволяє змінювати значення властивості @age.setter def age(self, age): self._age = age # Це дозволяє видаляти властивість @age.deleter def age(self): del self._age # Створюємо екземпляр класу i = Human(name="Данило") print(i.say("привіт")) # Друкує: «Данило: привіт» j = Human("Меланка") print(j.say("Привіт")) # Друкує: «Меланка: привіт» # Виклик методу класу i.get_species() # => "H. sapiens" # Зміна розділюваного атрибуту Human.species = "H. neanderthalensis" i.get_species() # => "H. neanderthalensis" j.get_species() # => "H. neanderthalensis" # Виклик статичного методу Human.grunt() # => "*grunt*" # Оновлюємо властивість i.age = 42 # Отримуємо значення i.age # => 42 # Видаляємо властивість del i.age i.age # => виникає помилка атрибуту #################################################### ## 6. Модулі #################################################### # Ви можете імпортувати модулі import math print(math.sqrt(16)) # => 4.0 # Ви можете імпортувати окремі функції з модуля from math import ceil, floor print(ceil(3.7)) # => 4.0 print(floor(3.7)) # => 3.0 # Можете імпортувати всі функції модуля. # Попередження: краще так не робіть from math import * # Можете скорочувати імена модулів import math as m math.sqrt(16) == m.sqrt(16) # => True # Ви також можете переконатися, що функції еквівалентні from math import sqrt math.sqrt == m.sqrt == sqrt # => True # Модулі в Python — це звичайні Python-файли. Ви # можете писати свої модулі та імпортувати їх. Назва # модуля співпадає з назвою файлу. # Ви можете дізнатися, які функції та атрибути визначені # в модулі import math dir(math) # Якщо у вас є Python скрипт з назвою math.py у тій же папці, що # і ваш поточний скрипт, то файл math.py # може бути завантажено замість вбудованого у Python модуля. # Так трапляється, оскільки локальна папка має перевагу # над вбудованими у Python бібліотеками. #################################################### ## 7. Додатково #################################################### # Генератори # Генератор "генерує" значення тоді, коли вони запитуються, замість того, # щоб зберігати все одразу # Метод нижче (*НЕ* генератор) подвоює всі значення і зберігає їх # в `double_arr`. При великих розмірах може знадобитися багато ресурсів! def double_numbers(iterable): double_arr = [] for i in iterable: double_arr.append(i + i) return double_arr # Тут ми спочатку подвоюємо всі значення, потім повертаємо їх, # аби перевірити умову for value in double_numbers(range(1000000)): # `test_non_generator` print value if value > 5: break # Натомість ми можемо скористатися генератором, аби "згенерувати" # подвійне значення, як тільки воно буде запитане def double_numbers_generator(iterable): for i in iterable: yield i + i # Той самий код, але вже з генератором, тепер дозволяє нам пройтися по # значенням і подвоювати їх одне за одним якраз тоді, коли вони обробляються # за нашою логікою, одне за одним. А як тільки ми бачимо, що value > 5, ми # виходимо з циклу і більше не подвоюємо більшість значень, # які отримали на вхід (НАБАГАТО ШВИДШЕ!) for value in double_numbers_generator(xrange(1000000)): # `test_generator` print value if value > 5: break # Між іншим: ви помітили використання `range` у `test_non_generator` і # `xrange` у `test_generator`? # Як `double_numbers_generator` є версією-генератором `double_numbers`, так # і `xrange` є аналогом `range`, але у вигляді генератора. # `range` поверне нам масив з 1000000 значень # `xrange`, у свою чергу, згенерує 1000000 значень для нас тоді, # коли ми їх запитуємо / будемо проходитись по ним. # Аналогічно включенням у вигляді списків, ви можете створювати включення # у вигляді генераторів. values = (-x for x in [1, 2, 3, 4, 5]) for x in values: print(x) # друкує -1 -2 -3 -4 -5 # Включення у вигляді генератора можна явно перетворити у список values = (-x for x in [1, 2, 3, 4, 5]) gen_to_list = list(values) print(gen_to_list) # => [-1, -2, -3, -4, -5] # Декоратори # Декоратор – це функція вищого порядку, яка приймає та повертає функцію. # Простий приклад використання – декоратор add_apples додає елемент 'Apple' в # список fruits, який повертає цільова функція get_fruits. def add_apples(func): def get_fruits(): fruits = func() fruits.append('Apple') return fruits return get_fruits @add_apples def get_fruits(): return ['Banana', 'Mango', 'Orange'] # Друкуємо список разом з елементом 'Apple', який знаходиться в ньому: # Banana, Mango, Orange, Apple print ', '.join(get_fruits()) # У цьому прикладі beg обертає say # Beg викличе say. Якщо say_please дорівнюватиме True, то повідомлення, # що повертається, буде змінено. from functools import wraps def beg(target_function): @wraps(target_function) def wrapper(*args, **kwargs): msg, say_please = target_function(*args, **kwargs) if say_please: return "{} {}".format(msg, "Будь ласка! Я бідний :(") return msg return wrapper @beg def say(say_please=False): msg = "Ви можете купити мені пива?" return msg, say_please print say() # Ви можете купити мені пива? print say(say_please=True) # Ви можете купити мені пива? Будь ласка! Я бідний :( ``` ## Готові до більшого? ### Безкоштовні онлайн-матеріали * [Learn Python The Hard Way](http://learnpythonthehardway.org/book/) * [Dive Into Python](http://www.diveintopython.net/) * [Официальная документация](http://docs.python.org/2.6/) * [Hitchhiker's Guide to Python](http://docs.python-guide.org/en/latest/) * [Python Module of the Week](http://pymotw.com/2/) * [A Crash Course in Python for Scientists](http://nbviewer.ipython.org/5920182) ### Платні * [Programming Python](http://www.amazon.com/gp/product/0596158106/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596158106&linkCode=as2&tag=homebits04-20) * [Dive Into Python](http://www.amazon.com/gp/product/1441413022/ref=as_li_tf_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1441413022&linkCode=as2&tag=homebits04-20) * [Python Essential Reference](http://www.amazon.com/gp/product/0672329786/ref=as_li_tf_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0672329786&linkCode=as2&tag=homebits04-20)