globals(), locals(), vars(), dir()
Программист на Python может узнать, какие именно переменные определенны в данный момент в интерпретаторе. Переменные можно разделить на локальные и глобальные. Глобальные определены на верхнем уровне кода снаружи функций и классов (грубо говоря без отступов слева). Локальные переменные наоборот определены внутри своих зон видимости, ограниченных классами и функциями.
Функция globals() выдает словарь глобальных переменных (ключ – имя переменной). Функция locals() возвращает словарь только локальных переменных. Пример:
Обратите внимание, что переменная y в locals() имеет другое значение, нежели чем в globals(). Это две разные переменные из разных областей, но внутри функции приоритет имеет локальная y.
Еще важно знать, что в список переменных входят не только простые переменные, которые вы определяете через знак присваивания, но и функции, классы и импортированные модули!
Через словари из locals() и globals() переменные можно не только читать, но и создавать, перезаписывать и удалять:
Функция vars() ведет себя как locals(), если вызвана без аргумента, а если с аргументом, то она просто получает
В глобальном контексте все три функции возвращают одно и тоже – глобальные переменные. Проверьте:
Функциия 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:
Зоны видимости отделяются только функциями, классами и модулями. Здесь все переменные x – разные:
Самая широкая зона видимости называется builtin. В нее попадают все имена, известные интерпретатору в данный момент, включая вещи импортированные из других модулей.
Казалось бы мы затерли pi, но мы затерли его лишь в глобальной области видимости. Повторно импортируя pi, мы получаем старую переменную с тем же адресом, иными словами мы достаем ее из builtin области в global.
Вы знали о всех этих особенностях?
В отличие он некоторых других языков в 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?
А вы посмотрите на этот классный синтаксис:
>>> 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])Заинтересованы?
tirinox.ru
Комплексные числа в Python. Бонус: фрактал
В Python есть встроенный тип данных complex, который моделируют комплексные числа. По-моему, теория комплексных чисел – настоящий прорыв в математике, оказавший колоссальное влияние на современную физику. Неудивительно, что комплексные числа оказались в стандартной…
Что если нужно импортировать модули не из директории проекта и не те, что установлены через pip, а из произвольного места на диске?
Конечно, можно было бы скопировать код оттуда в своей проект, но так не рекомендуется делать. Есть и другие решения.
В модуле sys есть переменная path. Она содержит список путей, в которых Python ищет названия модулей для импорта. Пожалуйста, не путайте
Мы можем влиять на эту переменную, например, добавляя туда свои пути. Если добавить в начало списка, то поиск модулей начнется именно с нового пути.
Порядок операторов здесь важен. Нельзя сделать сначала import, потому что на момент импорта my_module система еще не знает, где его можно найти.
Функция
Также, набрав команду
Минус способов с добавлением путей через
PYTHONPATH – переменная окружения, которую вы можете установить перед запуском интерпретатора. Будучи заданной, она также влияет на sys.path, добавляя пути поиска модулей в начало списка.
На Windows можно использовать команду set. Если надо задать два и более путей, разделите их точкой с запятой:
На Linux и macOS можно использовать export. Два и более путей разделяются двоеточием:
Или даже в одну строку:
Кто не знал, ключ
Если вам заранее известны пути импорта дополнительных модулей, можно задать их прямо в IDE. На примере PyCharm, заходите в настройки: Project: ваш проект – Project Structure – Add Content Root.
Таким образом, у вас будут работать все фишки IDE для импортированных по сторонним путям модулей, но код будет запускаться корректно только из этой IDE, а чтобы запустить его из-вне, например из терминала, придется все равно прописать PYTHONPATH.
Конечно, можно было бы скопировать код оттуда в своей проект, но так не рекомендуется делать. Есть и другие решения.
В модуле 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.
Знаю, давно ничего для вас не писал. Но я исправляюсь.
tirinox.ru
Расстояние Левенштейна на Python
Как понять насколько близки две строки? Как поисковая система все равно находит то, что надо, даже если вы совершили пару опечаток в запросе? В этом вопросе нам поможет расстояние по Левенштейну или редакционное расстояние. Почему редакционное? Потому что…
Левенштейн на практике, это, например, нечеткое сравнение строк:
Или нечеткий поиск текста:
Интересно? Тогда читайте новую заметку.
>>> 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)]Интересно? Тогда читайте новую заметку.
tirinox.ru
Нечеткое сравнение текстов на Python с FuzzyWuzzy
Недавно мы обсуждали расчет расстояния Левеншейна, настало время испытать его применение на практике. Библиотека FuzzyWuzzy содержит набор функций для нечеткого поиска строк, дедупликации (удаления копий), корректировки ошибок. Она позволяет стать поиску…
Задачка. Есть глобальная переменная
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. Удаление элемента из списка по индексу:
Также можно удалять по срезам. Пример: удаление первых двух элементов:
Удаление последних n элементов:
Удаление элементов с четными индексами:
Удаление произвольного среза:
Не путайте
2. Удаление ключа из словаря. Просто:
А вот строки, байты и сеты del не поддерживают.
3. Удаление атрибута объекта.
Примечание: можно через del удалить метод у самого класса (
4. Что значит удалить имя переменной? Это просто значит, что надо отвязать имя от объекта (при этом если на объект никто более не ссылается, то он будет освобожден сборщиком мусора), а само имя станет свободно. При попытке доступа к этому имени после удаления будет
Здесь кроется один нюанс. Если переменная была внутри функции помечена, как
Чтобы реально удалить глобальную переменную, можно сделать так:
В пунктах 1, 2, 3 в качестве имен могут фигурировать выражения и ссылки, так как операции идут над содержимым объектов, а в пункте 4 должно быть строго формальное имя удаляемого объекта.
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 – она может удалить несколько вещей за раз, если передать в нее кортеж или список объектов на удаление.
Пусть задан список из 5 элементов:
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]Чему будет равен список x?
Anonymous Quiz
11%
x = [1, 3, 5]
64%
x = [1, 2, 4]
2%
x = [1, 2, 4, 5] без ошибок
23%
x = [1, 2, 4, 5] с ошибкой
Подписчик просил меня показать пример игры по сети на Python, и вот я созрел!
Это будет карточная игра Дурак.
1️⃣ В первой части у нас будет логика игры (пока без сети на одной машине).
2️⃣ Во второй части я расскажу, как в локальной сети (включая WiFi дома или в любимой кафешке) автоматически находить себе соперников с помощью протокола UDP. Да-да, долой скучные клиент-серверные TCP соединения, как во всех обыденных примерах в интернете! 😎
3️⃣ В третьей части мы уже прикрутим полноценное сетевое взаимодействие между клиентами, которые уже нашли друг друга. Тоже на UDP.
4️⃣ И, если захотите, в четвертой части добавим графический интерфейс и протестируем игру в реальных условиях. ☕️
Это будет карточная игра Дурак.
1️⃣ В первой части у нас будет логика игры (пока без сети на одной машине).
2️⃣ Во второй части я расскажу, как в локальной сети (включая WiFi дома или в любимой кафешке) автоматически находить себе соперников с помощью протокола UDP. Да-да, долой скучные клиент-серверные TCP соединения, как во всех обыденных примерах в интернете! 😎
3️⃣ В третьей части мы уже прикрутим полноценное сетевое взаимодействие между клиентами, которые уже нашли друг друга. Тоже на UDP.
4️⃣ И, если захотите, в четвертой части добавим графический интерфейс и протестируем игру в реальных условиях. ☕️
tirinox.ru
Дурак по сети на Python: часть 1
Давайте попробуем разработать сетевую игру на Python, чтобы можно было играть по локальной сети. Думаю, начать надо именно с логики игры, а потом добавить уже сетевое взаимодействие. Я выбрал в качестве игры – карточную игру «Дурак», чтобы, во-первых, не…
Статья по второй части сетевой игры в Дурака готова! В ней расскажу о простеньком самодельном сетевом протоколе обнаружения клиентов по UDP. Он работает в пределах локальной сети (домашнего, кафешного, классного WiFi и т.п.) Протокол хорош тем, что вам не нужно запускать отдельный сервер и передавать клиентам его IP адрес. Клиенты игры, желающие начать новую партию сами найдут друг друга. Децентрализация!
tirinox.ru
Дурак по сети на Python: часть 2 – обнаружение
Надеюсь, вы уже ознакомились с частью 1. В этой части мы начнем реализовывать сетевые взаимодействия. Обычно в статьях по сетевому программированию нам предлагают использовать клиент-серверную модель по протоколу TCP. Тут кроется пара неудобств. Во-первых…
Напомню, что репозиторий игры по адресу https://github.com/tirinox/durakmq. Буду рад вашим звездочкам! 😋
Хотите часть 4, где мы прикрутим GUI – графический интерфейс к игре?
Anonymous Poll
88%
Да, конечно.
12%
Нет, не стоит.
Часть 3 – сетевая игра в Дурака.
В этой части рассматриваем аспекты обмена сообщения между двумя клиентами игры: сериализацию, отправку сообщений, создаем поток для приема сообщений, обрабатываем команды от пользователя. Короче, доводим игру до играбельного состояния и следим за соблюдением правил. https://xn--r1a.website/iv?url=https://tirinox.ru/durak-game-p3/&rhash=56b30beec7290d
В этой части рассматриваем аспекты обмена сообщения между двумя клиентами игры: сериализацию, отправку сообщений, создаем поток для приема сообщений, обрабатываем команды от пользователя. Короче, доводим игру до играбельного состояния и следим за соблюдением правил. https://xn--r1a.website/iv?url=https://tirinox.ru/durak-game-p3/&rhash=56b30beec7290d
tirinox.ru
Дурак по сети на Python: часть 3 – игра
В части 2 я привел способ обнаружения соперника в локальной сети. Как только двое игроков нашли друг друга, они могут начать игру, обмениваясь UDP пакетами напрямую, т.е. посылая их непосредственно по IP адресу и нужному порту. Да, номера портов вы выбираете…
Работа над частью 4 идет полным ходом. Я прикручиваю к сетевой игре в Дурака графический интерфейс на Kivy. Признаюсь честно, я раньше не работал с Kivy, поэтому учусь и делаю этот проект параллельно, встречая множество трудностей по собственному незнанию. Но дело тем не менее продвигается, и в ближайшие дни я закончу первую версию и выложу код с объяснениями. Самое классное, что игру можно сразу собирать под смартфон (проверял на Андроиде). Поэтому сможем прийти в кафе и поиграть. Пока вот скриншот-тизер:
Блог разработки Durak GUI
По Kivy в интернете реально мало толковой информации даже на английском языке. Всевозможные туториалы примитивны и просто копируют друг друга. Некоторые из них уже не работают на современной версии Kivy. В целом они не покрывают практически никакие вопросы, возникающие в процессе разработки реального приложения. Как узнать размер окна? Не известно наверняка. Приходится копаться в исходном коде движка, чтобы понять. Как сделать элементарное окно типа Alert с кнопкой и текстом? Да никто не знает. В документации есть картинка такого окна, но код рядом просто не соответствует.
Какие решения я вижу в этой ситуации? Можно присылать вопросы на stackoverflow.com (долго, и не факт, что ответят).
Я рекомендую искать уже готовый открытый код приложений на Kivy либо в репозиториях на GitHub, либо, например, на сайте searchcode.com. По аналогии с тем, как кто-то сделал до вас можно кое-как разобраться.
Касательно проекта игры. Я начал со стандартных раскладок, используя BoxLayout, StackLayout и подобные инструменты. Вид игры рисовался скучно, топорно и кривовато, как вы можете заменить на скриншоте из предыдущего поста. На мои вопросы не находилось никаких ответов. Об анимациях даже не приходилось и думать в таком положении вещей, ибо Layout берут на себя контроль над положением и размером виджетов.
Как давнего игродела, меня это совершенно не устраивало, и я решил радикально все переделать. Все игровые объекты будут располагаться на FloatLayout, который дает программисту полный контроль над положением и размерами виджетов. Далее я начертил чертеж, где вычислил координаты каждой карты на экране. Например, карты текущего игрока располагаются на дуге окружности радиусом в 0.9 от ширины экрана и центром ниже нижней кромки экрана. Угловые границы дуги: от -30º до 30º относительно вертикали. Также, добавил вращение карт путем матричных преобразований, дабы карты выстраивались в традиционный веер.
Далее каждый виджет я наделил атрибутами
Условно каждый кадр (1/60 долю секунды), реальное положение
Это все! Просто меняя атрибуты
По Kivy в интернете реально мало толковой информации даже на английском языке. Всевозможные туториалы примитивны и просто копируют друг друга. Некоторые из них уже не работают на современной версии Kivy. В целом они не покрывают практически никакие вопросы, возникающие в процессе разработки реального приложения. Как узнать размер окна? Не известно наверняка. Приходится копаться в исходном коде движка, чтобы понять. Как сделать элементарное окно типа Alert с кнопкой и текстом? Да никто не знает. В документации есть картинка такого окна, но код рядом просто не соответствует.
Какие решения я вижу в этой ситуации? Можно присылать вопросы на stackoverflow.com (долго, и не факт, что ответят).
Я рекомендую искать уже готовый открытый код приложений на Kivy либо в репозиториях на GitHub, либо, например, на сайте searchcode.com. По аналогии с тем, как кто-то сделал до вас можно кое-как разобраться.
Касательно проекта игры. Я начал со стандартных раскладок, используя BoxLayout, StackLayout и подобные инструменты. Вид игры рисовался скучно, топорно и кривовато, как вы можете заменить на скриншоте из предыдущего поста. На мои вопросы не находилось никаких ответов. Об анимациях даже не приходилось и думать в таком положении вещей, ибо Layout берут на себя контроль над положением и размером виджетов.
Как давнего игродела, меня это совершенно не устраивало, и я решил радикально все переделать. Все игровые объекты будут располагаться на FloatLayout, который дает программисту полный контроль над положением и размерами виджетов. Далее я начертил чертеж, где вычислил координаты каждой карты на экране. Например, карты текущего игрока располагаются на дуге окружности радиусом в 0.9 от ширины экрана и центром ниже нижней кромки экрана. Угловые границы дуги: от -30º до 30º относительно вертикали. Также, добавил вращение карт путем матричных преобразований, дабы карты выстраивались в традиционный веер.
Далее каждый виджет я наделил атрибутами
target_position и target_rotation – это позиция и угол поворота, куда стремиться карта со временем. Задал такой интервал:Clock.schedule_interval(self.update, 1.0 / 60.0)Условно каждый кадр (1/60 долю секунды), реальное положение
pos карты становится чуть ближе к ее целевому положению target_position. Движение получается экспоненциально затухающим: чем ближе карта к цели, тем она медленнее движется, поэтому анимации получились вполне естественные.EXP_ATT = 5.0
def update(self, dt):
df = self.EXP_ATT * dt
for child in self.root.children:
if hasattr(child, 'target_position'):
x, y = child.pos
# компенсируем положение точки, смещая ее из нижнего левого угла в середину виджета
x += child.size[0] / 2
y += child.size[1] / 2
tx, ty = child.target_position
if fast_dist(x, y, tx, ty) >= 0.1:
x += (tx - x) * df
y += (ty - y) * df
# возвращаем обратно из середины точку к углу
child.pos = (x - child.size[0] / 2, y - child.size[1] / 2)
if hasattr(child, 'target_rotation'):
tr, r = child.target_rotation, child.rotation
if abs(tr - r) >= 0.1:
child.rotation += (tr - r) * dfЭто все! Просто меняя атрибуты
target_position и target_position можно метать карты по столу с приятной анимацией. Ниже прикреплю GIF-анимацию.