Метка: питон

Троеточие

Троеточие. Оно же многоточие, если по правилам, как утверждала наша учительница русского языка в школе и ставила нам двойки. Что тут сказать? Для некоторых все, что больше двух – уже много.

В питоне есть такая вещь:

...

Да это три простые точки подряд без пробелов.

Зачем она нужна и что это такое? Это Ellipsis, по-русски – оно самое «…точие». По-научному – это литерал встроенной константы (Ellipsis). Типа есть True, False, None, … и Ellipsis.

>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>
>>> bool(...)
True

Т. е. троеточие это типа такое значение, которое вычисляется в константу Ellipsis, которая сама по себе равна себе же, имеет класс ellipsis и может быть приведена к bool=True, если надо.

Зачем она нужна? Да черт ее знает… Не припомню мест, где она используется в стандартной библиотеке.

Зато можно креативно использовать ее там, где у нас пустое тело класса или оператора (вместо pass).

class Test:
    pass

# красивее будет
# типа пока здесь пусто, но продолжение следует. загадочный и многозначительный код

class Test:
    ...

# или

if x == 5:
   ...  # я сюда еще чего-нить допишу потом
else: 
   print('not 5')

Можно троеточие передавать как значение аргумента ф-ции. Пример:

def foo(x):
    if x is Ellipsis:
        print('mda.....')
    else:
        print('x =', x)

foo(6)    # напечатает: x = 6
foo(...)  # напечатает: mda.....

Реальные применения троеточия встречаются в библиотеке для математических вычислений numpy.

Там он позволяет выбрать из многомерного массива подмассив, используя полную выборку по тем измерениям, которые не указаны конкретно. Сам не понял, что сказал, тут надо быть математиком!

В примере ниже у нас 4-х мерный массив. Мы фиксируем первый и последний индексы, а 2-й и 3-й будут выбраны полностью.

n[1,…,1] – эквивалент n[1,:,:,1]

>>> n = numpy.arange(16).reshape(2, 2, 2, 2)
>>> n
array([[[[ 0,  1],
         [ 2,  3]],

        [[ 4,  5],
         [ 6,  7]]],


       [[[ 8,  9],
         [10, 11]],

        [[12, 13],
         [14, 15]]]])
>>> n[1,...,1]            # equivalent to n[1,:,:,1]
array([[ 9, 11],
       [13, 15]])
>>> # also Ellipsis object can be used interchangeably
>>> n[1, Ellipsis, 1]
array([[ 9, 11],
       [13, 15]])

Не больше, чем синтаксический сахар, да и тот со привкусом стевии.

Еще одно редкое применение (с версии питона 3.5) – многоточие может пригодиться в подсказках для типов.

а) Для указание типа кортежа неопределенной длины с однородными типами элементов:

Tuple[int, ...]

б) Для указания типа вызываемого объекта, когда сигнатура параметров неизвестна. Пример:

def partial(func: Callable[..., str], *args) -> Callable[..., str]:
    # Body

Это, конечно, уже экзотика.

Может быть у тебя, мой милый читатель, есть идеи, как еще применить…

 

 

Defaultdict

Возьмем обычный питоновский dict. Определяем его мы так:

x = dict()

# или лучше

x = {}

Что будет, если мы обратимся к несуществующему элементу?

>>> x['abc']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'abc'

Возникает исключение KeyError. Можно попробовать отловить его конструкцией try-except, что выглядит и работает достаточно громоздко, можно проверить вхождение ключа операцией in или, наконец, воспользоваться методом get (в котором, кстати, мы можем указать значение по умолчанию, если ключа нет).

# плохо...
try:
    v = x['abc']
except:
    v = 'no value'

# лучше
v = x['abc'] if 'abc' in x else 'no value'

# еще лучше
v = x.get('abc', 'no value')

Это хорошо, но можно еще лучше!

Рассмотрим пример: допустим у нас есть задача по каждому ключевому слову из текста составить список позиций этого ключевого слова (номер слова) в тексте.

text = """
We develop a methodology for automatically analyzing text to aid in discriminating firms that encounter catastrophic 
financial events. The dictionaries we create from Management Discussion and Analysis Sections (MD&A) of 10-Ks 
discriminate fraudulent from non-fraudulent firms 75% of the time and bankrupt from nonbankrupt firms 80% of the 
time. Our results compare favorably with quantitative prediction methods. We further test for complementarities by 
merging quantitative data with text data. We achieve our best prediction results for both bankruptcy (83.87%) and 
fraud (81.97%) with the combined data, showing that that the text of the MD&A complements the quantitative financial 
information.
"""

key_words = [
    "quantitative",
    "results",
    "automatically"
]

# решение не претендует на общую эффективность, сделано для демонстрации

def solution1(text, keywords):
    # разбивка текста на слова и удаление лишних символов
    all_words = map(lambda word: word.strip(' .)(%\n').lower(), text.split(' '))

    kw_map = {}

    for word_no, word in enumerate(all_words):
        if word in key_words:
            if word in kw_map:
                kw_map[word].append(word_no)
            else:
                kw_map[word] = [word_no]

    return kw_map

print(solution1(text, key_words))

Нам понадобилось 4 строки, чтобы понять, есть ли уже такое слово словаре, если нет, то создать новый ключ со значением из списка с одним элементом, иначе добавить позицию в существующий список. Конечно, можно было сразу создать словарь, где ключи – ключевые слова, а значения – пустые списки, но есть и более элегантный способ – использовать defaultdict из модуля collection из стандартной библиотеки. Не зря мы в программировании всегда стремимся к простоте.

from collections import defaultdict

...

    kw_map = defaultdict(list)

    for word_no, word in enumerate(all_words):
        if word in key_words:
            kw_map[word].append(word_no)

    return kw_map

Ссылка на код.

Чем же отличается defaultdict от dict? И что значит параметр list?

Когда мы обращаемся к несуществующему ключу словаря, то defaultdict вызывает функцию, указанную при создании (в данном случае list) без параметров. И результат работы этой функции и будет присвоен новом элементу словаря с ключом, который раннее не существовал. А как только он появился, то исключении уже не будет брошено, и выполнится та операция, которую мы хотим (в данном случае append).

list – это встроенная функция, которая позволяет нам сконструировать список, если вызвана без параметров.

>>> print([])
[]
>>> print(list())   # тоже самое
[]

Поэкспериментируем в интерпретаторе:

>>> from collections import defaultdict
>>> d = defaultdict(list)   # создали нашdefaultdict
>>> d
defaultdict(<type 'list'>, {})
>>> d['test']   # усп! этого ключа еще нет в словаре
[]
>>> d   # а теперь есть, хотя мы всего лишь обратились на чтение
defaultdict(<type 'list'>, {'test': []})
>>> d['test2'].append('foo')   # test2 тоже нет, но он создасться как [] и append сработает
>>> d
defaultdict(<type 'list'>, {'test': [], 'test2': ['foo']})

Вместо list попробуем, например, int. То есть при обращении к несуществующему элементу defaultdict будет добавлено целое число (0 по умолчанию).

>>> d2 = defaultdict(int)
>>> d2['a']
0
>>> d2['b'] += 10   # операция += сработает, такое не пройдет с обычным dict
>>> d2
defaultdict(<type 'int'>, {'a': 0, 'b': 10})

defaultdict при создании принимает любую ф-цию, а не только встроенные! В следующем примеры мы создадим словарь по-умолчанию со значением элемента – числом 3 (с помощью лямбда ф-ции lambda: 3):

>>> d3 = defaultdict(lambda: 3)
>>> d3['fef']
3
>>> d3['rrr'] += 3
>>> d3
defaultdict(<function <lambda> at 0x10e0376e0>, {'fef': 3, 'rrr': 6})

И самое Питон-чудо напоследок! Смотрите как элегантно мы создадим тип данных «дерево» всего одной строкой!

def tree(): return defaultdict(tree)

Теперь мы можем:

x = tree()
x['a']['b']['c'] = 'test'
print(x)
defaultdict(<function tree at 0x10e037578>, {'a': defaultdict(<function tree at 0x10e037578>, {'b': defaultdict(<function tree at 0x10e037578>, {'c': 'test'})})})

Все поддеревья создались автоматически благодаря рекурсии! Каждый раз, когда мы хотим получить доступ к несуществующему поддереву, вызывается снова ф-ция tree, которая создает такое же поддерево, конструктор по-умолчанию которого тот же самый tree.

Специально для канала PyWay.