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

0
204
views

Изучая документацию Python для собственного удовольствия, можно найти трюки, которые обязательно ускользнут от вашего внимания в программистской рутине. Сайт proglib.io в своем переводе статьи «Hidden Gems of Python» рассказал о наиболее любопытных из них.

У меня появилось новое времяпрепровождение – чтение документации 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 – это не только полезный язык, но и весьма интересный. Мы все чем-то заняты в повседневной жизни, но это не должно нам мешать лучше изучить язык ради саморазвития. Удачи! Не останавливайтесь на достигнутом!

python logo

Хочешь знать больше о Python?

Подпишись на наш канал о Python в Telegram!

×

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here