Форум программистов, компьютерный форум, киберфорум
Viktorrus
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Понимание механизма, как работает декоратор в питоне.

Запись от Viktorrus размещена 20.11.2021 в 18:31
Обновил(-а) Viktorrus 26.11.2021 в 16:41

Рекомендация. Читать все это может оказаться утомительно. Поэтому советую сначала посмотреть видиоролик у Welemir1, а уже что будет там не понятно, можно будет посмотреть эти моменты в моем объяснении, так как оно предельно подробное.
Это хороший видео ролик уважаемого Welemir1.
https://www.youtube.com/watch?v=q4o_1cXAS-c

Наконец вроде полностью разобрался в понимании как работает декоратор.
Поясним, что такое функция.
Пусть у нас есть некоторая исходная функция func.
Любая функция в питоне является объектом. Любой объект питона размещается в оперативной памяти компьютера и имеет свой уникальный адрес. Когда мы говорим, что значением переменной var является некоторый объект, то это значит, что данная переменная содержит ссылку на данный объект (то есть содержит адрес этого объекта в оперативной памяти). Рассматриваемая нами исходная функция является объектом, размещенным в оперативной памяти, а имя функции func, это имя переменной, которая содержит ссылку на данный объект-функцию.
Когда мы указываем имя функции func, то мы обращаемся к этому объекту-функции. Если мы указываем имя функции с круглыми скобками func(), то мы вызываем эту функцию для ее выполнения.
Поясню, что делает декоратор (позже поясню, как он это делает).
Итак у нас определена некоторая исходная функция func.
Создается функция-декоратор decorator, которая может в качестве параметра принимать исходную функцию func и на базе исходной функции создавать новую функцию wrapper, базирующуюся на исходной функции (добавляя к исходной функции дополнительную логику, как бы декорируя исходную функцию). Замечу, что исходная функция func не изменяется, а создается на базе исходной именно новая функция wrapper И в оперативной памяти мы уже имеем две функции в виде объектов, которые будут иметь разные адреса в памяти.
Затем меняем ссылку в переменной func.
Если до этого, она содержала ссылку на объект исходной функции, то теперь переменная func будет иметь ссылку на объект новой функции. В результате на новую функцию будут иметь ссылку две переменные, не только wrapper но и переменная func. И теперь func() будет вызывать уже не исходную функцию, а новую функцию. Причем в оперативной памяти будут оставаться как новая функция так и старая исходная функция. При этом старая функция не будет питоном удаляться, так как к ней будет иметься ссылка изнутри новой функции.
Каким образом декоратор все это делает, я продолжу в комментарии.
Размещено в Без категории
Показов 1154 Комментарии 15
Всего комментариев 15
Комментарии
  1. Старый комментарий
    Итак разберемся как декоратор делает все то, что я описал выше.
    Для начала дам ссылки на материал, который помог мне разобраться.
    Это хороший видео ролик уважаемого Welemir1,
    https://www.youtube.com/watch?v=q4o_1cXAS-c
    И статьи из интернета
    https://habr.com/ru/post/141411/
    https://pythonworld.ru/osnovy/dekoratory.html
    https://proglib.io/p/vse-chto-... 2020-05-09
    Что касается Лутца, то у него часть про Декораторы написана слишком формально и текст труден для понимания, по крайней мере для первоначального изучения. Может он годится для закрепления знаний о декораторах? Не знаю, пока эту часть у Лутца внимательно не изучал.
    Итак приступим.
    Напишем например функцию-декоратор my_decorator, которая будет делать все то, что мы написали в разделе выше.
    Python
    1
    2
    3
    4
    5
    6
    
    def my_decorator(func):
        def wrapper():
            print('Работает код до вызова объекта_функции "obj_f1".')
            func()
            print('Работает код после вызова объекта_функции "obj_f1".')
        return wrapper
    Пусть у нас будет исходная функция func_1().
    Создаем в оперативной памяти объект-функцию (условно назовем этот объект "obj_f1"), разместив ссылку на этот объект в переменной func_1 (которую называют именем функции).
    На самом деле объект-функция, размещаемый в оперативной памяти не содержит внутри себя имени, а только имеет уникальный адрес в оперативной памяти, через который к этому объекту-функции можно обращаться. И именно этот адрес помещается в переменную func_1, что бы мы через нее могли обращаться к данному объекту-финкции.
    Python
    1
    2
    3
    4
    
    def func_1():  
        print('Работает исходная объект-функция "obj_f1"')
     
    func()    # Работает исходная объект-функция "obj_f1"
    Теперь, что бы использовать наш декоратор к объекту-функции "obj_f1", ссылка на которую находится в переменной func_1, напишем перед определением функции func_1() строку, сообщающую о применении к функции func_1 декоратора.
    Python
    1
    
    @my_decorator
    В итоге получаем код с функцией, использующей декоратор.
    Кликните здесь для просмотра всего текста
    Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    def my_decorator(func):
        def wrapper():
            print('Работает код до вызова объекта_функции "obj_f1".')
            func()
            print('Работает код после вызова объекта_функции "obj_f1".')
        return wrapper
     
    @my_decorator
    def func_1():  
        print('Работает исходная объект-функция "obj_f1".')
     
    func_1()     # вызов декорированной функции.
     
    # Работает код до вызова объекта_функции "obj_f1".
    # Работает исходная объект-функция "obj_f1".
    # Работает код после вызова объекта_функции "obj_f1".

    В абзаце ниже разберем, как это все работает.
    Запись от Viktorrus размещена 21.11.2021 в 16:06 Viktorrus вне форума
    Обновил(-а) Viktorrus 21.11.2021 в 16:18
  2. Старый комментарий
    Аватар для Avazart
    Это что такое было? Это типа сеанс "самовнушения" в духе я знаю что такое "декоратор" ?
    Или же все же попытка объяснить другим людям? Если второе то как по мне совсем неудачная.
    Запись от Avazart размещена 21.11.2021 в 16:43 Avazart на форуме
  3. Старый комментарий
    Avazart не обольщайтесь. В данном случае Ваше мнение для меня не важно. Я в первой теме написал для чего веду этот блог. Для того, что бы в случае если нужно выложить в какой то момент свое мнение в какой то теме на форуме, не сочинять его каждый раз снова, а брать из своего блога. А так как я выкладываю свое, а не чье-либо другое мнение, поэтому чьи либо оценки, а не конкретные замечания, меня не интересуют. Тем более, что я еще до конца не раскрыл эту тему, что бы кто то мог высказывать свои замечания.
    Запись от Viktorrus размещена 21.11.2021 в 19:30 Viktorrus вне форума
    Обновил(-а) Viktorrus 21.11.2021 в 19:42
  4. Старый комментарий
    Теперь рассмотрим, что пошагово делает питон встречая такой код.
    Кликните здесь для просмотра всего текста
    Python
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    def my_decorator(func):
        def wrapper():
            print('Работает код до вызова объекта_функции "obj_f1".')
            func()
            print('Работает код после вызова объекта_функции "obj_f1".')
        return wrapper
     
    @my_decorator
    def func_1():  
        print('Работает исходная объект-функция "obj_f1".')
     
    func_1()     # вызов декорированной функции.
     
    # Работает код до вызова объекта_функции "obj_f1".
    # Работает исходная объект-функция "obj_f1".
    # Работает код после вызова объекта_функции "obj_f1".

    Строка 1
    Python
    1
    
    def my_decorator(func):
    создает безымянный объект-функцию в оперативной памяти, сохраняя в этом объекте в том числе код тела функции. Одновременно создается переменная my_decorator, в которую помещается ссылка (адрес) на созданный безымянный объект. Будем для себя (не для питона) условно называть этот (безымянный для питона) объект, как "obj_d".
    Обращаться к этому объекту-функции "obj_d" можем через переменную my_decorator, так как они на текущий момент связаны через адрес (ссылку) в оперативной памяти.
    (прим. питон создает объект-функцию, сохраняя ее код в этом объекте, при этом, в отличие от классов, на этом этапе не заходит внутрь функции, а переходит к следующей строке, идущей за телом функции). Переход к:
    Строка 8
    Python
    1
    
    @my_decorator
    первое, что делает питон встречая символ @ декоратора, это проверяет следующую за данной строкой, строку кода, предполагая найти там оператор def определения функции. Если он не находит в следующей строке определение функции, то выдается сообщение об ошибке.
    Если питон находит в следующей строке за символом @ определение функции, то он создает объект-функцию.
    В нашем примере питон видит функцию
    Python
    1
    2
    
    def func_1():  
        print('Работает исходная объект-функция "obj_f1".')
    и создает безымянный объект-функцию в оперативной памяти, сохраняя в этом объекте в том числе код тела функции. Одновременно создается переменная func_1, в которую помещается ссылка (адрес) на созданный безымянный объект. Будем для себя (не для питона) условно называть этот (безымянный для питона) объект, как "obj_f1".
    Обращаться к этому объекту-функции "obj_f1" можем через переменную func_1, так как они на текущий момент связаны через адрес (ссылку) в оперативной памяти.
    После этого, питон автоматически выполняет инструкцию присваивания
    Python
    1
    
    func_1 = my_decorator(func_1)
    начиная с вычисления правой части этой инструкции
    Python
    1
    
    my_decorator(func_1)
    смотрим, что питон при этом делает.
    Через переменную my_decorator вызывается объект-функция "obj_d" и пошагово выполняется код этой функции (декоратора).
    Вызов функции происходит с передачей в качестве аргумента объекта-функции "obj_f1" через переменную func_1, которая именно в этот момент ссылается на объект-функцию "obj_f1".
    Python
    1
    
    my_decorator(func_1)
    Так как размеры текста ограничены, то продолжу в следующем комментарии.
    Запись от Viktorrus размещена 22.11.2021 в 19:42 Viktorrus вне форума
    Обновил(-а) Viktorrus 22.11.2021 в 19:59
  5. Старый комментарий
    Дальше пошагово рассмотрим, что делает питон, переходя к выполнению объекта-функции "obj_d".
    Python
    1
    2
    3
    4
    5
    6
    
    def my_decorator(func):
        def wrapper():
            print('Работает код до вызова объекта_функции "obj_f1".')
            func()
            print('Работает код после вызова объекта_функции "obj_f1".')
        return wrapper
    Сначала питон создает локальную переменную func и помещает в нее переданную через аргумент ссылку на объект-функцию "obj_f1". И переходит к первой строке тела объекта-функции "obj_d" (декоратора).
    Строка 2 создает безымянный объект-функцию в оперативной памяти, сохраняя в этом объекте в том числе код тела функции. Одновременно создается переменная wrapper, в которую помещается ссылка (адрес) на созданный безымянный объект. Будем для себя (не для питона) условно называть этот (безымянный для питона) объект, как "obj_f2".
    Обращаться к этому объекту-функции "obj_f2" можем через локальную переменную wrapper (только внутри объекта-функции "obj_d".
    Для того, что бы мы могли обращаться к объекту_функции "obj_f2" за пределами объекта-функции декоратора, возвращаем ее с помощью return наружу функции my_decorator (объекта-функции "obj_d").
    Здесь тонкий момент, работает прием сохранения информации из переменных объемлющей функции, во встроенной функции, когда мы ее передаем наружу. Этот прием используется в замыканиях (фабричных функциях). Поэтому и здесь новая функция, в локальной переменной func, сохранит ссылку на исходную функцию (объект-функцию "obj_f1") и за пределами декоратора.
    Вспомним, это мы вычисляли правую част инструкции
    Python
    1
    
    func_1 = my_decorator(func_1)
    Теперь переменная func_1 получает уже ссылку не на исходную функцию (объект-функцию "obj_f1"), а на новую функцию (объект функцию "obj_f2"), на которую ссылается локальная переменная wrapper.
    Поэтому теперь переменная func_1 будет вызывать новую вложенную в декоратор функцию.
    Строка 12
    Python
    1
    
    func_1()
    переходит к строке
    Строка 3
    Python
    1
    
    print('Работает код до вызова объекта_функции "obj_f1".')
    Строка 4
    Python
    1
    
    func()
    Вызов исходной функции.
    Строка 10
    Python
    1
    
    print('Работает исходная объект-функция "obj_f1".')
    Возврат в новую функцию из исходной функции.
    Строка 5 print('Работает код после вызова объекта_функции "obj_f1".')
    И возврат из новой (фактически декорирующей исходную) функции.
    Что бы убедиться, что все именно так и работает, запустите код в пошаговом режиме.

    Рекомендация. Читать все это может оказаться утомительно. Поэтому советую сначала посмотреть видиоролик у Welemir1, а уже что будет там не понятно, можно будет посмотреть эти моменты в моем объяснении, так как оно предельно подробное.
    Запись от Viktorrus размещена 22.11.2021 в 19:43 Viktorrus вне форума
    Обновил(-а) Viktorrus 22.11.2021 в 19:54
  6. Старый комментарий
    Цитата:
    Сообщение от Viktorrus Просмотреть комментарий
    Avazart не обольщайтесь. В данном случае Ваше мнение для меня не важно. Я в первой теме написал для чего веду этот блог.
    Ну просто будет ли смысл кому то читать здесь, если тут "не понятно"... В памяти не памяти... это все ведь вода... Ну так получается вы написали декоратор для print... (вызов функции func - это лишь дополнительные эффект). Мне кажется пояснений декорирования в интренте можно найти гораздо более простых и понятных.
    У меня лично первая мысль "все смешалось люди кони".....
    Запись от voral размещена 25.11.2021 в 08:52 voral на форуме
  7. Старый комментарий
    voral Я вижу Вы тоже так и не поняли, для чего я веду этот блог. Мне все равно, заходит в него кто либо или нет. Этот блог я веду для себя, что бы по многу раз не писать на форуме одно и тоже, высказывая свою точку зрения, а просто давать ссылку на темы в этом блоге, или копируя отсюда в комментарии на форуме. Поэтому мне все равно, кто что думает об этом блоге. Я имею право на свою точку зрения. И если кто заходит на мой блог, то принимаю во внимание только полезные для меня конкретные и обоснованные замечания. Например в Вашем комментарии одни общие замечания и не одного конкретного. Что у меня не правильно. Если для Вас что то не понятно, то это совсем не значит, что это не правильно. Поэтому Ваш комментарий для меня тоже не представляет интереса. Покажите, что в моем изложении Вы считаете не верным. Я подозреваю, что у Вас просто не достаточно знаний для глубокого понимания декораторов, поэтому Вам ближе поверхностное их объяснение, которое Вы просто запомнили как формулу. Но при таком подходе Вы вряд ли поймете не стандартные приемы применения декораторов.
    Запись от Viktorrus размещена 25.11.2021 в 14:14 Viktorrus вне форума
  8. Старый комментарий
    Потер все что написал...
    Не заморачивайтесь.... хотел лишь вам помочь....
    Запись от voral размещена 25.11.2021 в 14:26 voral на форуме
    Обновил(-а) voral 25.11.2021 в 14:29
  9. Старый комментарий
    Аватар для Usaga
    Цитата:
    Поэтому мне все равно, кто что думает об этом блоге.
    А метериал отсюда скопированный, или в виде ссылки данный, резко избавится от недостатков, о которых вам говорят? Просто, если это личная записка, то юзайте google docs или "заметки" в вашей ОС. Если думаете, что будете давать ссылки на этот блог, то всё равно услышите те же замечания, что материал подаётся безобразно. Если вам и там будет наплевать на это, то и ссылки вам никому приводить не надо (вам же наплевать). Значит снова возвращаемся к заметкам на своей машине локальной (или в других местах, в том числе и облаках). Да хоть на github в приватном gist.
    Запись от Usaga размещена 26.11.2021 в 12:33 Usaga вне форума
  10. Старый комментарий
    Usaga Мне надоело объяснять непонятливым. Но ничего не поделаешь. Просто придется, так как это все таки мой блог, просто чистить его от мусора, который пишут те, кто считает себя умнее, что бы брать на себя роль судьи, и решать что и как я должен делать. Который раз повторю. Это всего лишь мое мнение, может и ошибочное, но оно мое. И следую я всегда только своему мнению. Изменить его можно только если делать конкретные и обоснованные замечания, а не базаром как на рынке, и не заявлениями: "Я не понимаю, значит это неправильно, так как я считаю себя умнее вас".
    Кстати, многие считают мои комментарии на форуме лучшими, и помечают их таковыми. 946 человек пометили мои комментарии как "Спасибо", и 196 человек пометили мои комментарии как "Лучший ответ". Можете посмотреть статистику моих комментариев. И именно для этих людей я даю свои консультации. И именно их мнение для меня важно, так как я им помогаю. И именно поэтому мнение таких непонятливых как Вы для меня не имеет значение.
    Запись от Viktorrus размещена 26.11.2021 в 15:33 Viktorrus вне форума
    Обновил(-а) Viktorrus 26.11.2021 в 15:35
  11. Старый комментарий
    Цитата:
    Сообщение от Viktorrus Просмотреть комментарий
    Изменить его можно только если делать конкретные и обоснованные замечания, а не базаром как на рынке, и не заявлениями: "Я не понимаю, значит это неправильно, так как я считаю себя умнее вас".
    Елы палы. В том то и дело, что вам ни кто не говорит "это не правильно". Просто из опыта объяснения другим всевозможных вопросов, на мой личный взгляд, информация преподнесена скомкано. И от этого получается, что ЦА этой статьи не понятна. Одни еще больше запутаются, другим это нафиг не надо.

    Вы хотите чтоб здесь тема ушла в обсуждение того как доносить мысли? это будет здесь не в тему. С вами просто поделились впечатлением, по умному, фидбек это всегда хорошо. Что вы хотите услышать из аргументов и доказательств?


    И в место того, чтоб вставать в позу, просто бы попробовали прочитать написанное, поставив себя на позицию чайника.


    Очень возможно сбивает с толку название темы.

    Хотите конкретики. Пожалуйста.
    Ну сами попробуйте критично прочесть абзац про то, что делает декоратор. Под него спокойно попадает моя реплика, про то, что вы в примере привели декоратор для принта. К тому же "может принимать" но не обязана? Т.е. если придираться то любая функция, если внутри себя содержит вызов другой функции является декоратором.И тут же у вас начинается "содержит ссылку", две переменные будут содержать ссылку... (Это, как мне кажется, уже о реализации внутренней всей этой кухни.)

    И вот читает это новичек (а ведь именно он ЦА для подобных статей)... "ЧО?!"

    Далее идет меседж с примерами, ага уже тут прояснется глядя в код... Но опять вы уходите в хранение в памяти... При этом именно здесь уже чуть больше приоткрывется завеса на вопрос "что делает"

    Опять же это все ИМХО.... не ищите в моем меседже попытки спора или докопаться.. просто попытка донести, на мой взгляд, просто организовав более четкую структуру у той же самой информации, все бы стало лучше... не надо пугать с первых строк финтами с ссылками и памятью
    Запись от voral размещена 26.11.2021 в 15:48 voral на форуме
    Обновил(-а) voral 26.11.2021 в 15:49
  12. Старый комментарий
    Я вижу, некоторые не понимают одного из важных отличий питона от например языка С++. В питоне объекты не имеют имен, а только адреса в оперативной памяти (которые кстати могут со временем меняться). А переменные содержат только ссылки на объекты. Причем переменные не имеют жесткой связи с объектами, на которые они ссылаются, и эти ссылки в переменных могут заменяться на другие, и будет меняться эта временная связь между переменными и объектами, на которые они ссылаются. Именно эта особенность используется в декораторах в питоне. В С++ то же есть декораторы, но так как там связь переменных и объектах другая, более жесткая, то там внутренний механизм работы декораторов другой, но в итоге результат (видимый алгоритм) их работы тот же самый.
    Наверняка, то что я сейчас написал, опять кто то не поймет, но для кого то, более понятливого, это будет дополнительное объяснение механизма как реализуется работа декоратора в питоне, на которое нужно обратить особое внимание.
    Потому, что сам алгоритм работы декоратора, как шаблона для любого языка, очень простой. Вызывая функцию, у которой декоратор, в результате получаете функцию, которая вносит изменения в исходную функцию.
    А вот, что бы четко понимать, какие изменения в исходную функцию вы можете вносить, для этого нужно понимать, как работает внутренний механизм при использовании декораторов.
    Большая просьба. Кто этого не понимает, или спрашивайте конкретно, что не понятно, или лучше не пишите пространные оценки, что все не правильно. Я все равно мусор удалю.
    Запись от Viktorrus размещена 26.11.2021 в 16:07 Viktorrus вне форума
    Обновил(-а) Viktorrus 26.11.2021 в 16:10
  13. Старый комментарий
    Цитата:
    Сообщение от Viktorrus Просмотреть комментарий
    Я вижу, некоторые не понимают одного из важных отличий питона от например языка С++.
    Увы вы так и не услышали о чем вам говорю.
    Повторюсь еще раз (но уже последний, ваш блог, ваши правила, по большому счету мне на него пофиг):
    все "вопросы и не понимания" выше попытка посмотреть на текст глазами новичка. Скажите кто все прекрасно понимает о декораторах ваш текст зачем? Не ужели целевая аудитория потенциальная, которой вы будете давать ссылку сюда, это уже все знает и понимает. Тут вообще даже дело не в конкретном ЯП.

    В общем удачи Мне добавить нечего, а пытаться достучаться не интересно.
    Запись от voral размещена 26.11.2021 в 16:11 voral на форуме
  14. Старый комментарий
    voral я из своего опыта убедился, что именно не понимание динамической типизации в питоне, то есть то, что в питоне сами по себе объекты не имеют имен, приводит к тому, что многие часто не понимают как в некоторых случаях работает код питона. Это особенно касается тех, кто к питону пришол из С++, так как там с типизацией все по другому.
    Так на чем Мы остановились? Вы утверждаете что начинающий это не поймет, а я убедился что поймет, но только важно давать ему этот материал не сразу весь, а частями. Так как что бы понять механизм работы декоратора в питоне, нужно сначала хорошо усвоить несколько других достаточно сложных, но важных понятий. Как то динамическую типизацию, пространства имен, замыкания (фабричные функции). Это из главного, и может еще что то что может быть не понятно конкретному человеку.
    По этой причине я не буду давать ссылку на свой блок, а буду из него давать материал частями, и только убедившись, что очередная порция материала усвоена, давать следующую порцию.
    Поэтому в чем я с Вами соглашусь, вываливать новичку весь этот материал сразу (а поэтому давать ссылку на мой блог) не имеет смысла, он все равно сходу не поймет. Материал требует последовательного, тщательного освоения.
    Запись от Viktorrus размещена 26.11.2021 в 16:30 Viktorrus вне форума
  15. Старый комментарий
    voral Вы мне сделали полезное замечание, что название темы не удачное. Поэтому я решил его заменить на название, которое мне кажется больше подходит к содержимому темы.
    Запись от Viktorrus размещена 26.11.2021 в 16:44 Viktorrus вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.