PyWay – гуру Python 🐉
825 subscribers
22 photos
1 video
2 files
153 links
Ваш путь в глубины Python. Узнай все секреты и фишки у нас! Если хотите сотрудничать – пишите @account1242 (условия @pyway_ads)
Download Telegram
Что будет выведено на экран?
Anonymous Quiz
27%
2
31%
3
26%
4
16%
5
🌷Всех дам поздравляю с Международным женским днем! 🌷
Желаю вам счастья, любви, здоровья, отличного настроения, улыбок, солнца на улице и в душе!
Мы вас очень любим и ценим!
[код]
Решение предыдущей задачи

Если кратко, то при поиске словарь проверяет сначала хэш (hash) от ключей. Разные хэши – разные ключи – разные записи. Если хэши равны, то проверяется простое равенство (==) ключей. Если и они равны, то считается, что это один и тот же ключ, и ему отвечает одна и та же запись в словаре.

Все float('nan') имеют одинаковый хэши, но значение NaN (not a number – не число), не равно никакому другому float, включая само себя и другие float('nan'), поэтому для словаря все nan – разные ключи, и каждому из них отвечает отдельная запись.

>>> float('nan') == float('nan')
False
>>> hash(float('nan'))
0
>>> {float('nan'): 1, float('nan'): 2}
{nan: 1, nan: 2}


Кстати, спрашивали, как добраться до этих nan. Вот так:

>>> keys = list(d.keys()) # список ключей
>>> d[keys[0]], d[keys[1]] # по ключам добираемся до значений
(1, 2)
>>> del d[keys[0]] # убрать одно


Теперь ситуация с ключами 1, 1.0, True. Оказывается, их хэши тоже равны между собой. Нюанс в том, что и сами они равны между собой!

>>> hash(1), hash(1.0), hash(True)
(1, 1, 1)
>>> 1 == 1.0 == True
True


Поэтому для словаря – ключи 1, 1.0, True – одинаковые и дают доступ к одной и той же записи. Фактический ключ будет тот, что был записан самым первым.

Таким образом, у нас будет 2 записи от двух nan, и одна запись от 1, 1.0, True. Итого, ответ – 3.
​​Танчики на PyGame

Занятые вышли дни, поэтому не смог написать новых статей. Но, чтобы вы не скучали, вот вам один из моих проектов – Танчики на PyGame.
Игра хоть и не доделана полностью: в ней нет меню, звуков и редактора, но геймплей вполне работоспособный. Танчики спавнятся, атакуют, взрываются, стены разрушаются.

Управление – стрелки и пробел на выстрел.

main.py – точка входа. Инициализирует PyGame, создает класс Game, обрабатывает ввод с клавиатуры.
ai.py – интеллект врагов, включая алгоритм их появления.
bonus.py – игровой объект бонуса.
bonus_field_protect.py – алгоритм работы бонуса на защиту базы.
config.py – конфигурация и ключи запуска.
discrete_map.py – объект дискретной 2D карты (нужна для карты поля боя и карты для столкновений).
explosion.py – игровой объект взрыва.
field.py – игровой объект поля боя, обрабатывает столкновения и разрушение мира.
game.py – собирает все объекты вместе и связывает события.
my_base.py – игровой объект базы игрока (орел).
projectile.py – игровой объект снаряда.
score_node.py – игровой объект очков при уничтожении врага.
spritesheet.py – загрузчик спрайтов из одной сборной текстуры.
tank.py – игровой объект любого танка (своего или вражеского).
ui.py – элементы пользовательского интерфейса.
util.py – вспомогательные функции и классы, включая аниматор, таймер и базовый игровой объект.

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

>>> '{:.3}'.format(2.7182)
'2.72'

>>> '{:^15}'.format('hello')
' hello '


Эти параметры тоже можно задать динамически. Нет нужды дважды вызывать format, а достаточно просто добавить фигурные скобки внутрь фигурных скобок:

>>> '{:.{prec}}'.format(2.7182, prec=3)
'2.72'
>>> '{:{align}{width}}'.format('hello', align='^', width=15)
' hello '


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

data = [4, 8, 15, 16, 23, 42]
'{d[4]} {d[5]}'.format(d=data)
# '23 42'

class Plant(object):
type = 'tree'

'{p.type}'.format(p=Plant())


С богатыми возможностями форматирования вы можете познакомиться на сайте https://pyformat.info/ (англ.)

От себя еще добавлю, что f-строки еще круче, чем ''.format(...): внутри фигурных скобок возможно поставить вообще любое валидное Python выражение, хоть вызов функций:

>>> f'2 + 2 = {2 + 2}'
'2 + 2 = 4'
>>> f'sqrt(2) = {2**0.5:.5}'
'sqrt(2) = 1.4142'
>>> data = [1, 2, 3, 4]
>>> f'data sum = {sum(data)}'
'data sum = 10'


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

>>> f'{import os}'
File "<fstring>", line 1
(import os)
^
SyntaxError: invalid syntax


Вообще, способность вычислять выражения внутри строк и подставлять результат в нужно место строки называется: "интерполяция строк". Она работает не зависимо от типа кавычек строки, главно наличии буквы f перед строкой.
📕 #Библиотека XlsxWriter

Белые воротнички не умеют читать файлы JSON? Начальник требует от вас выгружать отчеты в ламповой Эксельке? Не беда. Библиотека XlsxWriter поможет все автоматизировать без головной боли. Просто взгляните на код:

import xlsxwriter

# откроем файл на запись
workbook = xlsxwriter.Workbook('my_report.xlsx')
# создадим лист
worksheet = workbook.add_worksheet()

# данные
expenses = (
['Аренда', 1000],
['Комуналка', 100],
['Еда', 300],
['Качалка', 50],
)

# формат для денег
money = workbook.add_format({'num_format': '#,##0"
"'})
# формат жирности шрифта
bold = workbook.add_format({'bold': True})

worksheet.write('A1', 'Наименование', bold)
worksheet.write('B1', 'Потрачено', bold)

for i, (item, cost) in enumerate(expenses, start=2):
worksheet.write(f'A{i}', item)
worksheet.write(f'B{i}', cost, money)

# колонкой ниже добавить подсчет суммы
worksheet.write('A6', 'Итого:', bold)
worksheet.write('B6', '=SUM(B2:B5)', money)

# задать колонкам от 0 до 1 каждой ширину 15
worksheet.set_column(0, 1, 15)

# сохраняем и закрываем
workbook.close()


В статье больше примеров!

#xls #excel
Python – это компилятор или интерпретатор?

Ответ неоднозначный. Python – это и первое, и второе. (Здесь мы говорим про самый распространенный CPython)
Когда вы вводите в терминале операторы по одному, то они выполняются сразу после ввода, в этом случае Python играет роль интерпретатора.
А если вы скармливаете Python исходный файл целиком, то он сначала компилирует его в промежуточное представление: низкоуровненый байт-код. Замечали файлы *.pyc? Вот это уже скомпилированный байт-код. Он выполняется виртуальной машиной Python.

Набросал для вас реализацию языка Brainfuck, причем в двух вариантах: интерпретатора и компилятора. Интерпретатор читает программу и сразу выполняет ее команда за командой, а компилятор выдает нам исполняемый файл, которым мы запускаем отдельно от компилятора. Уловили разницу?
​​Разбор URL

Функция urlparse из модуля urllib.parse разбирает URL на составные части: протокол, имя хоста, порт, путь, запрос и прочие.

>>> u1 = urlparse('https://tirinox:1234@www.site.com:8080/some/page/index.html?page=2&action=login')
>>> u1
ParseResult(scheme='https', netloc='tirinox:1234@www.site.com:8080', path='/some/page/index.html', params='', query='page=2&action=login', fragment='')
>>> u1.scheme, u1.query, u1.netloc
('https', 'page=2&action=login', 'tirinox:1234@www.site.com:8080')


Имя хоста, порт, логин и пароль объединены в поле netloc, но их компоненты доступны для чтения по этим атрибутам:

>>> u1.username, u1.password, u1.hostname, u1.port
('tirinox', '1234', 'www.site.com', 8080)


Собрать обратно ParseResult в URL:

>>> u1.geturl()
'https://www.site.com:8080/some/page/index.html?page=2&action=login'


Если мы хотим в URL поменять какие-то части, удобно делать вот так:

>>> u1._replace(scheme='http').geturl()
'http://www.site.com:8080/some/page/index.html?page=2&action=login'

Однако таким способом нельзя поменять компоненты netloc, например, отдельно порт. netloc нужно менять целиком:

>>> u1._replace(netloc="user:password@www.site.com:8090").geturl()
'https://user:password@www.site.com:8090/some/page/index.html?page=2&action=login'
globals(), locals(), vars(), dir()

Программист на Python может узнать, какие именно переменные определенны в данный момент в интерпретаторе. Переменные можно разделить на локальные и глобальные. Глобальные определены на верхнем уровне кода снаружи функций и классов (грубо говоря без отступов слева). Локальные переменные наоборот определены внутри своих зон видимости, ограниченных классами и функциями.

Функция globals() выдает словарь глобальных переменных (ключ – имя переменной). Функция locals() возвращает словарь только локальных переменных. Пример:

x, y = 5, 10
def test():
y, z = 33, 44
print('globals:', globals())
print('locals:', locals())
test()
"""Вывод:
globals: {'__name__': '__main__', ... '__file__': '/Users/.../vars.py', '__cached__': None, 'x': 5, 'y': 10, 'test': <function test at 0x107677280>}
locals: {'y': 33, 'z': 44}"""


Обратите внимание, что переменная y в locals() имеет другое значение, нежели чем в globals(). Это две разные переменные из разных областей, но внутри функции приоритет имеет локальная y.

Еще важно знать, что в список переменных входят не только простые переменные, которые вы определяете через знак присваивания, но и функции, классы и импортированные модули!

Через словари из locals() и globals() переменные можно не только читать, но и создавать, перезаписывать и удалять:

>>> x = 10
>>> globals()['x'] = 5
>>> x
5
>>> globals()['new_var'] = 10
>>> new_var
10
>>> del globals()['new_var']
>>> new_var
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'new_var' is not defined


Функция vars() ведет себя как locals(), если вызвана без аргумента, а если с аргументом, то она просто получает _ _ dict _ _ от аргумента. Если его нет у аргумента, то будет TypeError.

class Foo:
def __init__(self):
self.x = 5
f = Foo()
print(vars(f)) # {'x': 5}
print(vars(f) == f.__dict__) # True


В глобальном контексте все три функции возвращают одно и тоже – глобальные переменные. Проверьте:

print(globals())
print(locals())
print(vars())
print(globals() == locals() == vars()) # True


Функциия dir(), будучи вызвана без параметра, возвращает список имен переменных. Глобальных или локальных в зависимости от места вызова:

def test():
x = 10
print(dir()) # ['x']
y = 10
test()
print(dir()) # ['__annotations__', ..., '__spec__', 'test', 'y']


Все рассмотренные выше функции являются встроенными и не требуют импортов.
В дополнение об областях видимости переменных

В отличие он некоторых других языков в Python блоки типа for, if, while, with не создают областей видимости (scope) для переменных, то есть переменная внутри и снаружи блока будет одна и та же:

x = 1
if True:
x = 2
print(x) # 2


Частая ошибка – затирание внешней переменной в цикле for:

i = 10
for i in range(5): # затирает i
...
print(i) # 4


Зоны видимости отделяются только функциями, классами и модулями. Здесь все переменные x – разные:

x = 1
class Foo:
x = 2
def method(self):
x = 3
return x
print(x, Foo.x, Foo().method()) # все 3 разные


Самая широкая зона видимости называется builtin. В нее попадают все имена, известные интерпретатору в данный момент, включая вещи импортированные из других модулей.

>>> from math import pi
>>> pi, id(pi)
(3.141592653589793, 4465320624)
>>> pi = 3
>>> pi, id(pi)
(3, 4462262880)
>>> from math import pi
>>> pi, id(pi)
(3.141592653589793, 4465320624)


Казалось бы мы затерли pi, но мы затерли его лишь в глобальной области видимости. Повторно импортируя pi, мы получаем старую переменную с тем же адресом, иными словами мы достаем ее из builtin области в global.

Вы знали о всех этих особенностях?
Зачем мне вообще нужны комплексные числа? – спросят многие из вас.
Но разве не круто извлечь корень из -1?

А вы посмотрите на этот классный синтаксис:

>>> 1 + 2j
(1+2j)
>>> type(1 + 2j)
<class 'complex'>


Самое чудесное, что простыми формулами на комплексных числах можно описывать бесконечно самоподобные структуры, например, множество Мандельброта.

c = x + 1j * y
z = 0j
for n in range(iters):
z = z ** 2 + c
if (z * z.conjugate()).real > 4.0:
break
img.putpixel((px, py), palette[n])


Заинтересованы?
​​Что если нужно импортировать модули не из директории проекта и не те, что установлены через pip, а из произвольного места на диске?
Конечно, можно было бы скопировать код оттуда в своей проект, но так не рекомендуется делать. Есть и другие решения.

В модуле sys есть переменная path. Она содержит список путей, в которых Python ищет названия модулей для импорта. Пожалуйста, не путайте sys.path и переменную окружения PATH (которая, кстати, доступна через os.environ['PATH']). Это разные вещи, последняя не имеет отношения к поиску модулей Python.

>>> import sys
>>> sys.path
['', '/usr/local/Cellar/python@3.8/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', ..., '/usr/local/lib/python3.8/site-packages']

Мы можем влиять на эту переменную, например, добавляя туда свои пути. Если добавить в начало списка, то поиск модулей начнется именно с нового пути.

import sys
sys.path.insert(0, '/Users/you/Projects/my_py_lib')
import my_module # этот модуль лежит в my_py_lib


Порядок операторов здесь важен. Нельзя сделать сначала import, потому что на момент импорта my_module система еще не знает, где его можно найти.

import sys
import my_module # ModuleNotFoundError
sys.path.insert(0, '/Users/you/Projects/my_py_lib') # поздно

Функция site.addsitedir тоже модифицирует sys.path, добавляя путь в конец списка. Еще она делает некоторые дополнительные вещи, но мы их не касаемся. Пример использования:

import site
site.addsitedir('/Users/you/Projects/my_py_lib')
import my_module


Также, набрав команду python3 -m site в командной строке, вы можете узнать пути для импорта в текущим интерпретаторе Python.

Минус способов с добавлением путей через sys.path и site – IDE скорее всего не будет видеть и индексировать эти динамические пути, а значит будет много красных подчеркиваний и отсутствие автодополнения, даже если код при этом прекрасно выполняется.

PYTHONPATH – переменная окружения, которую вы можете установить перед запуском интерпретатора. Будучи заданной, она также влияет на sys.path, добавляя пути поиска модулей в начало списка.

На Windows можно использовать команду set. Если надо задать два и более путей, разделите их точкой с запятой:

set PYTHONPATH=C:\pypath1\;C:\pypath2\
python -c "import sys; print(sys.path)"
# Пример вывода:
['', 'C:\\pypath1', 'C:\\pypath2', 'C:\\opt\\Python36\\python36.zip', 'C:\\opt\\Python36\\DLLs', 'C:\\opt\\Python36\\lib', 'C:\\opt\\Python36', ..., 'Python36\\lib\\site-packages\\Pythonwin']


На Linux и macOS можно использовать export. Два и более путей разделяются двоеточием:

export PYTHONPATH='/some/extra/path:/foooo'
python3 -c "import sys; print(sys.path)"
# Пример вывода
['', '/some/extra/path', '/foooo', ...]


Или даже в одну строку:

PYTHONPATH='/some/path' python3 -c "import sys; print(sys.path)"

Кто не знал, ключ -c для python3 просто выполняет строчку кода. И да, лишних пробелов вокруг знака равно не должно быть, это такой синтаксис.

Если вам заранее известны пути импорта дополнительных модулей, можно задать их прямо в IDE. На примере PyCharm, заходите в настройки: Project: ваш проект – Project Structure – Add Content Root.

Таким образом, у вас будут работать все фишки IDE для импортированных по сторонним путям модулей, но код будет запускаться корректно только из этой IDE, а чтобы запустить его из-вне, например из терминала, придется все равно прописать PYTHONPATH.
Левенштейн на практике, это, например, нечеткое сравнение строк:

>>> from fuzzywuzz import fuzz
>>> fuzz.token_sort_ratio("я люблю спать", "Я люблю спать!")
100
>>> fuzz.token_sort_ratio("я люблю спать", "я люблю есть")
56


Или нечеткий поиск текста:

from fuzzywuzzy import process

strings = ['привет', 'здравствуйте', 'приветствую', 'хай', 'здорова', 'ку-ку']
process.extract("Прив", strings, limit=3)
# [('привет', 90), ('приветствую', 90), ('здравствуйте', 45)]


Интересно? Тогда читайте новую заметку.
Задачка. Есть глобальная переменная g. В функции мы делаем del g.

g = 100

def f():
global g
g = 200
del g
g = 300

f()
print(g)
Что будет выведено на экран pring(g)?
Anonymous Quiz
17%
100
5%
200
47%
300
31%
NameError (удалили же g)
Инструкция del (от англ. delete), как можно понять из названия, нужна чтобы что-то удалять, а именно имена переменных, атрибуты объектов, элементы списков и ключи словарей.

1. Удаление элемента из списка по индексу:

>>> x = [1, 2, 3, 4, 5]
>>> del x[2]
>>> x
[1, 2, 4, 5]


Также можно удалять по срезам. Пример: удаление первых двух элементов:

>>> x = [1, 2, 3, 4, 5]
>>> del x[:2]
>>> x
[3, 4, 5]


Удаление последних n элементов: del x[n:].
Удаление элементов с четными индексами: del x[::2], нечетными: del x[1::2].
Удаление произвольного среза: del x[i:j:k].

Не путайте del x[2] и x.remove(2). Первый удаляет по индексу (нумерация с 0), а второй по значению, то есть находит в списке первую двойку и удаляет ее.

2. Удаление ключа из словаря. Просто:

>>> d = {"foo": 5, "bar": 8}
>>> del d["foo"]
>>> d
{'bar': 8}

А вот строки, байты и сеты del не поддерживают.

3. Удаление атрибута объекта.

class Foo:
def __init__(self):
self.var = 10

f = Foo()
del f.var
print(f.var) # ошибка!


Примечание: можно через del удалить метод у самого класса (del Foo.method), но нельзя удалить метод у экземпляра класса (del Foo().method - AttributeError).

4. Что значит удалить имя переменной? Это просто значит, что надо отвязать имя от объекта (при этом если на объект никто более не ссылается, то он будет освобожден сборщиком мусора), а само имя станет свободно. При попытке доступа к этому имени после удаления будет NameError, пока ему снова не будет что-то присвоено.

>>> a = 5
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined


Здесь кроется один нюанс. Если переменная была внутри функции помечена, как global, то после ее удаления глобальная переменная никуда не денется, а имя освободится лишь в зоне видимости функции. Причем если мы снова присвоим ей значение, то она опять окажется глобальной, т.е. del не очищает информацию о global!

g = 100
def f():
global g
g = 200
del g # g останется вне фукции
g = 300 # таже самая глобальная g

f()
print(g) # 300


Чтобы реально удалить глобальную переменную, можно сделать так: del globals()['g'].

В пунктах 1, 2, 3 в качестве имен могут фигурировать выражения и ссылки, так как операции идут над содержимым объектов, а в пункте 4 должно быть строго формальное имя удаляемого объекта.

>>> x = [1, 2, 3]
>>> y = x
>>> del y # удаляет именно y, но x остается
Еще одна особенность del – она может удалить несколько вещей за раз, если передать в нее кортеж или список объектов на удаление.

x, y, z = 10, 20, [1, 2, 3]
del x, y, z[2]

Пусть задан список из 5 элементов:

x = [1, 2, 3, 4, 5]
del x[2], x[4]