eFusion 23 апреля 2021

🐍 Скрытые сокровища Python

Изучая документацию Python для собственного удовольствия, можно найти трюки, которые обязательно ускользнут от вашего внимания в программистской рутине. Рассказываем о наиболее любопытных из них.
2

Перевод публикуется с сокращениями, автор оригинальной статьи Charudatta Manwatkar.

У меня появилось новое времяпрепровождение – чтение документации Python просто для удовольствия. Когда читаете что-то на досуге, вы склонны замечать интересные лакомые кусочки, которые могли пропустить. Итак, вот список лакомых кусочков, которые заставили меня подумать: «О! Вы можете сделать это на Python?»

1. Атрибуты функций

Подобно тому, как устанавливаются атрибуты классов и объектов, можно устанавливать и атрибуты функций.

        def func(x):
    intermediate_var = x**2 + x + 1

    if intermediate_var % 2:
        y = intermediate_var ** 3
    else:
        y = intermediate_var **3 + 1

    # setting attributes here
    func.optional_return = intermediate_var
    func.is_awesome = 'Yes, my function is awesome.'

    return y

y = func(3)
print('Final answer is', y)

# Accessing function attributes
print('Show calculations -->', func.optional_return)
print('Is my function awesome? -->', func.is_awesome)
    

Мы установили атрибуты optional_return и is_awesome в 10-й и 11-й строках соответственно, а также получили доступ к ним вне функции в строках 19 и 20. Вывод программы будет следующим:

        Final answer is 2197
Show calculations --> 13
Is my function awesome? --> Yes, my function is awesome.
    
Это удобно, когда вы хотите, чтобы опция извлекала некоторую промежуточную переменную, но не возвращала ее явно с помощью оператора return каждый раз, когда вызывается функция. Также обратите внимание, что атрибуты могут быть заданы как внутри определения функции, так и снаружи.

2. Цикл For-else

В Python вы можете добавить else в цикл for. Условие else будет срабатывать только в том случае, если во время выполнения в теле цикла не был обнаружен оператор break.

        my_list = ['some', 'list', 'containing', 'five', 'elements']

min_len = 3

for element in my_list:
    if len(element) < min_len:
        print(f'Caught an element shorter than {min_len} letters')
        break
else:
    print(f'All elements at least {min_len} letters long')
    
        All elements at least 3 letters long
    

Обратите внимание, что здесь ни один элемент не имеет длины меньше 3, т. е. оператор break никогда не выполнится. Следовательно условие else будет срабатывать (после выполнения цикла for) и печатать показанный выше вывод.

Этого можно достичь с помощью отдельной переменной, которая отслеживает, был ли встречен оператор break. Не исключено, что это также будет менее запутанным для читающего ваш код человека. Добиться результата можно следующим образом:

        my_list = ['some', 'list', 'containing', 'five', 'elements']

min_len = 3

no_break = True
for element in my_list:
    if len(element) < min_len:
        print(f'Caught an element shorter than {min_len} letters')
        no_break = False
        break

if no_break:
    print(f'All elements at least {min_len} letters long')
    

3. Разделители для INT

Трудно визуально различить целые числа, вроде 10000000 и 100000000. Мы не можем использовать здесь запятые, как в обычном (английском – прим. ред.) языке, потому что Python будет интерпретировать его как кортеж из нескольких целых чисел.

У Python есть очень удобный способ борьбы с этим: можно использовать подчеркивание в качестве разделителя для улучшения читабельности. Таким образом 1_000_000 будет интерпретироваться как целочисленное значение:

        a = 3250
b = 67_543_423_778

print(type(a))
print(type(b))
print(type(a)==type(b))
    
        <class 'int'>
<class 'int'>
True
    

4. eval() и exec()

Python имеет возможность динамически считывать строку и обрабатывать ее как часть программного кода. Это достигается с помощью функций eval() и exec() (eval предназначена для вычисления выражений, а exec – для выполнения операторов).

        a = 3

b = eval('a + 2')
print('b =', b)

exec('c = a ** 2')
print('c is', c)
    
        b = 5
c is 9
    

В строке 3 функция eval() считывает входную строку как выражение Python, вычисляет ее и присваивает результат переменной b. В строке 6 функция exec() считывает входную строку, как оператор Python и выполняет ее.

Вы даже можете передавать динамически созданные строки этим функциям. Например, можно создать 1000 переменных с именами x_0, x_1,..., x_999 без необходимости вручную записывать каждое из этих объявлений переменных в код. Кому-то подобный способ покажется совершенно бессмысленным, но это не так.

В более широком контексте программирования, а не только в Python, использование eval/exec дает крутой результат, потому что они позволяют писать динамический код, использующий доступную в рантайме информацию для решения проблем, которых может не быть при компиляции. Exec – это почти встроенный в Python интерпретатор. Если у вас есть трудная задача, один из способов решения – использовать именно его. Узнать больше об этих инструментах можно из другой статьи.

5. Ellipsis

Ellipsis или ... – это встроенная константа Python, аналогичная встроенным константам типа None, True, False и т. д. Она используется различными способами. Разберем некоторые.

5.1 Плейсхолдер

Подобно pass, Ellipsis может использоваться в качестве плейсхолдера, когда код не полностью написан, но требует некоторого заполнения:

        def some_function():
    ...
    
def another_function():
    pass
    

5.2 Альтернатива «None»

None применяется, когда вы хотите обозначить пустой инпут или return, но иногда ни один из них не подходит, и в этом случае заполнителем может служить многоточие:

        # вычисление nth odd
def nth_odd(n):
    if isinstance(n, int):
        return 2 * n - 1
    else:
        return None


# вычисление исходных данных n в nth odd
def original_num(m=...):
    if m is ...:
        print('This function needs some input')
    elif m is None:
        print('Non integer input provided to nth_odd() function')
    elif isinstance(m, int):
        if m % 2:
            print(f'{m} is {int((m + 1)/2)}th odd number')
        else:
            print(f'{m} is not an odd number')


original_num()

a = nth_odd(n='some string')
original_num(a)

b = nth_odd(5)
original_num(b)

original_num(16)
    

Функция nth_odd() вычисляет энное нечетное число. Функция original_num() вычисляет исходное число n, учитывая энное нечетное число. Здесь None является одним из ожидаемых входных данных функции original_num(), поэтому мы не можем использовать его в качестве плейсхолдера по умолчанию для аргумента m. Вывод кода будет следующим:

        This function needs some input
Non integer input provided to nth_odd() function
9 is 5th odd number
16 is not an odd number
    

5.3 Нарезка массива в NumPy

NumPy использует многоточие для среза массива. Следующий код показывает два эквивалентных способа нарезки массива NumPy:

        import numpy as np

a = np.arange(16).reshape(2,2,2,2)

print(a[..., 0].flatten())
print(a[:, :, :, 0].flatten())
    
        [ 0  2  4  6  8 10 12 14]
[ 0  2  4  6  8 10 12 14]
    

Таким образом ... сообщает о наличии стольких ':', сколько необходимо.

Заключение

В статье мы рассмотрели следующие интересные особенности, которые можно использовать в следующих целях:

  • атрибуты функций – для присваивания атрибутов функциям точно так же, как и объектам;
  • циклы for-else – для отслеживания того, был ли цикл выполнен без оператора break;
  • разделители int – для удобочитаемости больших целочисленных значений, вроде 32_534_478;
  • eval() и exec() – для считывания строковых значений как обычного кода Python с последующим запуском;
  • ellipsis – для применения универсальной встроенной константы.

Python – это не только полезный язык, но и весьма интересный. Мы все чем-то заняты в повседневной жизни, но это не должно нам мешать лучше изучить язык ради саморазвития. Удачи! Не останавливайтесь на достигнутом!

Дополнительные материалы:

Источники

МЕРОПРИЯТИЯ

Комментарии 2

ВАКАНСИИ

Добавить вакансию
Android developer
Москва, по итогам собеседования
QA Backend Specialist
от 150000 RUB до 230000 RUB
Business development manager (Game dev)
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ

BUG