Автоматизирай Това Начало на книгата

Валидация на данните

Валидирането на входните данни (input validation) е добавянето на код, който проверява дали въведените от потребителя стойности, примерно текст с функцията input(), са в правилния формат. Ако например потребителят трябва да въведе възраст кодът не трябва да приема безмислени отговори като отрицателни стойности(не са в интервала на приемливите стойности) или думи (грешен тип на данните). Валидирането на входните данни може да предотврати бъгове или проблеми със сигурността. Ако се направи функция withdrawFromAccount(), която приема като аргумент сума, която се тегли от сметка, трябва да е сигурно, че сумата е положително число. Ако withdrawFromAccount() извади отрицателно число от сметката, "тегленето" всъщност ще добави пари!

Обикновено валидирането на входните данни става чрез повтарящо се питане на потребителя, докато не въведе валидни данни, като в следващия пример:

while True:
    print('Въведи възраст:')
    age = input()
    try:
        age = int(age)
    except:
        print('Въведи число с цифри.')
        continue
    if age < 1:
        print('Въведи положително число.')
        continue
    break

print(f'Възрстта е {age}')

При изпълнение на програмата може да се получи следният резултат:

Въведи възраст:
пет
Въведи число с цифри.
Въведи възраст:
-2
Въведи положително число.
Въведи възраст:
30
Възрстта е 30

При изпълнението на този код ще се пита за възраст, докато не се въведе валидна. Така със сигурност в променливата age ще има валидна стойности след излизане на while цикъла. Това предпазва от проблеми в програмата по-късно.

Бързо писането на подобен валидиращ код за всеки input() в програмата става досадно. Също така може да се изпуснат определени случаи и невалидна стойност да премине през проверките. В тази глава се описва използването на външния модул PyInputPlus за валидиране на входни данни.

Модулът PyInputPlus

PyInputPlus съдържа функции, подобни на input(), за няколко вида данни : числа, дати, имейл адреси и други. При въвеждане на невалидни данни PyInputPlus иска въвеждане отново, точно както кода в предната секция. PyInputPlus има и други полезни добавки, като определяне на броя на повторенията или време за въвеждане, ако има такова ограничение.

PyInputPlus не е част от стандартната библиотека на Python, така че трябва да се инсталира отделно с помощта на pip. За инсталирането на модула в командния ред се пише pip install --user pyinputplus. В Допълнение А има подробни инструкции за инсталирането на външни модули. В интерактивната конзола може да се провери дали PyInputPlus е инсталиран правилно:

>>> import pyinputplus

Модулът е инсталиран успешно, ако изпълнението на горния ред не води до грешка.

PyInputPlus има функции за различни типове данни:

  • inputStr() е като вградената функция input(), но с всички възможности на PyInputPlus. На тази функция може да се подаде и собствена валидираща функция.
  • inputNum() Осигурява въвеждането на число и връща целочислено или дробно число, според наличието на десетична точка във въведеното число.
  • inputChoice() Осигурява въвеждането на една от посочените стойности
  • inputMenu() Подобна на inputChoice(), но показва меню с номерирани възможности
  • inputDatetime() Осигурява въвеждането на дата и час
  • inputYesNo() Осигурява въвеждането на отговор "yes" или "no"
  • inputBoll() Подобна на inputYesNo(), но приема и "True" и "False", като връща булева стойност
  • inputEmail() Осигурява въвеждането на валиден имейл адрес
  • inputFilepath() Осигурява въвеждането на валиден път и име на файл. Може да провери дали съществува файл с такова име
  • inputPassword() Подобна на вградената input(), но показва символи * вместо написаното, за да не се показва тайна информация, която потребителят въвежда

Тези функции автоматично искат ново въвеждане, докато не се напише валидна стойност:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum()
пет
'пет' is not a number.
42
>>> response
42

Кодът as pyip в import израза спестява писането на pyinputplus при всяко извикване на функция от модула. Вместо това се използва по-краткото име pyip. В примера се вижда, че функциите връщат int или float стойност, а не низ, както input(). Така се получава резултат 42 или 3.14, а не '42' или '3.14'.

На функцията input() може да се подаде низ, който да се използва за подканващ надпис. По същия начин може да се подаде низ на функциите от PyInputPlus, който да се използва за подканващ надпис. Низът се подава като аргумент с име prompt:

>>> response = input('Въведи число:')
Въведи число:42
>>> response
'42'
>>> import pyinputplus as pyip
>>> response = pyip.inputInt(prompt = 'Въведи число:')
Въведи число:котка
'котка' is not an integer.
Въведи число:42
>>> response
42

Python има вградена функция help(), с която може да се разбере повече за някоя друга функция. Например help(pyip.inputChoice) ще покаже допълнителна информация за функцията inputChoice(). Пълната документация на модула се намира на https://pyinputplus.readthedocs.io/ .

Функциите от модула PyInputPlus имат няколко допълнителни възможности, които ги отличават от input().

Именуваните аргументи min, max, greaterThan и lessThan

Функциите, приемащи числа, inputNum(), inputInt() и inputFloat(), имат и именувани аргументи min, max, greaterThan и lessThan, с които да се определи интервал на валидните стойности. Пример от интерактивната конзола:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum('Въведи число: ', min=4)
Въведи число: 3
Number must be at minimum 4.
Въведи число: 4
>>> response
4
>>> response = pyip.inputNum('Въведи число: ', greaterThan=4)
Въведи число: 4
Number must be greater than 4.
Въведи число: 5
>>> response
5
>>> response = pyip.inputNum('Въведи число :', min=4, lessThan=6)
Въведи число :6
Number must be less than 6.
Въведи число :3
Number must be at minimum 4.
Въведи число :4
>>> response
4

Тези аргументи не са задължителни, но могат да помогнат за ограничаване на въведените числа. Числото не може да е по-малко от аргумента min или по-голямо от аргумента max (макар че може да е равно на min или max). Също така числото трябва да е по-голямо от greaterThan и по-малко от lessThan (тоест числото не може да е равно на тях).

Именуваният аргумент blank

По подразбиране не е позволено да се въведе празен стринг. Това може да се промени с подаване на стойност True за аргумента blank:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum('Въведи число: ')
Въведи число: (въведен празен низ с натискане на Enter)
Blank values are not allowed.
Въведи число: 42
>>> response
42
>>> response = pyip.inputNum(blank=True)
(въведен празен низ с натискане на Enter)
>>> response
''

Използването на blank=True позволява добавяне на незадължителни данни, които потребителят може да не въведе.

Именуваните аргументи limit, timeout и default

Функциите от PyInputPlus по подразбиране продължават да питат потребителя, докато не въведе валидни данни (или докато програмата се изпълнява). За промяна на това поведение могат да се използват именуваните аргументи limit и timeout. Подаването на цяло число като limit аргумент определя колко пъти функцията ще се опита да получи валиден отговор от потребителя. Подаването на цяло число като timeout аргумент определя колко време в секунди има потебителят за въвеждане на валиден отговор.

Използването на тези параметри водят до получаване на изключения при липса на валидни данни. Изключенията са съответно RetryLimitException и TimeoutException. Пример от интерактивната конзола :

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(limit=2)
тест
'тест' is not a number.
number
'number' is not a number.
Traceback (most recent call last):
-- изрязани редове --
pyinputplus.RetryLimitException
>>> response = pyip.inputNum(timeout=10)
42(Въведено след повече от 10 секунди)
Traceback (most recent call last):
-- изрязани редове --
pyinputplus.TimeoutException

Ако при използването на тези именувани аргументи се подаде и аргумент default, фунцията връща тази стойност, вместо да предизвиква изключение. Пример от интерактивната конзола:

>>> response = pyip.inputNum(limit=2, default='N/A')
Здравей
'Здравей' is not a number.
свят
'свят' is not a number.
>>> response
'N/A'

В примера функцията inputNum() не предизвиква изключение, а връща стойността по подразбиране - низ 'N/A'.

Именуваните аргументи allowRegexes и blockRegexes

За определяне дали въведените данни са валидни или не може да се използват и регулярни изрази. Именуваните аргументи allowRegexes и blockRegexes приемат списък от регулярни изрази, по които се определя какво функцията смята за валидни данни. Пример от интерактивната конзола, в който inputNum() може приеме и римски числа, осевн нормалните арабски числа:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(allowRegexes=[r'(I|V|X|L|D|M)+', r'zero'])
XLII
>>> response
'XLII'
>>> response = pyip.inputNum(allowRegexes=[r'(i|v|x|l|g|m)+', r'zero'])
xlii
>>> response
'xlii'

Този регулярен израз проверява само въведените букви. Функцията ще приеме и невалидни вимски числа като 'XVX' или 'MILLI', защото регулярния израз r'(I|V|X|L|D|M)+' ги приема.

По подобен начин се задава и списък с регулярни изрази, които функцията да не приема за валидни. За тях се използва именувания аргумент blockRegexes. Пример от интерактивната конзола, в който inputNum() няма да приема четни числа:

>>> import pyinputplus as pyip
>>> response = pyip.inputNum(blockRegexes=[r'[02468]$'])
42
This response is invalid.
44
This response is invalid.
43

Списъкът с позволени регулярни изрази е с предимство пред забранените, когато се използват и двата аргумента. Пример от интерактивната конзола, в който може да се въведе 'насам' и 'натам', но блокира всички останали думи, които съдържат 'на':

>>> import pyinputplus as pyip
>>> response = pyip.inputStr(allowRegexes=[r'насам', r'натам'], blockRegexes=[r'на'])
на
This response is invalid.
навик
This response is invalid.
натам
>>> response
'натам'

Функциите от модула PyInputPlus могат да спестят досадното писане на код за валидиране на данните. Модулът съдържа още много възможности. Неговата документация се намира онлайн на https://pyinputplus.readthedocs.io/

Подкрепете автора чрез покупка на оригиналната книга от No Starch Press или Amazon.