Size: a a a

2019 January 27
Oh My Py
Кортеж здорового человека: автоматическая замена названий

Допустим, вы импортируете данные из CSV и превращаете каждую строчку в кортеж. Названия полей взяли из заголовка CSV-файла. Но что-то идёт не так:

# headers = ("name", "age", "with")
>>> Pet = namedtuple("Pet", headers)
ValueError: Type names and field names cannot be a keyword: 'with'

# headers = ("name", "age", "name")
>>> Pet = namedtuple("Pet", headers)
ValueError: Encountered duplicate field name: 'name'


Решение — аргумент rename=True в конструкторе:

# headers = ("name", "age", "with", "color", "name", "food")
Pet = namedtuple("Pet", headers, rename=True)

>>> Pet._fields
('name', 'age', '_2', 'color', '_4', 'food')


Ставьте 👍, если хотите ещё про кортежи; или 😴, если хватит уже и поехали дальше.
источник
2019 January 28
Oh My Py
Кортеж здорового человека: значения по умолчанию

Хорошо, продолжим пока про кортежи ツ Если у кортежа куча необязательных полей, всё равно приходится каждый раз перечислять их при создании объекта:

Pet = namedtuple("Pet", "type name alt_name")

>>> Pet("pigeon", "Френк")
TypeError: __new__() missing 1 required positional argument: 'alt_name'

>>> Pet("pigeon", "Френк", None)
Pet(type='pigeon', name='Френк', alt_name=None)


Чтобы этого избежать, укажите в конструкторе аргумент defaults:

Pet = namedtuple("Pet", "type name alt_name", defaults=("нет",))

>>> Pet("pigeon", "Френк")
Pet(type='pigeon', name='Френк', alt_name='нет')


defaults присваивает умолчательные значения справа налево, с хвоста. Работает в питоне 3.7+

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

Pet = namedtuple("Pet", "type name alt_name")
default_pet = Pet(None, None, "нет")

>>> default_pet._replace(type="pigeon", name="Френк")
Pet(type='pigeon', name='Френк', alt_name='нет')

>>> default_pet._replace(type="fox", name="Клер")
Pet(type='fox', name='Клер', alt_name='нет')


Но с defaults, конечно, куда приятнее.
источник
2019 January 29
Oh My Py
Именованный кортеж vs объект

Эта заметка может быть немного тяжеловата для начинающих, так что если что — просто пропустите её. Скоро мы уже закончим с нюансами кортежей.

Одно из преимуществ именованного кортежа — легковесность. Армия из ста тысяч голубей займёт всего 10 мегабайт:

from collections import namedtuple
import objsize  # non-standard

Pet = namedtuple("Pet", "type name age")
frank = Pet(type="pigeon", name="Френк", age=None)

pigeons = [frank._replace(age=idx) for idx in range(100000)]

>>> round(objsize.get_deep_size(pigeons)/(1024**2), 2)
10.3


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

Так происходит, потому что обычные объекты в питоне таскают с собой увесистый дандер __dict__, в котором лежат названия и значения всех атрибутов объекта:

class PetObj:
 def __init__(self, type, name, age):
   self.type = type
   self.name = name
   self.age = age

frank_obj = PetObj(type="pigeon", name="Френк", age=3)

>>> frank_obj.__dict__
{'type': 'pigeon', 'name': 'Френк', 'age': 3}


Объекты-namedtuple же лишёны этого словаря, а потому занимают меньше памяти:

>>> frank.__dict__
AttributeError: 'Pet' object has no attribute '__dict__'

>>> objsize.get_deep_size(frank_obj)
335

>>> objsize.get_deep_size(frank)
227


Но как именованному кортежу удалось избавиться от __dict__? Поговорим об этом в следующий раз ツ
источник
2019 January 30
Oh My Py
Что внутри у namedtuple и почему он такой лёгкий

Если вы давно работаете с питоном, то наверняка знаете: легковесный объект можно создать через дандер __slots__:

class PetSlots:
 __slots__= ("type", "name", "age")
 
 def __init__(self, type, name, age):
   self.type = type
   self.name = name
   self.age = age

frank_slots = PetSlots(type="pigeon", name="Френк", age=3)


У «слотовых» объектов нет словаря с атрибутами, поэтому они занимают мало памяти. «Френк на слотах» такой же лёгкий, как «Френк на кортеже», смотрите:

>>> objsize.get_deep_size(frank)
239

>>> objsize.get_deep_size(frank_slots)
231


Если вы решили, что namedtuple тоже использует слоты — вы недалеки от истины ツ

Как вы помните, конкретные классы-кортежи объявляются динамически:

Pet = namedtuple("Pet", "type name age")


Конструктор namedtuple применяет разную тёмную магию и генерит примерно такой класс (сильно упрощаю):

class Pet(tuple):
 __slots__= ()
 
 type = property(operator.itemgetter(0))
 name = property(operator.itemgetter(1))
 age = property(operator.itemgetter(2))
 
 def __new__(cls, type, name, age):
   return tuple.__new__(cls, (type, name, age))


То есть наш Pet — это на самом деле обычный tuple, к которому гвоздями приколотили три метода-свойства:

— type возвращает нулевой элемент кортежа
— name — первый элемент кортежа
— age — второй элемент кортежа

А __slots__ нужен только для того, чтобы объекты получились лёгкими. В результате Pet и занимает мало места, и может использоваться как обычный кортеж:

>>> frank.index("Френк")
1

>>> type, _, _ = frank
>>> type
'pigeon'


Хитро придумано, а?
источник
2019 January 31
Oh My Py
Чистый код: раздвоение личности у функции

Я решил не терзать вас больше именованными кортежами, поэтому последнюю заметку серии выложил на хабре.

А сегодня очередной выпуск «чистого кода». Есть в модуле collections класс OrderedDict. Это обычный словарь, только помнит порядок, в котором добавлялись ключи:

from collections import OrderedDict

d = OrderedDict()
d["Френк"] = "наглый"
d["Клер"] = "хитрая"
d["Питер"] = "тупой"

>>> d.keys()
odict_keys(['Френк', 'Клер', 'Питер'])


Ничего особо интересного в упорядоченном словаре нет — тем более, что начиная с версии 3.6 обычный словарь тоже сохраняет порядок ключей.

Но есть у него любопытный метод move_to_end():

>>> d.move_to_end("Френк")
>>> d.keys()
odict_keys(['Клер', 'Питер', 'Френк'])


Всё вроде понятно, метод передвигает указанный ключ в конец словаря. Логично предположить, что должна существовать парная операция — передвинуть в начало. Интересно, как она называется, наверно move_to_start() или что-то в этом роде. А вот и нет:

>>> d.move_to_end("Френк", False)
>>> d.keys()
odict_keys(['Френк', 'Клер', 'Питер'])


То есть, чтобы передвинуть ключ в начало, мы делаем move_to_end(False). Это как если бы login(False) выполнял logout(). Это как если бы left(False) выполнял right(). Настолько хрестоматийно плохо, что я не понимаю, как это оказалось в стандартной библиотеке.

Много лет назад Роберт Мартин написал в «Чистом коде»: если у функции есть переключатель, который кардинально меняет её поведение — функцию следует разделить на две:

d.move_to_end("Френк")
d.move_to_start("Френк")


Не вижу причин спорить с Мартином в данном случае.
источник
2019 February 04
Oh My Py
Объединить отсортированные списки в один

Предположим, вы решили провести чемпионат мира по оглаживанию собак. Кто погладит больше всех шерстяных волчар за день, тот и победил. Участники не смогли собраться вместе, поэтому каждый город провёл независимое состязание и прислал результат:

washington = [
   (99, "Френк"),
   (80, "Клер"),
   (73, "Зоя")
]

moscow = [
   (90, "Валера"),
   (88, "Мария"),
   (50, "Анатолий")
]

beijing = [
   (123, "Чан"),
   (109, "Пинг"),
   (70,  "Ки"),
]


Теперь ваша задача — выбрать трёх призёров. Я знаю как минимум один простой способ:

all = sorted(washington + moscow + beijing)
winners = all[-3:]

>>> winners
[(99, 'Френк'), (109, 'Пинг'), (123, 'Чан')]


Если всего n участников, такая реализация займёт 2n памяти и потребует O(n log n) операций. Довольно расточительно.

Можно сделать то же самое за константное время и память:

import heapq

all = heapq.merge(washington, moscow, beijing, reverse=True)

>>> next(all)
(123, 'Чан')

>>> next(all)
(109, 'Пинг')

>>> next(all)
(99, 'Френк')


heapq.merge() возвращает генератор, который работает поверх исходных коллекций — поэтому не расходует лишнюю память. И он учитывает, что исходные списки уже отсортированы — поэтому не выполняет лишних действий.

P.S. Френк, всего лишь третье место. Не ожидал от тебя.
источник
2019 February 11
Oh My Py
Выбрать топ-k элементов списка

Сегодня новое соревнование — граждане города выбирают самое наглое животное. Результаты опроса поступили в виде неупорядоченного списка пар «количество голосов — участник»:

contenders = [
 (31, "индюк"),
 (22, "крыса"),
 (79, "кот"),
 (98, "голубь"),
 (13, "собака"),
 (95, "енот"),
 (15, "хомяк"),
]


Осталось, как обычно, выбрать трёх победителей. Как насчёт такого:

>>> sorted(contenders)[-3:]
[(79, 'кот'), (95, 'енот'), (98, 'голубь')]


Неплохо. Но, как вы помните, сортировка списка занимает O(n log n) операций. Жирновато, чтобы просто выбрать топ-3 элемента.

Вот альтернатива:

>>> import heapq
>>> heapq.nlargest(3, contenders)
[(98, 'голубь'), (95, 'енот'), (79, 'кот')]


Такой вариант использует только O(n) операций — при небольшом k (в данном случае k = 3). Для больших k вариант с sorted() эффективнее.

Ну а если k = 1 (выбираем одного победителя), то так:

>>> max(contenders)
(98, 'голубь')


Я даже знаю, как его зовут ツ
источник
2019 February 18
Oh My Py
Обработать заявки с учётом приоритетов

Если система обрабатывает заявки, редко бывает, что все они одинакового веса. Чаще встречаются разные приоритеты: клиенты бывают обычные и VIP, баги бывают минорные и критические, заказы бывают «до 1000 ₽» и «10000+ ₽».

Если приоритетов нет, обслуживать заявки просто: кто раньше пришёл, того раньше и обслужили (first in, first out — FIFO). С приоритетами сложнее: более важные заявки должны идти вперёд, но среди заявок с одинаковым приоритетом по-прежнему должен действовать принцип FIFO.

Допустим, была у нас система без приоритетов:

from collections import deque

def process(requests):
 while requests:
   client, task = requests.pop()
   print(f"{client}: {task}")

requests = deque()
requests.appendleft(
 ("Лукас", "нарвать бананов"))
requests.appendleft(
 ("Зоя", "почесать спинку"))
requests.appendleft(
 ("Френк", "насыпать зёрен"))

>>> process(requests)
Лукас: нарвать бананов
Зоя: почесать спинку
Френк: насыпать зёрен


Обработка по порядку, всё честно. А теперь допустим, что у заявки появился вес:

→ Лукас, вес 1
→ Зоя, вес 1
→ Френк, вес 10

Френк с весом 10 должен пойти первым. А Зоя и Лукас — после него, в порядке поступления: сначала Лукас, потом Зоя.

Реализовать эту логику поможет модуль heapq:

import heapq
import time
requests = []

heapq.heappush(requests,
 (-1, time.time_ns(), "Лукас"))
heapq.heappush(requests,
 (-1, time.time_ns(), "Зоя"))
heapq.heappush(requests,
 (-10, time.time_ns(), "Френк"))


Здесь первым аргументом мы передаём вес заявки. heapq.heappush() ставит первыми элементы с меньшим значением, там что берём вес со знаком минус.

Вторым аргументом передаём текущее время в наносекундах, чтобы заявки с одинаковым весом разрешались в порядке поступления.

Проверим результат:

def process(requests):
 while requests:
   _, _, client = heapq.heappop(requests)
   print(f"{client}")

>>> process(requests)
Френк
Лукас
Зоя


Порядок!
источник
2019 February 19
Oh My Py
Сегодня == сейчас

В каждом языке есть участки, которые не особо удались создателям. Для большинства языков, созданных до двухтысячных годов, камнем преткновения стала работа со временем.

Питон — не исключение. Возьмём функцию, которая сравнивает дату-время с точностью до минуты:

from datetime import datetime, timezone

def equal(dt1, dt2):
 return dt1.replace(second=0, microsecond=0) == dt2.replace(second=0, microsecond=0)


И сравним «сегодня» и «сейчас»:

>>> equal(datetime.today(), datetime.now())
True


Оказывается, это одно и то же ツ Метод today() возвращает не начало дня, как можно было бы ожидать, а текущий момент времени.

А что насчёт времени по UTC?

>>> equal(datetime.now(timezone.utc), datetime.utcnow())
False


Неожиданно, now() с часовым поясом UTC и utcnow() возвращают разные значения. Это потому, что now() возвращает объект с часовым поясом, а utcnow() — без. Хотя дата-время у них и совпадают.

Если работать с датой-временем средствами стандартной библиотеки, лучше всегда использовать или только наивные объекты (без часового пояса), или только осведомлённые (с часовым поясом). Если их смешать — гарантированно будет беда.
источник
2019 February 21
Oh My Py
Дата из строки

До некоторых пор в питоне не было простого способа создать дату из строки.

Либо так:

import time
from datetime import date

>>> date_struct = time.strptime("2019-02-20", "%Y-%m-%d")
>>> date(*date_struct[:3])
datetime.date(2019, 2, 20)


Либо так:

from datetime import date, datetime

>>> dt = datetime.strptime("2019-02-20", "%Y-%m-%d")
>>> dt.date()
datetime.date(2019, 2, 20)


Либо сторонние библиотеки вроде dateutil или arrow.

Но с версии 3.7 делать это стало легко и приятно, если строковые даты вы храните в формате ISO 8601 (что в любом случае хорошая идея):

>>> date.fromisoformat("2019-02-20")
datetime.date(2019, 2, 20)


Для даты-времени тоже работает:

>>> datetime.fromisoformat("2019-02-20T14:30:15")
datetime.datetime(2019, 2, 20, 14, 30, 15)


🐥
источник
2019 March 26
Oh My Py
Очень противоречивый день недели

Помните, я писал, что работа с датой и временем в Питоне не очень удалась? Проявилось это и в нумерации дней недели.

Нашлось место аж трём вариантам, бережно размазанным по трём модулям:

time.strftime("%w")
интервал [0, 6], вс = 0


time.strftime("%u")
интервал [1, 7], пн = 1


time.struct_time.tm_wday
интервал [0, 6], пн = 0


datetime.date.weekday()
интервал [0, 6], пн = 0


datetime.date.isoweekday()
интервал [1, 7], пн = 1


calendar.weekday()
интервал [0, 6], пн = 0


Судя по всему, сначала борьба шла между вс = 0 и пн = 0, а потом пришёл ISO 8601 и вообще всё испортил ツ
источник
2019 June 13
Oh My Py
Очередь с приоритетами

Некоторое время назад мы с вами сделали систему обработки заявок с приоритетами. Правила простые: более важные заявки идут вперёд, но среди заявок с одинаковым приоритетом действовует принцип очерёдности (FIFO).

Помог нам в этом модуль heapq. Он всем хорош, кроме того, что исполнен в архитектурном стиле «потроха наружу» — это не всегда удобно.

Для ленивых любителей ООП в питоне есть класс queue.PriorityQueue, который реализует ровно то, что нам надо — очередь с приоритетами.

Сделаем простую заявку из двух полей — заказчик и вес:

import time
from functools import total_ordering

@total_ordering
class Request:
 def __init__(self, name, weight):
   self.name = name
   self.weight = -weight
   self._timestamp = time.time_ns()

 def __str__(self):
   return self.name

 def __eq__(self, other):
   return (self.weight, self._timestamp) == (other.weight, other._timestamp)

 def __gt__(self, other):
   return (self.weight, self._timestamp) > (other.weight, other._timestamp)


Если не понимаете, что это за @total_ordering, странные методы с кучей подчёркиваний и зачем тут _timestamp — не переживайте, разберём отдельно.

А очередь возьмём готовую:

from queue import PriorityQueue

q = PriorityQueue()
q.put(Request(name="Лукас", weight=1))
q.put(Request(name="Зоя", weight=1))
q.put(Request(name="Френк", weight=10))


Теперь проверим. Ожидаем, что первым будет Френк (у него больше вес), вторым Лукас (пришёл раньше), а третьей — Зоя.

while not q.empty():
 print(q.get())

Френк
Лукас
Зоя


Так и вышло ツ

Есть неприятный нюанс. Модуль queue вообще-то создан для многотопочной работы, поэтому если вызвать у очереди get(), когда она пустая — поток выполнения заблокируется на веки вечные. Так что придётся со всех сторон обкладываться проверками empty() и full() (если решите ограничить максимальный размер очереди).
источник
2019 June 15
Oh My Py
Некоторые животные равнее: сравниваем объекты в питоне

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

class Pet:
 def __init__(self, type, name, weight, importance):
   self.type = type
   self.name = name
   self.weight = weight
   self.importance = importance
 
 def __repr__(self):
   return self.name


weight — это вес в килограммах, а importance — чувство собственной важности (единица измерения не сообщается). Общий ранг рассчитывается как (weight + importance).

С методикой разобрались, осталось всех сравнить и построить итоговый рейтинг:

frank = Pet(type="голубь", name="Френк", weight=1, importance=100)
claire = Pet(type="лиса", name="Клер", weight=5, importance=90)
zoe = Pet(type="свинка", name="Зоя", weight=90, importance=10)

pets = [claire, frank, zoe]
sorted(pets)

TypeError: '<' not supported between instances of 'Pet' and 'Pet'


Вот досада, по умолчанию питон не знает, как сравнивать зверей. Дальше к нашим услугам куча способов, как это сделать.

Если хотим просто отсортировать список, подойдёт аргумент key в функции sorted:

key = lambda x: x.weight + x.importance
sorted_pets = sorted(
 pets,
 key=key,
 reverse=True
)
print("Рейтинг по методике:")
print(sorted_pets)

Рейтинг по методике:
[Френк, Зоя, Клер]


key — это функция, которая принимает наш объект, а возвращает число. Функция sorted упорядочивает список объектов именно по этим числам.

Если сортировать только по ЧСВ, можно даже свою функцию не писать — в модуле operator есть готовая:

import operator

key = operator.attrgetter("importance")
sorted_pets = sorted(
 pets,
 key=key,
 reverse=True
)
print("Рейтинг по ЧСВ:")
print(sorted_pets)

Рейтинг по ЧСВ:
[Френк, Клер, Зоя]


Френк ожидаемо победил в обеих номинациях. Чёртова птица.
источник
2019 June 17
Oh My Py
Сравнение объектов: кортежи

Опубликованный рейтинг зверей подвергся разгромной критике Фонда защиты дикой природы. Недопустимо складывать ЧСВ с килограммами, возмущаются эксперты.

Британских учёных такими мелочами не смутишь. Они выпустили новую редакцию методики. Теперь, уважая право животных на самоопределение, мы должны прежде всего сравнивать их ЧСВ. И только если ЧСВ одинаковое, сравнивать вес.

Вопрос, как это реализовать. Раньше у нас была простая функция:

key = lambda x: x.weight + x.importance


Как поменять её на «сначала ЧСВ, затем вес»? Очень просто — с помощью кортежа:

key = lambda x: (x.importance, x.weight)


Питон сравнивает кортежи поэлементно, причём переходит к следующему элементу, только если предыдущие одинаковые:

(1, 10) < (2, 1)
True

(1, 10) > (1, 1)
True


Именно то, что нам надо! А что, если сделать весь объект Pet кортежем?

from collections import namedtuple
Pet = namedtuple("Pet", ("importance", "weight", "name", "type"))

frank = Pet(...)
claire = Pet(...)
zoe = Pet(...)
pets = [claire, frank, zoe]

sorted_pets = sorted(pets, reverse=True)

print([p.name for p in sorted_pets])
['Френк', 'Клер', 'Зоя']


Теперь не приходится указывать key — питон сортирует зверей как кортежи. Бонусом получили возможность сравнивать объекты напрямую:

claire > zoe
True
источник
2019 June 18
Oh My Py
Сравнение объектов: дандеры

На презентации второй версии отчёта Френк нагадил на научного руководителя проекта. Пребывая в крайне дурном настроении, тот зачем-то полез в исходники и обнаружил там нашу реализацию через namedtuple.

Брызжа слюной, научрук отверг её как «необъектную». Попытки объяснить ему, что в питоне всё — объект не увенчались успехом. Придётся переделывать на явное использование class, а то не отстанет.

Окей, вот наш класс:

class Pet:
 def __init__(self, name, weight, importance):
   self.name = name
   self.weight = weight
   self.importance = importance


Сравнение на нём, естественно, не работает:

frank = Pet(...)
claire = Pet(...)
zoe = Pet(...)

claire > zoe
TypeError: '>' not supported between instances of 'Pet' and 'Pet'


А чтобы заработало — придётся перекрыть специальные методы (они же дандеры), которые отвечают за сравнение:

__lt__ (<)
__le__ (<=)
__eq__ (==)
__ne__ (!=)
__gt__ (>)
__ge__ (>=)


Поскольку перекрывать все шесть методов лениво, можно воспользоваться декоратором @functools.total_ordering и перекрыть только два — eq и ещё один (например, lt). Остальное декоратор сделает сам.

@functools.total_ordering
class Pet:
 ...
 @property
 def rank(self):
   return (self.importance, self.weight)
 
 def __eq__(self, other):
   return self.rank == other.rank
 
 def __lt__(self, other):
   return self.rank < other.rank


Для краткости я опустил проверку, что other обладает нужными свойствами.

Теперь будет работать и обычное сравнение, и сортировка:

claire > zoe
True

sorted_pets = sorted(pets, reverse=True)
print(sorted_pets)
[Френк, Клер, Зоя]


Такой способ создания «сравнибельных» объектов всем хорош. Проверен временем, его ещё Рюрик в битве при Фермопилах с ацтеками использовал. Но есть и более молодёжный. О нём в следующий раз.
источник
2019 June 19
Oh My Py
Сравнение объектов: финал

Плохие новости: британские учёные не угомонились. Теперь каждый релиз должен проходить согласование в архитектурном комитете. И наш последний вариант с total_ordering и дандерами комитет забраковал.

Обоснование, цитирую: «решение недостаточно инновационное» (если когда-нибудь наблюдали работу архитектурного комитета в жизни, вас такой дичью не удивить). В приватной беседе архитекторы намекнули, что следует использовать новые фичи языка. Ну что ж, расчехляем датаклассы.

Датаклассы! Эта абсолютно анти-pythonic штука, которая нарушает сразу несколько постулатов питонячьего дзена. Но реализация с ними будет более компактной, тут не поспоришь. Вот она:

from dataclasses import dataclass, field

@dataclass(order=True)
class Pet:
 importance: int
 weight: int
 name: str = field(compare=False)


А вот как это работает:

— декоратор dataclass генерит кучу дандеров для класса, включая eq
— параметр order=True заставляет его дополнительно сгенерить дандеры lt, le, gt, ge
— сравнение производится по полям в том порядке, как они перечислены в классе (importance, weight, name)
field(compare=False) исключает поле name из сравнения, так что сравнивается только (importance, weight)

Дальше можно сравнивать объекты как обычно:

frank = Pet(...)
claire = Pet(...)
zoe = Pet(...)

frank > claire > zoe
True


Инновационнее некуда. Надеюсь, архитекторы будут довольны. Хотя лично мне этот вариант нравится меньше всех предыдущих.

Теперь вы знаете о сравнении объектов больше, чем 90% питонистов. Осталась ещё пара нюансов — они выйдут в режиссёрской версии от Френка за 99₽ в семечковом эквиваленте.
источник
2019 June 24
Oh My Py
Создать полный дубль коллекции

У нас ответственная миссия: запустить в космос автомобиль. Сначала подготовим инфраструктуру — собственно машину и мега-пушку:

from dataclasses import dataclass

@dataclass
class Car:
 brand: str
 model: str
 driver: str

class SpaceCannon:
 def launch(self, cars):
   car = cars[0]
   print(f"{car.brand} {car.model} driven by {car.driver} sent to space!")


Проверим:

car = Car(brand="Tesla", model="Roadster", driver="Starman")
cars = [car]
cannon = SpaceCannon()
cannon.launch(cars)

Tesla Roadster driven by Starman sent to space!


Работает!

Как всякий уважающий себя космический завод, наш умеет копировать машины. Очень удобно — можно сделать копию коллекции машин и всячески над ней издеваться. Например, очистить:

copied_cars = cars[:]
copied_cars.clear()


Оригинальный список при этом не пострадал, его можно спокойно запускать:

cannon.launch(cars)
Tesla Roadster driven by Starman sent to space!


О, тут инженерам ещё хохма в голову пришла:

copied_cars = cars[:]
copied_cars[0].brand = "ToSky"
copied_cars[0].model = "Zhiguli"
copied_cars[0].driver = "Roskosmos guy"


Очень смешно, отправить в космос чела из Роскосмоса на жигулях, ха-ха. Пошутили и хватит, запускаем Теслу:

cannon.launch(cars)
ToSky Zhiguli driven by Roskosmos guy sent to space!


Ну вот (((

Проблема в том, что cars[:] выполняет так называемое поверхностное копирование — сам список копируется, но в качестве его элементов используются ссылки на элементы оригинального списка.

Поэтому, меняя copied_cars[0], мы превратили оригинальную Теслу в Жигули (что само по себе заслуживает уважения, конечно).

Создать полный дубликат коллекции поможет модуль copy:

import copy

car = ...
cars = [car]
copied_cars = copy.deepcopy(cars)
copied_cars[0].model = "Zhiguli"
cannon.launch(cars)

Tesla Roadster driven by Starman sent to space!


Ну, другое дело.
источник
2019 June 25
Oh My Py
Узнать день недели 40 лет назад

Есть в питоне модуль calendar. Лично я ожидал от него крутых фич по работе с датами, которые не влезли в datetime.

На деле он занимается форматированием календарей в HTML (именно то, что требуется в стандартной библиотеке любого языка) и предоставляет гениальные методы вроде itermonthdays, itermonthdays2, itermonthdays3 и itermonthdays4 (оцените богатство выбора, прямо как на воскресной ярмарке).

Но есть в нём и полезные функции. Например, узнать день недели для любой даты в прошлом или будущем:

import calendar
wday = calendar.weekday(1959, 11, 5)
calendar.day_name[wday]

'Thursday'


Или вспомнить, сколько дней в июне:

import datetime as dt
today = dt.date.today()
_, days = calendar.monthrange(today.year, today.month)
days

30


Или проверить, високосный ли год:

calendar.isleap(2020)
True


А генерировать HTML-календари с помощью calendar вы не будете, надеюсь ツ
источник
2019 June 26
Oh My Py
Быстро найти элемент коллекции

Френк решил открыть магазин диковинок. Прайс-лист огромный, приведу только несколько позиций:

from collections import namedtuple
Product = namedtuple("Product", ("price", "name"))

products = [
 Product(1500, "живой багет"),
 Product(3300, "мельница для сыра"),
 Product(6500, "костюм картошки"),
 Product(9900, "беспилотная сова"),
]


Магазин открылся, торговля идёт бойко, но есть проблемка. Покупатели донимают вопросом «у меня есть X рублей, какую самую дорогую дичь я могу купить за эту сумму?».

Френк очень плохо считает (неудивительно для голубя), поэтому требуется наша помощь. Давайте сначала решим «в лоб»:

def suggest(max_price):
 best_product = Product(0, None)
 for product in products:
   if product.price > max_price:
     continue
   if product.price > best_product.price:
     best_product = product
 if best_product.name is None:
   return None
 return best_product

>>> suggest(5000)
Product(price=3300, name='мельница для сыра')


Работает как часы! Только Френк жалуется, что suggest() что-то долго думает (прайс-лист огромный, помните?). Это неудивительно, мы ведь каждый раз перебираем все товары — сложность алгоритма O(n)

Надо бы отсортировать товары по цене и использовать алгоритм бинарного поиска, который работает за O(log n). Правда, не слишком греет перспектива реализации алгоритма — Френк требует сделать всё сию же секунду.

Нам поможет модуль bisect стандартной библиотеки:

import bisect

prices = sorted(p.price for p in products)

def suggest(max_price):
 best_index = bisect.bisect(prices, max_price)
 if best_index == 0:
   return None
 return products[best_index - 1]

>>> suggest(5000)
Product(price=3300, name='мельница для сыра')


Работает так:

— Создали отсортированный список цен.
— Покупатель принёс 5000₽ денег.
bisect.bisect() определил, на какую позицию списка можно вставить 5000, чтобы список остался отсортированным (третья позиция, между 3300 и 6500).
— Элемент слева от этой позиции и есть интересующий нас товар («мельница» за 3300).

Френк доволен.
источник
2019 June 27
Oh My Py
Френк вчера так достал своей беспилотной совой, что я совсем забыл спросить у вас одну вещь.

Допустим, вы пишете программу, которой на вход последовательно, одно за другим, приходят числа. Ваша задача — накапливать их как-то, а потом, когда числа перестанут приходить — вернуть отсортированный список.

Как думаете, что будет работать быстрее:

— Складывать приходящие числа в неупорядоченную кучу, отсортировать в конце.
— Постоянно поддерживать отсортированный список (с помощью bisect), в конце просто вернуть его.

Опрос следует.
источник