#8. Паттерн "Моносостояние" | Объектно-ориентированное программирование Python
HTML-код
- Опубликовано: 26 ноя 2021
- Курс по Python ООП: stepik.org/a/116336
Делаем класс, у которого объекты имеют единое локальное пространство, единые локальные атрибуты - паттерн "Моносостояние".
Плейлист по Python ООП: • Объектно-ориентированн...
Инфо-сайт: proproprogs.ru/python_oop
Telegram-канал: t.me/python_selfedu
Паттерны проектирования: порождающие, структурные, поведенчиские. Пожалуйста, Сергей! Если сделать отдельные видео, где объяснить кратко о каждом, на простом примере кода и добавить их потом в плейлист, то это будет топ в ютюбе! Услыште нас! Мы хотим паттерны.
Парни поставте лайк, чтоб Сергей увидел.
up
Такого наставника хотели бы многие студенты.Спасибо вам огромное
Интересная вещь, хотя и пришлось немного поломать мозги, чтобы понять до конца, почему оно работает именно так. Долго не мог понять, как после обозначения нового атрибута "th1.attr_new" он попадает в "__shared_attrs"? Пришлось покопать и потом дошло, что "__dict__" лишь указывает на словарь, который в данном случае является свойством класса, общим для всех экземпляров.
Другими словами, строку
th1.attr_new = "new_attr"
нагляднее можно записать так:
th1.__dict__["attr_new"] = "new_attr" - то есть через "__dict__" мы обращаемся к словарю, на который "__dict__" указывает, и создаём там новую пару ключ-значение, что одновременно является новым атрибутом класса.
но у нас же должны только shared_attr быть локальными, как он добавляется то...
@@user-no9zt9dh4o Мне тоже долго пришлось понять, но, все-таки дошло. если кеу нет в __shared_attr то он просто добавляет это значение в предыдущую словарь, а если есть то обновляет значение.
class Point:
fuck = {'first': 1, 'second': 2}
def __init__(self):
self.__dict__ = {'its me': 456}
p = Point()
p.__dict__
>> {'its me': 456}
p.newer = 'new!!!'
p.__dict__
>>{'its me': 456, 'newer': 'new!!!'}
p.newer = 'i am already old :('
p.__dict__
>>{'its me': 456, 'newer': 'i am already old :('}
проще говоря, мы создаем новое значение в __shared_attrs?
@@HubaBuba2007 да
Ура, ура паттерны пошли. Сергей, пожалуйста продолжайте про паттерны. Мы хотим паттерны с примерами!
Спасибо, с нетерпением жду каждого нового урока.
Спасибо огромное! Да, ждем отдельный плейлист про паттерны)
кто не до конца понял почему так получается сделайте 2 словаря А и В. И далее операцию А = В. После этого добавив в любой словарь новый ключ со значением, во втором также окажется эта новая пара. так работают в Питоне ссылки на изменяемые объекты. Так и в нашем примере все объекты имеют общий словарь через ссылку при инициализации и присвоив новый аттрибут вы доавляете его в __dict__и значит __shared_attr , тем самым меняя __dict__ во всех остальных объектах.
Сергей, большое спасибо, очень полезный урок!
Спасибо вам большое за ваш труд. Очень полезные уроки
Спасибо! Очень интересно! Паттерны это интересная тема!
Спасибо за уроки!
Информация из ролика ещё дополняется крупицами информации из комментариев.
Спасибо большое, у вас самое подробные уроки в отличии от остальных блогеров, да ещё они вздумали своими школами обзавестись, так что от них нечего чего то ждать существенного.
Интересная тема, спасибо, Сергей!
я вроде и понял и не понял суть паттерна.
Ведь мы же можем создать атрибуты класса типо MIN_COORD и MAX _COORD, и через классметод прописать их изменение, тогда для всех объектов класса. также будут общие одинаковые данные, которые будут меняться для всех
Пример использования. Если несколько пользователей работают с программой и вносят измененя, например, в базу данных и нужно, что эти измененя учитывались при дальнейшей работе. Например у меня есть БД товаров с остатками и несколько пользователей продают эти товары. Мне нужно, что бы после того, как пользователь 1 продал 1шт какого-то товара, остальные пользователи (все) видели, что на остатке уже 99, а не 100, чтобы не продать несуществующий товар.
Тогда это будут атрибуты класса, а не объекта. Да, к ним есть доступ через объекты, и для всех объектов они будут одинаковыми. Но, например, если мы пропишем для какого-либо объекта новый атрибут, которого нет у класса, то это будет атрибут только этого конкретного объекта, а не всех объектов этого класса. И моносостояния не случится. Нам же надо , чтобы у всех объектов класса всегда были одни и те же атрибуты, и если меняется или добавляется какой-нибудь атрибут через какой-нибудь объект, то это должно происходить для всех объектов.
Серёга, ты просто красавчик
Спасибо. Очень интересно.
Я думаю может пригодиться если надо что-то сразу бэкапить😌 либо когда требуется создавать клоны , полезный материал
Короче, кто не понял магии.
У экземпляра класса есть свои локальные атрибуты. Они хранятся в __dict__. Это словарь, который автоматически создаётся при создании объекта (или инициализации, хз). Но в данном паттерне мы как бы подменяем родной __dict__ экземпляра на __shared_dict самого класса. И, соответственно, когда мы добавляем/удаляем/меняем атрибуты экземпляра класса, мы меняем их, по факту, в __shared_dict.
Если всё равно не понятно, то запустите код ниже. Он делает, по сути, тоже самое.
a = [1,2,3]
b = a
c = a
b[1] = 'ай'
c[0] = 'ой'
print(a)
print(b)
print(c)
Присоединяюсь к очереди за паттернами =)
отлично, спасибо
спасибо!
лайк и комментарий в поддержку канала.
Прикольно)
👍👍
прикольно
Как всегда респектище! С другой стороны, если этот метод нигде не применяется, то какой в нём смысл? И ещё, хотелось бы слышать от Вас одну вещь, которую Вы не озвучиваете ни в одном своём ролике: что к примеру данный метод применяется в написании таких то программ, а вот этот метод применяется вот для для этого и так далее. Потому, что выучив язык надо определиться какую область программирования(а не решение задач) дальше изучать, а это новый курс, на основе пройденного.
Спасибо! Расскажите про вложенные(внутренние) классы пожалуйста.
будут дальше )
👍
❣️
эх это бы еще отрабатывать на задачах, а слушать и делать вещи разные, хоть и сижу параллельно с пайчармом. шеф делай на степике курс платный с задачами, купил бы сразу)
уже делаю, скоро будет! )
У меня есть задача, если интересно, по теме, но с осложнениями, в одном из коментов, как вы любите. ))
А чем это отличается от того, когда мы на прошлых уроках переопределяли метод __new__?
А в многопоточных приложениях такое моносостояние сохраняется? К примеру, в одном потоке мы считываем данные (например, из консольного приложения), а в другом потоке мы обрабатываем их.
А то я для хранения данных в много-потоках вместо этого использую статические свойства класса (не создавая экземпляры) или статические геттеры и сеттеры. Если что, я в Python не профессионал, это моё хобби.
Да, нормально делаете, т.е. обращаетесь к атрибутам класса только для чтения информации (с изменение нужно быть осторожнее, т.к. два и более потоков одновременно могут менять один и тот же атрибут).
Почему-то не получается вывести атрибуты. При такой инициализации в словаре экземпляра класса просто ссылка на функцию __init__ . Получается, как будто несуществующие поля
Все очень круто, но у меня есть вопрос: это же можно делать когда мы создадим Singlton. Будет ли какая-то разница? Или через Singlton это не уместно делать, но принцип тот же: ссылается на одно и то же. Спасибо.
Это отличается от Singleton, т.к. здесь нет запрета но создание множества экземпляров, просто у всех у них единые локальные свойства, но пространство имен, все же свое, независимое.
@@selfedu_rus а с точки зрения многопоточности есть разница, с чем работать?
В случае синглтона потоки будут обращаться к одной памяти, где хранится объект. В случае монопоточности - к одному участку памяти, где хранятся свойства. Вроде нет особой разницы. Это как я представляю.
Мои знания о многопоточности практически нулевые, так как я только азы изучил
Тут есть пару вопросов,1-как вы раньше упоминали если переменная существует в изначальном классе,то экземпляры класса наследуют ее в виде ссылки,а если мы эту переменную хотим изменить то она локально создается внутри экземпляра ,так вот не является ли self.__dict__ локальной переменной для каждого экземпляра,мне просто не очень понятно почему вы к th2 id присвоили 3 и почему то значение локальной переменной которая находится в локальной переменной __dict__ поменялось и у других экземпляров.тут под __dict__ я подразумеваю __shared_attrs,изначально shared_attr ведь переменная класса,которая наследуется в экземпляры,а присваивая th2.id =3 вы по сути создаете локальную переменную __shared_attrs внутри экземпляра,вместо ссылки из основного класса и в нее уже добавляете новый атрибут id=3
Видимо я понял,принцип работы словаря чуть отличается от обычных переменных ,словарь так и будет оставаться ссылкой от главного класса внутри экземпляра,даже если поменяем или добавим ему 1 переменную внутри и болле в экземпляре класса, и она будет менятся у экземляров,то есть меняя в экземпляре переменную словаря,он всерогно будет оставаться ссылкой от главного класса,он перестанет ей быть и станет локальной переменной,только в том случае ,если мы в самом экземпляре сменим тип словаря на любой другой
теперь остаётся узнать, почему это работает именно так)
@@antonivanov3830 потому что словарь изменяемый тип и мы от всех экземпляров ссылаемся на один и тот же словарь __shared_attrs, и поэтому, когда мы изменяем __dict__ какого либо экземпляра, то мы изменяем и __shared_attrs, а на него ссылаются и остальные экземпляры тоже
Для чего может использоваться данный паттерн или в каких задачах проявляются его сильные стороны?
Чтобы "self.__shared_attrs" нельзя было переобпределить нам его нужно инкапсулировать тем плагином, который закрывает возможность изменения свойства класса или через дескрипторы, верно?
да
Получается создать атрибуты отличные от других экземпляров классов уже не получится? А что при наследовании?
Да, не получится. И при наследовании также, т.к. мы на уровне экземпляров классов работаем
спасибо за урок. Все же не понимаю, как так получается что после добавления нового аргумента какому то из обьектов, этот же аргумент создается во всех экземлярах класса.
коллекция __dict__ у всех объектов одна и та же
блин у меня чуть голова не взорвалась в понимании механизма работы моносостояния, но так и не понял (( как же меняется словарь класса??
PS через полтора часа похоже понял - вся фишка в одинаковой ссылке на словарь.... но писец как не очевидно для новичков
Не понял , чем это отличается от статических атрибутов класса?
А куда изначально добавляется новый созданный атрибут в __shared_attr или в про-во имен экземпляра? Просто в коде мы пишем self.__dict__ = self.__shared_attrs , это равно пр-во имен экземпляра равно словарю shared? Как тогда это влияет на другие экземпляры?
в пространстве имен класса
У меня не работает моно... создаются как будто два разных экземпляра все равно
А можно реализовать этот паттерн просто переопределивши метод __setattr__, чтобы он устанавливал все атрибуты в окружение класса переданного экземпляра?
def __setattr__(self, key, value):
setattr(self.__class__, key, value)
Можно, но, по моему вариант в видео элегантнее.
А почему новый атрибут attr_new добавился в словарь __shared_attrs?
Потому что __shared_attrs ссылается на коллекцию __dict__ объекта класса.
@@selfedu_rus Но ведь
self.__dict__ = self.__shared_attrs
тут __dict__ ссылается на __shared_attrs.
А что сама __shared_attrs ссылается на что-либо в коде нет. Не пойму, в чём магия?
@@CrazyHandMaker ну, да, __dict__ на __shared_attrs, в итоге обе ссылки ведут на один и тот же словарь
@@CrazyHandMaker попробуйте представить что _dict_ и __shared_attrs это всего лишь навсего имена переменных, как ссылки, на одну и туже область памяти. Примитивно, но смысл тот же, мне помогает понимать.
@@alexanderg9089 Ну по факту так и есть. Но где это указывается?
А как так получается, что редактируя атрибут одного объекта задеваются остальные экземпляры? Да, в этот раз мы инициализировали объекты с помощью одного словаря, но что это изменило?
Локальные атрибуты классов определяются словарем __dict__. Если он един для всех объектов, то и значения также едины.
Скажите пожалуйста, почему self._shared_attrs вообще работает? ThreadData.__shared_attrs я проверил тоже работает и я понимаю почему, но почему работает с .self???
есть объяснение в предыдущих уроках, магия вседозволенности в Python. Через ThreadData вы обращаетесь к атрибутам класса, через self как к атрибутам класса так и к атрибутам экземпляра класса.
@@alexanderg9089 вот я и подумал, разве при инициализации __dict__ через self.__shared_attrs не будет создаваться пустой словарь в экземпляре класса? Оказалось, что не будет. И это привело меня в недоумение. Единственное объяснение - класс object проверяет наличие словаря с таким именем в базовом классе, если не находит в экземпляре и только если и там тоже нет - создает. Но корректнее, как по мне, пользоваться вызовом "BasicClass.__somearg", особенно в потоках.
Кто может сказать, можно ли создать моносостояние, но каким-то образом поменять атрибуты одного из объектов, не меняя атрибуты других?
Не знаю зачем тебе это, но конечно можно. Главное помнить, что после этого объект станет полностью независим от других.
import copy
class Node:
__coords = {"x": 0, "y": 0}
def __init__(self) -> None:
self.__dict__ = self.__coords
def free_state(self):
self.__dict__ = copy.deepcopy(self.__dict__)
def linked_state(self):
self.__dict__ = Node.__coords
a = Node()
b = Node()
c = Node()
a.x = 10
print(f"b.x = {b.x}, c.x = {c.x}")
c.free_state() # С этого момента с - независимый объект
a.x = 5
a.z = 7
c.y = 2
print(f"b.__dict__ = {b.__dict__}, c.__dict__ = {c.__dict__}")
c.linked_state() # С этого момента с - обратно связанный объект
print(f"b.__dict__ = {b.__dict__}, c.__dict__ = {c.__dict__}")
Результат:
---------------------------------------------
b.x = 10, c.x = 10
b.__dict__ = {'x': 5, 'y': 0, 'z': 7}, c.__dict__ = {'x': 10, 'y': 2}
b.__dict__ = {'x': 5, 'y': 0, 'z': 7}, c.__dict__ = {'x': 5, 'y': 0, 'z': 7}
пришел сегодня сертификат по Вашему курсу. в нем написано что я закончил с отличием хотя я прошел только 39% . странно
ошибка с моей стороны была, поправил, ну сертификат у вас уже останется, пользуйтесь :)
То есть получается у всех экземпляров класса будет одно и то же значение?
да
Вооот, а мне нужно и то и другое и причём в одном списке, как это сделать сейчас думаю. Некоторые экземпляры связаны потому что они то же самое но в разных местах, другие не связаны, хотя они в точности то же самое, просто атрибуты свои немного отличаются, так как ссылаются на разные физические пины контроллера.
Я вот только не понял, почему ссылка идет не на cls (cls.__shared_attrs), а на сам объект.
Словарь __shared_attrs определен в самом классе и общий для всех объектов.
разные ссылки ведущие к одному к экземпляру
🏃
Зачем __dict___ ?
class ThreadData:
name = 'thread1'
data = {}
id = 1
будет ровно тоже самое
Еще более странно зачем оно потребовалось в контектсте использования потоков где наоборот нужно губокое копрование что бы не попортить данные.
Потому что при попытке изменения переменных name,id и т.д. через экземпляр класса(к примеру, th1.id = 5), внутри локальных областей экземплярлв будут созданы локальные переменные, а атрибуты класса не изменятся
что такое паттерны ? зачем они ?
паттерн (схема, шаблон) построения архитектур больших программ
это тонкий намек на то что в Python выполняется только один поток?))
хотелось бы несколько задач под каждым видео, до выхода курса не дотерплю
можно взять из предыдущего круса
И очень занимательный факт получается. Если сравнивать объекты th1 is th2 то это будет False, а если сравнить th1.__dict__ is th2.__dict__ будет True
Потому что в классе не реализован магический метод, отвечающий за сравнение
про первые атрибуты понятно почему они общие, но почему новый атрибут и его значение стали общими не понял увы, подскажите кто нибудь понятными словами только)))
Я уже собирался накать простыню текста, но кто-то опередил, и рассказал суть. Упорядочи комментарии "Сначала новые" и увидишь ответ на свой вопрос на два комментария ниже. Если кратко, то когда A и B - это списки, словари, множества или объекты, то операция A = B *не копирует* B в A. Это ссылки, и A теперь ссылается на ту же область памяти, что и B.
В нашем случае у всех новых экземпляров объектов пространство имен "не свое", т.е. __dict__ будет ссылаться на один и тот же словарь __shared_attrs основного класса. Если будет непонятно после прочтения коммента ниже, то могу потом подробнее объяснить.
Понял кажется, то есть по сути при создании в одном экземпляре нового атрибута, этот атрибут и его значение запишутся в шаддер атт, так же как бы записалось в __дикт__ ?
@@MevErexспасибо за ответ)
Да, все верно поняли. Типичная ошибка многих новичков, которые после кода:
A = {"name": "Masha", "age": 18}
B = A
B["name"] = "Olya"
print(A["name"])
получают результат:
Olya
типа что за магия, я не трогал А вообще, как так. Более того, можно вынести это все вообще в отдельную переменную вне объектов и изменять только ёё, с тем же результатом.
C = ThreadData._ThreadData__shared_attrs
A = ThreadData()
B = ThreadData()
C["port"] = 8080
Теперь во всех экземплярах класса A и B появится новый атрибут port, а в основном классе ThreadData в словаре __shared_attrs добавится ключ "port" со значением 8080.
python не перестает меня удивлять, protected и private атрибуты есть, но толку от этих модификаторов доступа нет, так как их можно легко обойти. Как же я скучаю по С++, в котором если какой то метод private, то он private, и доступа к нему из вне класса нет как ты не крути, все просто и понятно не то что в якобы простом python
на самом деле и в С++ можно напрямую к приватным обратиться, но это уже будет извращением ))
прикольно
👍👍