
Size: a a a
items = [1, 3, 5, -17, 20, 3, -6]
for x in items:
if x < 0:
print(x)
break
else:
print('not found')
list(filter(lambda x: x < 0, items))[0]
[x for x in items if x < 0][0]
next(x for x in items if x < 0)
items = [1, 2, 4]
result = next((x for x in items if x < 0), 'not found')
print(result) # not found
def is_odd(x):
return x % 2 != 0
next(x for x in items if is_odd(x))
# или еще лучше
next(filter(is_odd, items))
product()
– прямое (Декартово) произведение одной или нескольких последовательностей.permutations()
– перестановки и размещения элементов последовательности. combinations()
– уникальные комбинации из элементов последовательности.combinations_with_replacement()
– комбинации с замещениями.>>> print(*product([1, 2, 3], 'XY'))
(1, 'X') (1, 'Y') (2, 'X') (2, 'Y') (3, 'X') (3, 'Y')
>>> print(*combinations([1, 2, 3], 2))
(1, 2) (1, 3) (2, 3)
>>> print(*permutations([1, 2, 3], 2))
(1, 2) (1, 3) (2, 1) (2, 3) (3, 1) (3, 2)
combinations_with_replacement()
. Не зря же они ее туда добавили? :)def make_adder(a):
def adder(x):
return a + x
return adder
plus_5 = make_adder(5)
print(plus_5(3)) # 8
def make_adders():
adders = []
for a in range(3):
def adder(x):
return a + x
adders.append(adder)
return adders
adders = make_adders()
for adder in adders:
print(adder(2)) # 4 4 4
a
, значение которой останется равным 2 после выполнения всего цикла целиком.a=a
:def make_adders():
adders = []
for a in range(3):
def adder(x, a=a): # FIX!
return a + x
adders.append(adder)
return adders
adders = make_adders()
for adder in adders:
print(adder(2)) # 2 3 4
def adder(x, that_a=a): # FIX!
return that_a + x
def make_adders():
for a in range(3):
def adder(x):
return a + x
yield adder
a=a
из поста выше! Казалось бы, что код должен также содержать в себе баг и выводить "4 4 4", но он работает, как задумано изначально:adders = make_adders()
for adder in adders:
print(adder(2)) # 2 3 4
adders = list(make_adders())
for adder in adders:
print(adder(2)) # 4 4 4
a = 0
adder(2) = 0 + 2 = 2
a = 1
adder(2) = 1 + 2 = 2
a = 2
, и все функции выдают одинаковый результат.adders = make_adders()
for adder in adders:
print(adder(2)) # 2 3 4
next(adders) # StopIteration
loop.run_in_executor
.import asyncio
from functools import wraps, partial
def async_wrap(func):
@wraps(func)
async def run(*args, loop=None, executor=None, **kwargs):
if loop is None:
loop = asyncio.get_event_loop()
pfunc = partial(func, *args, **kwargs)
return await loop.run_in_executor(executor, pfunc)
return run
# превращаем обычный sleep в асинхронный
import time
async_sleep = async_wrap(time.sleep)
# асинхронное удаление файлов
import os
async_remove = async_wrap(os.remove)
# своя функция
@async_wrap
def my_func(duration):
print("my_func start")
time.sleep(1)
print("my_func end")
# применение
async def main():
await asyncio.gather(async_sleep(3), my_func(2))
await my_func(1)
asyncio.run(main())
await my_func(1, loop=myloop)
ProcessPoolExecutor
), что может повлечь за собой неожиданные эффекты. Например, matplotlib на macOS часто падает, если построение графика совершается через executor
.from collections import namedtuple
# новый класс User с полями name и phone
User = namedtuple('User', ['name', 'phone'])
# конкретный юзер - экземпляр
user1 = User('John', phone='+79991002030')
print(user1) # User(name='John', phone='+79991002030')
'name, phone'
или даже 'name phone'
. Имена полей любые, кроме зарезервированных слов и имен, начинающихся с подчеркивания.>> Point = namedtuple('Point', ['x', 'y'])
>> p = Point(x=11, y=22) # создание
>>> p[0] + p[1] # доступ по индексам
33
>>> p.x + p.y # доступ по именам
33
>>> x, y = p # распаковка, как обычный кортеж
>>> x, y
(11, 22)
>>> p # читабельное repr
Point(x=11, y=22)
>>> Point._make((22, 33)) # создание из обычного кортежа или другого итерируемого объекта
Point(x=22, y=33)
>>> p._asdict() # представление в форме словаря
{'x': 11, 'y': 22}
from collections import namedtuple
User = namedtuple('User', ['name', 'phone'])
user = User(name='Jack') # ошибка! не указан phone!
User = namedtuple('User', ['name', 'phone'], defaults=('NoName', 'NoPhone'))
user = User(name='Jack')
>> user
User(name='Jack', phone='NoPhone')
>>> User() # вообще без параметров
User(name='NoName', phone='NoPhone')
fields = ('val', 'left', 'right')
Node = namedtuple('Node', fields, defaults=(None,) * len(fields))
>>> Node()
Node(val=None, left=None, right=None)
Node = namedtuple('Node', ('val', 'left', 'right'), defaults=(100, 200))
'left'
и 'right'
, а 'val'
останется без дефолтного значения, вынуждая нас всегда его указывать:>>> Node()
TypeError: __new__() missing 1 required positional argument: 'val'
>>> Node(13)
Node(val=13, left=100, right=200)
>>> Node(val=42)
Node(val=42, left=100, right=200)
'users'
:Group = namedtuple('Node', ('name', 'users'), defaults=('5B', [])) # плохо!
g = Group()
g.users.append('Vanya') # повлияет на g2 тоже!
g2 = Group()
print(g2.users) # ['Vanya']
def new_empty_group(name='5B'):
return Group(name=name, users=[])
_replace
возвращает новый объект, в котором отредактированы выбраные поля, а все остальные равны значениям из предыдущего кортежа.from collections import namedtuple
Book = namedtuple('Book', ['id', 'title', 'authors'])
book1 = Book(1, 'Игрок', 'Достоевский Ф.М.')
book2 = book1._replace(id=2, title='Преступление и наказание')
>>> book1
Book(id=1, title='Игрок', authors='Достоевский Ф.М.')
>>> book2
Book(id=2, title='Преступление и наказание', authors='Достоевский Ф.М.')
# сам класс
Book.__doc__ += ': Hardcover book in active collection'
# его поля
Book.id.__doc__ = '13-digit ISBN'
Book.title.__doc__ = 'Title of first printing'
Book.authors.__doc__ = 'List of authors sorted by last name'
help(Book)
и увидите, что у Book есть теперь описание класса и всех полей. А также по умолчанию namedtuple добавляет кучу стандартной документации по методам класса.hypot
, рассчитывающий расстояние от начала координат.class Point(namedtuple('Point', ['x', 'y'])):
_ _ slots = ()
@property
def hypot(self):
return (self.x 2 + self.y 2) ** 0.5
def __str__(self):
return f'Point: x={self.x} y={self.y} hypot={self.hypot}'
>>> print(Point(1, 2))
Point: x=1 y=2 hypot=2.23606797749979
from typing import NamedTuple
class Employee(NamedTuple):
name: str
id: int
Employee = namedtuple('Employee', ['name', 'id'])
_ _ slots = ()
здесь играет особую роль. Я еще не рассказывал про слоты, но если вкратце, то таким образом мы предотвращаем создание внутреннего словаря для данного класса, что экономит еще 8 байт.class Computer:
def __init__(self, cores, ram=2048):
self.cores = cores
self.ram = ram
def __repr__(self):
return f'Computer({self.cores}, {self.ram})'
# прочие методы, eq
from dataclasses import dataclass
@dataclass
class Computer:
cores: int
ram: int = 2048
print(Computer(2)) # Computer(cores=2, ram=2048)
@dataclass
class Number:
value: int # поле, сгенерируется код
foo = 10 # просто переменная уровня класса, будет проигнорирована
print(Number(10, foo=20)) # ошибка!
from dataclasses import make_dataclass
Computer = make_dataclass('Computer', ('ip', 'cores', 'ram'))
from dataclasses import asdict
asdict(Computer(cores=8, ram=2**15)) # {'cores': 8, 'ram': 32768}
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
class C:
...
init
(вкл) – генерация метода инициализации. Игнорируется, если у класса уже есть свой init
.repr
(вкл) – генерации метода repr
для класса. Дает удобное представление класса при печати его в консоль или в логи.eq
(вкл) – генерировать ли метод равенства ==? Сравниваются все поля по очереди (похоже на tuple).order
(выкл) – добавить ли методы сравнения (>, ≥, ≤, <
)? Принцип сравнения такой же, как у tuple – поля сравниваются по порядку сверху вниз.frozen
(выкл) – заморозка полей. При попытке присвоения к полям класса возникнет исключение. Таким образом класс становится только для чтения (чем-то похож на namedtuple).unsafe_hash
(выкл) – отвечает за генерацию метода hash
. Почему unsafe (то есть небезопасный)? Потому что ваш класс может быть мутабельным (изменяемым), поэтому вы сами берете на себя ответственность за соответствия содержимого класса и его хэша. Ситуация, когда сначала от экземпляра берут хэш, чтобы, например, сохранить его во множестве, а потом поля класса меняются, приводит к наличию двух одинаковых элементов во множестве. Это противоречит его смыслу, но возможно. У меня был пост об этом. frozen=True
и eq=True
, то класс считается уже неизменяемым, и к нему автоматически генерируется hash-функция, поэтому не требуется дополнительно ставить unsafe_hash=True
.from dataclasses import dataclass
@dataclass(order=True)
class Player:
name: str
score: int
p1 = Player('Maksim', 10)
p2 = Player('Ivan', 30)
p3 = Player('Ivan', 40)
print(f'{p1} > {p2} = {p1 > p2}') # True: Maksim > Ivan
print(f'{p2} > {p3} = {p2 > p3}') # False: Ivan == Ivan, но 30 < 40!
Maksim > Ivan
, получается что p1 > p2
. Сравнение даже не смотрит на поле score
(счет).score
. Следовательно, p2 > p3 == False
, потому что p2.score < p3.score
.field
).field
позволяет настраивать каждое поле класса индивидуально. Это тема для отдельного поста.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)
@dataclass
class Foo:
x: int = field(default=10) # x: int = 10
= []
. В последних версиях Python здесь будет исключение уже на этапе определения класса! Иначе бы все экземпляры Class разделяли бы один и тот же объект списка в поле students
:@dataclass
class Class:
number: int
students: list = [] # Ошибка!
field
с указанием default_factory
.students: list = field(default_factory=lambda: [])
# или еще короче и правильнее вот так:
students: list = field(default_factory=list)
default_factory=set
или default_factory=dict
или даже default_factory=MyClass
, если у класса MyClass есть конструктор без обязательных аргументов.@dataclass
class User:
name: str
password: str = field(repr=False)
admin = User('admin', '1234')
print(admin) # User(name='admin')
init=False
– ваш выбор.compare=False
:@dataclass(order=True)
class User:
name: str = field(compare=False)
age: int
ken = User('Ken', 20)
linda = User('Linda', 18)
print(ken > linda) # True
age
. Попробуйте убрать compare=False
и увидите, что результат сравнения изменится.