На
форуме БУ нередко сталкиваюсь с игроделами, которые пытаются писать скрипты методом научного тыка (а иногда даже ненаучного) и бездумным переделыванием других скриптов. Полагая, что это происходит из-за непонимания сути питоновых объектов, я попытаюсь наглядно показать, что вообще творится в питоне во время создания и изменения всяких разных переменных и классов.
(Мне почему-то постоянно говорят, что в BGE питон какой-то особенный и «дайте мне урок на блендеровый питон» и всё такое, мне уже начинает надоедать писать: ОБЫЧНЫЙ ПИТОН И ПИТОН В БЛЕНДЕРЕ АБСОЛЮТНО ОДИНАКОВЫЕ И СОВЕРШЕННО НИЧЕМ НЕ ОТЛИЧАЮТСЯ! В BGE лишь можно импортировать модуль bge, но и для него верно абсолютно всё написанное ниже и в любых других уроках по питону.)В Python всё является объектом. Массивы, классы, строки, числа, функции, модули, типы и даже None являются полноценными объектами, которые содержат в себе различные атрибуты и методы.
Объекты сами по себе ниоткуда не берутся. При запуске скрипта гарантировано присутствуют лишь следующие объекты:
__builtins__
__doc__
__name__
__package__
И с ними сразу можно что-то делать:
print(__name__)
(Скриншот из блендера)И даже Blender Game Engine не добавляет никаких объектов: ни cont, ни scene, ни obj никакие ниоткуда сами не берутся: их должен доставать откуда-то сам скрипт. (А делается это импортированием модуля bge и вызовом оттуда нужных функций вроде bge.logic.getCurrentController().)
Объект __builtins__ — особый объект, он содержит в себе все стандартные типы и функции. Когда вы пишете:
import bge
то на самом деле выполняется примерно следующее:
bge = __builtins__.__import__("bge")
По сути через этот __builtins__ всё и работает, и можно, например, что-нибудь в нём сломать (например, тот же импорт).
def imp(*args, **kwargs):
print("Я СЛОМАЛ ИМПОРТ!!!")
__builtins__.__import__ = imp
import bge # вывод: Я СЛОМАЛ ИМПОРТ!!!
print(bge) # вывод: None
При создании
переменной (любой: хоть число, хоть массив, хоть модуль, хоть None — у нас всё объекты) в неё записывается
ссылка на объект, а сам объект валяется где-то в оперативной памяти далеко от самой переменной со ссылкой.
arr = [1, 2, 3]
Схематично такое выглядит так:
(Сами переменные, разумеется, тоже хранятся где-то в оператинвой памяти, но во избежание загромождения рисунка я буду рисовать их (и иногда сами объекты) отдельно.)Дальше важно понимать, что при присваивании объекта переменной с самим объектом ничего не творится: в переменную лишь записывается ссылка на него и всё. Поэтому при таком действии
b = arr
Получится следующее:
Соответственно, если мы попробуем в объекте что-нибудь поменять, обе переменные всё равно будут возвращать одно и то же.
b[1] = 8
print(arr) # вывод: [1, 8, 3]
print(b) # вывод: [1, 8, 3]
И сам объект можно рассматривать как кучку переменных, доступ к которым можно получить с помощью этой самой точки. Собственно выше я и подменил значение переменной __builtins__.__import__ на своё:
Удалить объект нельзя, можно лишь удалить все ссылки на него.
del imp
__builtins__.__import__ = None
Но, как правило, когда ссылок на объект не остаётся, просыпается сборщик мусора и всё удаляет.
Создание собственных объектов осуществляется созданием класса и экземпляров этого класса. Класс тоже является объектом.
class Test:
a = 0
b = 0
(CPython заранее создаёт объекты с некоторыми маленькими числами, чтобы побыстрее работало)А вот дальше начинается магия. При создании экземпляра класса ОН БУДЕТ ПУСТОЙ, лишь переменная __class__ будет в нём.
obj = Test()
В случае, если мы попытаемся обратиться к переменной a нашего obj, он при её отсутствии у себя вытянет её из своего класса:
print(obj.a) # вывод: 0
Test.a = 3
print(obj.a) # вывод: 3
А вот при попытке присвоить obj.a какое-нибудь число мы уже создадим переменную в самом объекте:
obj.a = 7
print(Test.a) # вывод: 3
print(obj.a) # вывод: 7
del obj.a
print(obj.a) # вывод: опять 3
Собственно такое присваивание чаще всего и используют в функции __init__, которая вызывается каждый раз при создании экземпляра класса. Кстати, все функции, объявленные внутри класса, принимают первым аргументом объект, через который была вызвана функция.
class Num:
def __init__(self, n):
self.num = n
def show(self):
print(self.num)
obj1 = Num(3)
obj2 = Num(4)
print(obj1.num) # вывод: 3
obj1.show() # вывод: 3
print(obj2.num) # вывод: 4
obj2.show() # вывод:4
С функциями всё то же самое, что и с объектами: в самом объекте их нет, они есть лишь в классе. И их можно вызывать прямо из класса.
Num.show(obj2) # вывод: 4
И функцию в классе тоже можно подменить (но делать этого лучше не надо):
def fakenum(self):
print(self.num + 100)
Num.show = fakenum
obj1.show() # вывод: 103
Но тут уже должен был возникнуть вопрос: что вообще за self, что за аргументы, откуда они взялись, где и как хранятся? Тут пора вводить понятие
пространства имён. Оно определяет, какие переменные в каком месте будут видны. Ранее до появления функций мы работали с
глобальным пространством имён. Все эти __builtins__, arr, Test, Num, obj1 и obj2 хранились именно в нём. Технически это самый обычный словарь, то есть абсолютно такой же объект, как и все остальные. Его можно получить с помощью функции globals() и творить с ним что угодно.
globals()['a'] = 5
print(a) # вывод: 5
globals()['a'] = "Hello"
print(a) # вывод: Hello
Помимо этого есть
локальное пространство имён, которое у каждой функции своё. Вне функций локальное пространство имён совпадает с глобальным, и это можно проверить, получив его с помощь функции locals():
print( globals() is locals() ) # вывод: True
Так как это такие же самые объекты, их (точнее, ссылки) можно точно так же присвоить любой переменной. Не забывайте, что всё есть объекты, а переменные есть ссылки на эти объекты.
g = globals()
print(g['g']) # вывод: {'g': {...} и так далее
# это же получилась ссылка на самого себя!
print(g['g']['g']['g']['g']['g']['g']['g']['g']['g']['g']['g']['g']['g']['g'])
# вывод: {'g': {...} и так далее
print(globals() is g['g']['g']['g']) # вывод: True
При вызове функции создаётся своё локальное пространство имён, в которое скидываются все переданные аргументы.
def test(a, b): print( locals() )
test(3, 4) # вывод: {'b': 4, 'a': 3}
test("q", "w") # вывод: {'b': 'w', 'a': 'q'}
Разумеется, вне функции достучаться до её локального пространства имён никак нельзя (у каждого её вызова оно своё, функция может выполняться одновременно в нескольких потоках каждый со своим локальным пространством имён, может вообще не выполняться и так далее). (А вот к тому глобальному пространству имён, в котором она работает (у каждого модуля своё глобальное пространство имён), можно достучаться через атрибут __globals__)
При обращении к переменной приоритет имеет локальное пространство имён: если переменная с требуемым названием там существует, то возвращается значение из этого самого локального пространства имён; если её там нет, то из глобального; если и там нет, то питон ругается ошибкой. При создании переменных внутри функции они создаются в локальном пространстве имён (даже если такая же переменная есть в глобальном). Чтобы использовать только глобальную переменную, можно использовать ключевое слово globals. И далее в примерах всё сказанное выше:
a = 5
def test1():
print(a)
test1() # вывод: 5
def test2(a):
print(a)
test2(3) # вывод: 3 - имеется локальная переменная a
test2(None) # вывод: None - это тоже объект, и переменная так же существует
def test3(b):
a = b
print(a)
print(a) # вывод: 5
test3(3) # вывод: 3
print(a) # вывод: по-прежнему 5, потому что функция создала локальную переменную a
def test4(b):
globals a
a = b
print(a)
print(a) # вывод: 5
test4(0) # вывод: 0
print(a) # вывод: 0 - функция заменила значение глобальной переменной