Содержание

Синтаксический разбор простого предложения

Синтаксический разбор простого предложения прочно вошёл в практику начальной и средней школы. Это самый трудный и объёмный вид грамматического разбора. Он включает характеристику и схему предложения, разбор по членам с указанием частей речи.

Строение и значение простого предложения изучается начиная с 5 класса. Полный набор признаков простого предложения обозначается в 8 классе, а в 9 классе основное внимание уделяется сложным предложениям.

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

Начнём с самого простого: поможем ребятам подготовиться к выполнению синтаксического разбора в 5 классе. В начальной школе ученик запоминает последовательность разбора и выполняет его на  элементарном уровне, указывая грамматическую основу, синтаксические связи между словами, вид предложения по составу и цели высказывания, учится составлять схемы и находить однородные члены.

В начальной школе используются разные программы по русскому языку, поэтому уровень требований и подготовка учащихся разные. В пятом классе я принимала детей, обучавшихся в начальной школе по программам образовательной системы «Школа 2100», «Школа России» и «Начальная школа XXI века». Отличия есть и большие. Учителя начальной школы проделывают колоссальную работу, чтобы компенсировать недостатки своих учебников, и сами «прокладывают» преемственные связи между начальной и средней школой.

В 5 классе материал по разбору предложения обобщается, расширяется и выстраивается в более полную форму, в 6-7 классах совершенствуется с учётом вновь изученных морфологических единиц (глагольные формы: причастие и деепричастие; наречие и категория состояния; служебные слова: предлоги, союзы и частицы).

 

 

Покажем на примерах отличия между уровнем требований в формате синтаксического разбора.

 

В 4 классе

В 5 классе

В простом предложении выделяется грамматическая основа, над словами обозначаются знакомые части речи, подчёркиваются однородные члены, выписываются словосочетания или рисуются синтаксические связи между словами. Схема: [О -, О]. Повествовательное, невосклицательное, простое, распространённое, с однородными сказуемыми.

Сущ.(главное слово)+прил.,

Гл.(главное слово)+сущ.

Гл.(главное слово)+мест.

Нареч.+гл.(главное слово)

Синтаксические связи не рисуются, словосочетания не выписываются, схема и основные обозначения такие же, но характеристика иная: повествовательное, невосклицательное, простое, двусоставное, распространённое, осложнено однородными сказуемыми.

Разбор постоянно отрабатывается на уроках  и участвует в грамматических заданиях контрольных диктантов.

В сложном предложении подчёркиваются грамматические основы, нумеруются части, над словами подписываются знакомые части речи, указывается вид по цели высказывания и эмоциональной окраске, по составу и наличию второстепенных членов. Схема разбора: [О и О]1, [ ]2, и  [ ]3. Повествовательное, невосклицательное, сложное, распространённое.

Схема остаётся той же, но характеристика иная: повествовательное, невосклицательное, сложное, состоит из 3 частей, которые связаны бессоюзной и союзной связью, в 1 части есть однородные члены, все части двусоставные и распространённые.

Разбор сложного предложения в 5 классе носит обучающий характер и не является средством контроля.

Схемы предложения с прямой речью: А: «П!» или «П,» — а. Вводится понятие цитаты, совпадающее по оформлению с прямой речью.

Схемы дополняются разрывом прямой речи словами автора: «П, — а. — П.» и «П, — а, — п». Вводится понятие диалога и способы его оформления.

Схемы составляют, но характеристика предложений с прямой речью не производится.

 
 
План разбора простого предложения

1. Определить вид предложения по цели высказывания (повествовательное, вопросительное, побудительное).

2. Выяснить тип предложения по эмоциональной окраске (невосклицательное или восклицательное).

3. Найти грамматическую основу предложения, подчеркнуть её и обозначить способы выражения, указать, что предложение простое.

4. Определить состав главных членов предложения (двусоставное или односоставное).

5. Определить наличие второстепенных членов (распространённое или нераспространённое).

6. Подчеркнуть второстепенные члены предложения, указать способы их выражения (части речи): из состава подлежащего и состава сказуемого.

7. Определить наличие пропущенных членов предложения (полное или неполное).

8. Определить наличие осложнения (осложнено или не осложнено).

9. Записать характеристику предложения.

10. Составить схему предложения.

 

Для анализа мы использовали предложения из прекрасных сказок Сергея Козлова про Ёжика и Медвежонка.

1) Это был необыкновенный осенний день!

2) Обязанность каждого — трудиться.

3) Тридцать комариков выбежали на поляну и заиграли на своих писклявых скрипках.

4) У него нет ни папы, ни мамы, ни Ёжика, ни Медвежонка.

5) И Белка взяла орешков и чашку и поспешила следом.

6) И они сложили в корзину вещи:  грибы, мёд, чайник, чашки — и пошли к реке.

7) И сосновые иголки, и еловые шишки, и даже паутина — все распрямились, заулыбались и затянули изо всех сил последнюю осеннюю песню травы.

8) Ёжик лежал, по самый нос укрытый одеялом, и глядел на Медвежонка тихими глазами.

9) Ёжик сидел на горке под сосной и смотрел на освещённую лунным светом долину, затопленную туманом.

10) За рекой, полыхая осинами, темнел лес.

11) Так до самого вечера они бегали, прыгали, сигали с обрыва и орали во всё горло, оттеняя неподвижность и тишину осеннего леса.

12) И он прыгнул, как настоящий кенгуру.

13) Вода, куда ты бежишь?

14) Может, он с ума сошёл?

15) Мне кажется, он вообразил себя. .. ветром.

 

Образцы разбора простых предложений

 

Скачать образцы разборов в формате .doc 75,5 КБ

«У нас олень в тундре и тот понимает, что «Единая Россия» ничего не решает»

Известный политолог о том, как на выборах в РФ удалось избежать белорусского сценария

«Для проведения успешной избирательной кампании ты должен либо лизать кованый имперский сапог, либо целовать мировую жабу. Однако предвыборная кампания «Новых людей» пошла не то чтобы мимо, но даже перпендикулярно всем этим имеющимся каналам коммуникации», — объясняет успех новой парламентской партии публицист и бывший идеолог «Единой России» Алексей Чадаев. О том, кто такие политики из Faberlic, почему единороссы набрали на 19% больше своего текущего рейтинга, чем объяснить провал «Умного голосования» и что заставляет Дмитрия Медведева чувствовать себя одиноким и чужим на празднике жизни, Чадаев рассказал в интервью «БИЗНЕС Online».

Алексей Чадаев: «Государственная Дума с точки зрения депутатского состава осталась под контролем и за все будет голосовать, как ей скажут. При этом запрос на новые фигуры удовлетворен только очень частично» Фото: Владимир Андреев/URA.RU/ТАСС

«Оппозиционеры стеклись в братские могилы и там благополучно перекусали друг друга»

— Алексей Викторович, год назад вы поделились довольно мрачным прогнозом относительно парламентских выборов 2021 года. Позволю себе процитировать: «Сначала на выборах в Госдуму в 2021-м накидают от души — не бей лежачего. ЕР опять получит свое чаемое конституционное большинство. Потом случится улица — точно мощнее и масштабнее, чем была Болотная 10 лет назад. Росгвардия героически спасет страну от „цветного сценария“ — это будет шоу куда покруче нынешнего минского. Под эту лавочку расформируют к чертям внутриполитический блок АП как очевидно не справившийся с задачей „удержания стабильности“».

Но вот выборы прошли, «Единая Россия» опять на коне, а на улице почему-то тихо. Что позволило избежать катастрофического хода событий?

— Есть два фактора, которые помогли сломать этот сценарий. Да, конституционное большинство у «Единой России» осталось, но одновременно появилась новая парламентская партия, пришли новые игроки и прежний расклад сильно поменялся. Набор аргументов о том, что система власти сделала ставку на консервацию любой ценой, у идеологов протеста оказался выбит. Это одно. А второе — это стратегическая ошибка, которую допустили те же идеологи протеста, сделав ставку на КПРФ. Причем внешне это казалось разумным — у коммунистов все лето росли рейтинги, они довольно много набрали на борьбе с пенсионной реформой и на борьбе с вакцинацией, и это выглядело довольно крепко.

Но в чем проблема? Очевидно, что либеральный электорат в отношении КПРФ вынужден был действовать, превозмогая себя — что называется, «мышки плакали, кололись, но продолжали есть кактус». Психологически тяжело было голосовать за неких «замшелых» людей с иконой Сталина в руках. И даже относительный успех коммунистов (все-таки они отрастили думскую фракцию в 1,5 раза) трудно приписать целиком себе, а самое главное, трудно этот успех продавать на Западе. Для того чтобы доказать, что электоральный успех сталинистов является несомненным достижением прозападного протеста, нужно обладать какой-то очень вывернутой наизнанку логикой. Все-таки простые брюссельские ребята из Европарламента не умеют так гибко мыслить. В этом смысле протестное голосование оказалось демотивированным.

Есть еще один фактор — его можно обозначить как «фактор два с половиной», а именно то, что случилось с одномандатными кампаниями. В тех немногочисленных округах, где имелись большие шансы на победу оппозиционного кандидата (в первую очередь в Москве, немножко в Питере и далее по стране — в том же Томске, где кандидат от ЛДПР сподобился обойти соперника-единоросса), образовалась настоящая толкотня из известных и влиятельных борцов с режимом. Никто из них не хотел уступать другому и договариваться, и все ждали, что УГ, «Умное голосование», укажет именно на них (сайт УГ связан фондом борьбы с коррупцией, признанным иноагентом и экстремистской организацией, запрещенной в РФ, — прим. ред.). При этом боролись даже не за избирателей, а за благосклонность Леонида Волкова (соратник Алексея Навального, координатор УГ — прим. ред.) Однако с публикацией списка УГ затягивали, а когда это все-таки произошло, все пролетевшие мимо стали бурно возмущаться сделанным выбором, плевать в Волкова и друг в друга, и в результате сам инструмент УГ оказался дискредитированным. Если, скажем, в прошлом году тех, кто атаковал «Умное голосование», можно было обвинить в том, что они продались Кремлю, то в нынешнем году УГ атаковала буквально вся оппозиционная тусовка, а защищать его оказалось некому.

Второй и третий факторы (вернее, второй с половиной) — это практически одно и то же, ошибки стратегии. Но вместе они привели к тому, что «катастрофический» сценарий, описанный мною в прошлом году, все-таки не сработал. В то же время, как мне кажется, для страны это хорошо — причем хорошо не в контексте охранительной логики. Конечно, какой-нибудь лоялист непременно скажет про борцов с режимом: «Так им и надо, они опять обделались!» Однако в том, что они «обделались» или сделали неудачные ставки, я лично никакой особой радости не вижу — в отличие от упертых лоялистов. Так почему же это хорошо? На мой взгляд, потому что не произошло дальнейшего ужесточения и консервации режима в формате осажденной крепости. События не стали развиваться по белорусскому сценарию прошлого года, а в самой российской политической системе осталось достаточно воздуха, достаточно пространства для маневра и для разнообразной палитры мнений.

«Есть несколько причин, определяющих, почему рейтинги «Единой России» так различаются от итоговых результатов партии. Во-первых, сказывается влияние специфических территорий, которые я называю султанатами. Второе — это фактор одномандатной кампании» Фото: © Евгений Одиноков, РИА «Новости»

— В одном из прошлых интервью «БИЗНЕС Online» вы констатировали, что с 2018 года рейтинги «Единой России» стояли на отметке в 27–30 процентов.

— Они и сейчас не превысили этой планки.

— Тогда откуда взялось почти 50 процентов голосов, поданных за «ЕдРо» на думских выборах?

— Тут есть несколько причин, определяющих, почему рейтинги «Единой России» так отличаются от итоговых результатов партии. Во-первых, сказывается влияние специфических территорий, которые я называю султанатами (в той же Чечне партия власти набрала 96,62 процента голосов в рамках думских выборов). Второе — это фактор одномандатной кампании, и он всегда так работает. Любой понимает, что одномандатники неизбежно тянут за собой и партийный список. Согласитесь, каким надо быть шизофреником, чтобы по одномандатным округам голосовать за кандидата от одной партии, а по спискам поддерживать другую? Это тоже упражнение не для слабых умов. А в рамках одномандатной кампании сильных оппозиционеров было немного, и они все, как я уже сказал, толкались локтями на очень небольшом количестве округов. Это уже ошибка оппозиции: вместо того чтобы договориться между собой и распределить одномандатные округа по стране, они все сконцентрировались на узком пятачке, где, по их мнению, был шанс пролезть в Думу. Конечно, если бы борцы с режимом рассредоточились по стране, мало кто из них выиграл бы выборы в одномандатных округах. Но голосование по партийным спискам оказалось бы другим! Транслируя свою повестку и привлекая к себе внимание, оппозиционеры создали бы эффект другой списочной кампании. Но поскольку в нашей оппозиции мыслить стратегически никто, по всей видимости, не умеет, этого не произошло. Каждый свои проблемы решал, все изголодались и устали быть никем вне парламента — какими-то блогерами и несистемными деятелями. Хотелось ощутить себя чем-то полноценным. В результате они все стеклись в эти братские могилы и там благополучно перекусали друг друга даже без особого участия власти.

Что же получилось в итоге? В большинстве одномандатных округов по стране присутствовал сильный единоросс и несколько ноу-неймов, которые формально представляли оппозиционные партии, но толком кампанию не вели. На кого избиратель в этом случае сделает ставку? Для того чтобы эффективно представлять интересы территорий, нужно быть способным открывать в столице нужные двери, а это лучше всего получается у депутатов самой сильной и влиятельной политической партии, за которой, так или иначе, стоит большинство. Таким образом, одномандатное голосование от одного избирательного цикла к другому все больше скатывается к тому, чтобы стать вообще однопартийным. Прибавьте к этому шедшие параллельно кампании регионального и местного уровня — губернаторские, в заксобрания и прочие, где работает та же механика. В результате «ЕдРо» даже без «коррекций» набирает значительно выше собственных рейтингов.

«Из всех идеологем, опробованных в ходе кампании «Новых людей», наибольшие электоральные плоды принес регионализм» (на фото: Александр Хуруджи, Сардана Авксентьева (слева направо), Сангаджи Тарбаев, Елена Хохлова (справа налево) и председатель партии «Новые люди» Алексей Нечаев (в центре) Фото: © Виталий Белоусов, РИА «Новости»

«Таскать каштаны из огня для товарищей из администрации президента никто не нанимался»

— Не могу не спросить о КПРФ, которая на этот раз сделала очевидный электоральный рывок и набрала почти 19 процентов голосов. Я согласен с определением «замшелая» в отношении данной партии, но почему бы ей на волне успеха не сбросить с себя этот имидж? Скажем, осуществить персональный транзит власти Геннадия Зюганова гораздо раньше, чем это сделает Владимир Путин, и поменять Геннадия Андреевича, возглавляющего коммунистов аж с 1993 года, на более энергичного и нонконформистского лидера вроде Павла Грудинина, Николая Платошкина или Максима Шевченко? Вот шороху тогда будет!

— Не будет этого. Скорее всего, появится Юрий Афонин — такой же Зюганов, только молодой. И станет сидеть на этом посту следующие 50 лет с той же «иконой» и так же договариваться с администрацией президента. Пока что такой сценарий мне представляется основным. Понимаете, есть политические силы, которые заточены на то, чтобы бороться за власть, а есть силы, которые заточены на то, чтобы снимать ренту со своего статуса вечной оппозиции. Коммунисты — это второе. Они о том, чтобы вся, так сказать, народная боль, возмущение и прочие формы социального протеста стекались к ним, а они бы потом обменивали все это с начальниками за «зубцами» на какие-то преференции, на свое относительно тихое сидение и всякие разные мелкие уступки. Вроде поста главы одного из комитетов Госдумы или же одной-двух губерний «красного пояса».

— Тогда о главной политической новинке российского парламента — «Новых людях». За них нередко голосовали по принципу: «Кто тут у нас есть в бюллетенях? Жирик, Зюганов, Миронов — это все старье. А вот наконец-то „Новые люди“! Поставлю-ка я за них галочку!» Но при этом так и осталось загадкой: а что скрывается за этим симпатичным брендом, кроме вкусно пахнущей компании Faberlic? И не станет сетевой маркетинг просто еще одной политической моделью?

— Что касается Faberlic, то это как раз тот случай, когда опыт сетевого маркетинга стал хорошим подспорьем для проведения успешной избирательной кампании по ту сторону традиционных каналов коммуникации. Почему? Потому что все медиа в России контролируются либо Кремлем и региональными властями, либо условной заграницей (соцсети, видеохостинги и прочие платформы). Соответственно, чтобы остаться в информационном поле, ты должен либо лизать кованый имперский сапог, либо целовать мировую жабу. Первым занимаются системные силы, чтобы их пустили в эфир, а вторым — несистемные, чтобы им отсыпали трафик. Заметим, что предвыборная кампания «Новых людей» пошла не то чтобы мимо, но даже перпендикулярно всем этим имеющимся каналам. Посредством той же модели, которая действует в сетевом маркетинге, голоса собирались в основном на земле, вживую. Это дало в нужной степени автономность всех имеющихся рычагов, тех или других, а также возможность быть достаточно самостоятельными в сфере политического позиционирования. Партия при этом избегала резких конфликтов и не брала на вооружение лозунги радикальной оппозиции — по понятным причинам. У системы много способов снимать кандидатов или «рубить» их по собранным подписям. И без того «Новые люди» оказались чемпионами по количеству кандидатов, не допущенных к выборам, — особенно к региональным. Однако на федеральном уровне был достигнут определенный паритет.

Разумеется, осторожность партийной стратегии, о которой я говорю, создавала «Новым людям» репутацию спойлера. Хотя в реальности региональные политические администраторы считывали партию как угрозу и боролись с ней как с угрозой. Чем чаще раздавался вопль из либерального лагеря, что это «проект Кремля», тем громче раздавался стон региональных политадминистраторов: «Да они же оттягивают голоса у „Единой России“!» Тем не менее, пройдя по «бритве Оккама» (один из принципов методологии — прим. ред.) в буквальном смысле босиком, «Новым людям» удалось набрать 3 миллиона голосов (5,32 процента), чтобы зайти в Думу.

— И это, как я понимаю, сломало «катастрофический» белорусский сценарий, с которого мы начали разговор?

— Это точно не было целью! Таскать каштаны из огня для товарищей из администрации президента никто не нанимался, да еще и за свой счет. По-моему, изо всех преодолевших 5-процентный барьер партий «Новые люди» были единственными, кто использовал свои ресурсы, а не благотворительные бюджеты. Но косвенный эффект, безусловно, именно такой, как вы говорите.

— Еще хочу уточнить по «Новым людям»: у них есть какая-то идеологическая скрепа, какие-то идейные идентификаторы? Или от этого они отказались вполне осознанно?

— Из всех идеологем, опробованных в ходе кампании «Новых людей», наибольшие электоральные плоды принес регионализм. Тема судьбы регионов и тема перераспределения ресурсов (вроде «Хватит забирать наши налоги в Москву!»), а также тема расширения прав самоуправлений оказались самыми востребованными. Неслучайно флагманом, вернее, флагвумен и лицом кампании стала Сардана Авксентьева. Главным в ее образе было как раз то, что она бывший мэр Якутска, среднего по российским меркам города, где живут чуть более 345 тысяч человек. Кстати, если смотреть по социологическим показателям, то наибольшей поддержкой «Новые люди» пользовались как раз в средних городах, а не в столицах, как это принято у либеральных партий. Тезис о том, чтобы развивать не только Москву, но и Россию, в итоге сработал даже сильнее, чем то, что было изначально написано на знаменах у «Новых людей»: «Надоели старые — давайте новых!» На этот тезис «ЕдРо», кстати, ответило: «Смотрите, у нас у самих до 50 процентов партийного состава обновилось!» Так что новизна перестала быть уникальным конкурентным преимуществом. А вот запрос на децентрализацию денег оказался тем, на что у партии власти оказалось ответить нечем по понятным причинам. Таким образом, избиратели «ЕдРа», которые живут не в столице, стали постепенно переориентироваться, отчего региональные чиновники и подняли стон. Нажатие условной кнопки о том, что Россия — это не только Москва, привело «Новых людей» к успеху. Особенно на отдаленных территориях. Я напомню, что больше всего партия собрала на Дальнем Востоке, в Сибири, кое-где на югах и так далее.

«Шизофрения, которая возникла в головах у избирателей «Справедливой России», не прибавила эсерам электоральных успехов» (на фото: лидер партии «Патриоты России» Геннадий Семигин, руководитель фракции «Справедливая Россия» Сергей Миронов и председатель партии «За правду» Захар Прилепин (слева направо) Фото: «БИЗНЕС Online»

«Когда отчитывались, как мы строили Крымский мост, из разных регионов шли робкие реакции: «А может, Вологодскую область тоже к России присоединить?»

— Отсутствие Алексея Навального на свободе и в публичном поле тоже ведь сыграло свою роль в том, что выборы прошли мирно, а «майданный» сценарий удалось предотвратить?

— Здесь я бы поспорил. Во-первых, нельзя сказать, чтобы он совсем отсутствовал — в медиасфере его «письма из застенков» публикуются и тиражируются с завидной регулярностью, иногда до двух раз в неделю. Так что в новостной повестке оппозиционер есть. Но здесь работает другой фактор. Когда нет выборов, нет никакой другой политики, кроме уличной. И тогда Навальный — высотой с три горы. А вот когда начинаются выборы и в публичное пространство выходит огромное количество игроков с разными флагами, внимание все-таки переключается. Скажем, коррупция — большая проблема в нашем отечестве, кто бы спорил! Но кроме коррупции есть десятки других проблем, и некоторые из них не менее внушительны. К примеру, призыв говорить только о коррупции в эпоху, когда к тебе с разных сторон подступают с вакцинами и призывают уколоться, слышится слабо. Просто сместилась повестка. А раз это произошло, значит, сместилась и расстановка сил.

— Почему же «Справедливая Россия», которая пыталась играть на левой повестке и которая для этого объединилась с партией «За правду» Захара Прилепина и прочими «красными» патриотами, смогла вырасти только на 1 процент по сравнению с выборами 2016 года?

— Тут все совсем просто. Этакий турбопатриотизм от Прилепина — за Донбасс. А классический эсер, который превалировал до Прилепина, ратовал за то, чтобы раздать деньги бедным. При этом любая домохозяйка понимает, что деньги в бюджете есть либо на то, чтобы раздать бедным, либо на Донбасс. Когда по телевизору транслировались бравурные отчеты, как мы строили Крымский мост и сколько денег вложили в то, чтобы подтянуть Крым хотя бы до общероссийского уровня, из разных регионов РФ шли робкие реакции: «А может, Вологодскую или Владимирскую область тоже к России присоединить?» И в основном, по моей оценке, этими вопросами задавался потенциальный эсеровский электорат. Как будто Россия собирается c завидной регулярностью присоединять к себе все новые земли, а деньги, которые могли бы получить убогий старик и вдовица, отправить на очередную демонстрацию имперской мощи. Вполне понятно, что шизофрения, которая возникла в головах у избирателей «Справедливой России», не прибавила эсерам электоральных успехов.

— И даже тот факт, что ДНР и ЛНР в этом году впервые массово приняли участие в российских выборах, не сыграл никакой роли?

— Отток избирателей внутри России оказался значительнее, чем бонусы донбасского голосования.

— Массовая раздача денег накануне выборов — пенсионерам, военным, учителям, семьям с детьми и прочим — не была воспринята избирателем как подкуп? По этому поводу ворчали в соцсетях…

— Ворчание в соцсетях можно смело игнорировать. Бо́льшая часть граждан восприняла раздачу как само собой разумеющееся — дескать, молодцы, наконец-то сообразили денег дать. Но привело ли это к поддержке партии власти — еще вопрос. Деньги-то раздавали президент с правительством, а голосовать почему-то надо было за «ЕдРо». Как ни объясняли людям в телевизоре, что это одно и то же и что «партия так решила», у нас олень в тундре и тот понимает, что «Единая Россия» ничего не решает. Потому что решают совсем в другом месте, а партии лишь объясняют, что именно она «решила». Поэтому затруднительно сказать, что раздача денег дала какой-то рост рейтинга. Точно нет. Это скорее снизило уровень социального недовольства и напряжения. В таком смысле это была оборонная стратегия. «ЕдРо» в электоральном плане потеряло меньше, чем могло бы. Это компенсировало и удержало достигнутое, но не помогло нарастить новые голоса.

«Представьте, если бы Медведев (справа) вышел под софиты и начал кричать на пару с Турчаком, как «мы всех жахнули»! Его бы просто в очередной раз подняли на смех. И встал бы вопрос, кто кого вообще жахнул» Фото: © Григорий Сысоев, РИА «Новости»

«Медведев сидит в своем доме на Воздвиженке всеми забытый, ему грустно»

— Скажите, самоизоляция Владимира Путина и «сильный кашель» Дмитрия Медведева в момент подведения первых итогов выборов не символизирует ли их скорое отсутствие в ближайшей политической перспективе? Или это ряд случайностей?

— Да нет, что вы. Это дистанцирование в жанре того, что «партии партиями, а страна у нас одна». Андрей Турчак опять ведь не удержался и вылез со своим коронным заявлением: «Мы всех жахнули!» А Владимир Путин, сколько бы его ни приклеивали к «ЕдРу», склонен, как я вижу, считать себя президентом всей страны, а не одной отдельно взятой правящей партии и ее сторонников. На выборах Путин им помог как мог, а по итогам просто отошел в сторону. Это разумно.

А с Дмитрием Медведевым другая история, совсем другая. Человек хотел партийный список возглавлять, но его не взяли. Человек хотел Думу возглавить, но ему не дали. И вот он сидит в своем доме на Воздвиженке (Дом приемов правительства — прим. ред.) всеми забытый, ему грустно. Он чужой на празднике жизни. Теперь представьте, если бы он вышел под софиты и начал бы кричать на пару с Турчаком, как «мы всех жахнули»! Его бы просто в очередной раз подняли на смех. И встал бы вопрос, кто кого вообще жахнул.

— Удалось ли этим выборам решить проблему путинского транзита власти – 2024, как это предполагалось вначале?

— А вот это я пока плохо понимаю. Да, Государственная Дума с точки зрения депутатского состава осталась под контролем и за все будет голосовать, как ей скажут. В то же время запрос на новые фигуры удовлетворен только очень частично. Новых лиц в политике меньше, чем стране хотелось бы. Стратеги ломают голову: «Что же, опять повторилась гонка на лафетах? Опять Зюганов, опять Жириновский и опять Миронов?» Думаю, что в ближайшие три года нас ждет интересный кастинг — и внутри Думы, и вне ее. Что до «Новых людей», то их перспективы роста связаны с одним — удастся ли им из, по сути, авторского и нишевого проекта стать платформой для объединения разных сил — в том числе и тех, кто сейчас не входит в парламент. Сейчас «Новые люди» представляют собой энергию 3 миллионов голосов, которые были за них поданы. А если еще намагнитить сюда энергию всех тех, кто не прошел или не смог участвовать, тогда это будет совсем другая мощь сигнала и другой политический вес. Это то, за что «Новым людям» предстоит бороться в ближайшие годы.  

следователь по делу Чикатило — о колбасе для серийного убийцы и стихах из тюрьмы — РТ на русском

Города Таганрог, Шахты и Ростов-на-Дону в 1980—1990-е годы называли «треугольником смерти»: именно в этом районе орудовали самые страшные маньяки СССР, в том числе Андрей Чикатило и Владимир Муханкин. В раскрытии их дел принимал участие следователь Амурхан Яндиев. В интервью проекту RT «Незабытые истории» он рассказал, какие принципы использовал в работе, как ему удавалось добиться признательных показаний от преступников, почему маньяки посвящали ему стихи и зачем он кормил Чикатило колбасой.

В 1980—1990-е годы вся Ростовская область цепенела от ужаса. Каждый год в болотах и лесах находили изувеченные тела взрослых и детей. В регионе орудовали серийные убийцы — Андрей Чикатило и его последователи: Владимир Муханкин, Константин Черёмухин, Роман Бурцев. В общей сложности на их совести не менее 73 жертв, из них 55 человек (согласно показаниям самого преступника) лишил жизни Чикатило.

Выследить, поймать и добиться признательных показаний от них удалось при помощи следователя Амурхана Яндиева и его коллег. Сейчас полковнику юстиции в отставке 76 лет, он работает адвокатом, консультирует следователей, читает лекции в вузах и в учебном центре Ростовского СК. Он также написал несколько работ об опыте общения с преступниками, в том числе известную книгу «Серийный убийца: портрет в интерьере». Следователь рассказал RT о профессиональных приёмах на допросах, деталях громких дел и о том, что понял о маньяках.

«На след вывели женщины»

— Как вы ловили Андрея Чикатило?

— Впервые Чикатило оказался за решёткой в 1984 году. Он клюнул на одну из переодетых сотрудниц милиции, но почувствовал неладное и оставил девушку.

Сотрудники милиции его остановили для проверки, нашли при нём вазелин, нож, мыло, проволоку, верёвки. Тогда Чикатило объяснил, что вазелин ему нужен для бритья или смазывать какие-то ранки, нож требовался в командировках, верёвка для работы, а проволоку просто так на улице подобрал.

Конечно, за такой подозрительный набор его задержать не могли. Но оказалось, что ранее он украл линолеум и аккумулятор с предприятия, на котором работал. За это его и арестовали на три месяца. Рассчитывали, что за это время найдутся доказательства его причастности к убийствам, свидетели, но делом занимались тогда не так тщательно, поэтому он снова оказался на свободе.

К 1985 году уже официально значилось 36 убийств, которые, предположительно, совершил Чикатило. Генеральная прокуратура решила изъять из производства прокуратуры Ростовской области это дело, и его поручили старшему следователю по особо важным делам Иссе Костоеву. Он пригласил меня быть руководителем одной из двух следственных групп: одна в Шахтах, вторая в Ростове, поскольку убийца орудовал в двух городах.

Мы пригласили специалистов: психологов, психиатров и сексологов, которые пришли к выводу, что все преступления убийца совершал на сексуальной почве. Тела, как правило, находили в лесу, причём характер повреждений был примерно одинаковый: преступник хаотично наносил удары внутрь, не вынимая нож из тела, а также вырезал половые органы у своих жертв. Он профессионально заметал следы и совершал преступления, оставаясь незамеченным.

  • © АРХИВ МИЛИЦИИ СССР

Сначала мы пробовали определить его группу крови. Экспертиза ошибочно показала, что у убийцы четвёртая группа крови, хотя на самом деле была вторая. Это на время сбило с толку следствие: мы, как выяснилось, сначала отрабатывали лиц с совершенно не той группой крови.

Далее мы проверяли душевнобольных, ранее судимых за преступления сексуального характера. Параллельно мы отправляли Чикатило «манки», то есть наших переодетых сотрудниц. За ним была организована колоссальная слежка. Наши сотрудники наблюдали за ним абсолютно везде: на вокзалах, железнодорожных станциях и в местах скопления людей. Нам пришлось перекрыть весь город.

В 1990 году в Ботаническом саду Ростова был обнаружен труп мальчика. Он был ещё тёплым, когда мы его нашли. Той же ночью мне приснился сон: некая женщина говорит, что убийца — её муж и, мол, она станет его следующей жертвой, раз мы перекрыли город. Потом она повела меня в поле, где за стогом сена крест-накрест лежали трупы. Преступника во сне мы задержали.

Сон оказался во многом вещим. На след Чикатило нас вывели именно женщины: кассирша на вокзале сказала, что её дочь с подругой видели, как какой-то мужчина пытался снять с электрички мальчика, но тот убежал. Приметы совпали с внешностью Чикатило.

Когда мы напали на его след, я испытал непередаваемое удовольствие. Дело было поздней осенью, мы искали его по стылым электричкам, а я даже не обращал внимания, что жутко замёрз, — настолько кайфовал от происходящего. 20 ноября 1990 года мы задержали Андрея Чикатило.

— На вашем счету поимка не только Чикатило, но и других серийных убийц.Есть какая-то особая методика?

— Не думаю, что это именно методика. Просто я наблюдательный, у меня хорошая зрительная память, а мышлению не мешают бурные эмоции. Ещё помогает поставить себя на место преступника.

Помню, как-то мне дали дело, на первый взгляд, совершенно бесперспективное. Без вести пропал парень, по нашей инициативе было возбуждено дело об убийстве. С момента преступления прошёл почти год, причастные к смерти парня не помнили, где закопали тело: надо было искать где-то в очень густой и длинной лесополосе.

Тогда я просто прошёл по лесу и ткнул пальцем в место, где сам бы закопал труп, будь я убийцей. И что же — выкопали яму в человеческий рост, сразу же увидели, что земля смешана с глиной, что указывало на то, что тут уже раньше копали. Затем появился характерный запах солярки — ей заливают места, где хотят что-то спрятать, чтобы собака не взяла след. Потом обнаружили и сам труп с металлическим тросом, на котором его повесили.

«Топни ногой — они разбегутся»

— На вас как-то повлияло общение с серийными убийцами?

— Для многих покажется странным, но, общаясь с такими людьми, я получаю удовольствие. Почему я написал работы и книги и о Чикатило, и о серийном убийце Муханкине, который тоже орудовал в Ростовской области и убил восемь человек? Мне интересна их жизнь, я её изучаю, и если на неё посмотреть детально, то таких людей даже в чём-то становится жалко.

— Можете привести пример?

— Когда Муханкин был ещё в утробе матери, его уже не любили родители. Отец бросил мать, а в деревне быть молодой, без мужа и беременной — стыдно. И она пыталась от него избавиться всеми способами. Когда ребёнок родился, его мать пыталась подбросить его отцу, потом постоянно меняла мужей, что тоже оказывало влияние на его психику.

Мальчика никто не воспитывал, было только издевательское отношение со стороны самого близкого человека, который должен был окружить любовью и лаской своего ребёнка. Муханкина просто истязали. Он рассказывал, что от безысходности ночевал на кладбище в самостоятельно вырытой норе.

Конечно, мы потом всё это проверили. По своей инициативе я решил провести свидание с матерью, посмотреть, какая у них будет реакция друг на друга. Они вели себя как посторонние люди. Муханкин ей говорит: «Ну что, мам, прощай, больше вы меня никогда не увидите», — а она стоит и молчит. Вот такое расставание.

И с каждым маньяком такая история. Чикатило тоже пережил многое. Сам рассказывал, что его жена Феня скалкой гоняла по дому. А потом он выходил на улицу и катался на электричках в поисках жертвы. Вот про них говорят, что они убийцы, жестокие звери, которые просто не могут быть трусами. Да никакие они не звери. Топни ногой — они разбегутся.

— Как вы приходили в себя после таких расследований?

— Очень сложно. Часто снились тревожные сны. Мне помогала прежде всего семья. А ещё мы с друзьями любили ходить играть в бильярд — так и отвлекался.

«Чикатило заплакал»

— Какие эмоции вы испытывали, общаясь с убийцами? Злость, ужас, отвращение?

— Ничего этого преступнику я не показывал. У меня другой метод, задача которого — обличить в полном объёме убийцу. Возможно, кому-то он покажется неожиданным. Это не обман, не хитрость — исключительно тактические приёмы следователя. Я пытаюсь изобличить зло, а не делать какое-то доброе дело убийце, бандиту или негодяю. Любой способ, если он не нарушает закон, приемлем.

Вот привезли мне Муханкина. Он ждёт, что следователь даст ему по башке и начнёт упрекать во всём. Если так себя вести, то преступник замкнётся в себе, ведь над ними всю жизнь издевались, и ничего не расскажет.

Я тогда зашёл со словами: «О, привет, Володька!» — как будто бы мы с ним давно знакомы, пожал его руку, спросил всё ли у него хорошо, не обижает ли кто. У нас сложились доверительные отношения, и он рассказал мне о своих убийствах, ничего не скрывая.

— Какие ещё принципы использовали в работе?

— Убийце нужно всегда говорить правду, даже в мелочах. Обходиться без насилия и жестокости. Например, с Чикатило мы обращались друг к другу уважительно, называли по отчеству.

Однажды я приехал к нему в Москву, в Бутырку, после его обследования в институте Сербского. Он такой худой был, и я ему сказал: «Ой, Романыч, что с тобой случилось?» А Чикатило заплакал, рассказал, как там плохо кормили и как его обследовали. Ну я знал, что он страстно любил колбасу, привёз ему её, после чего он немного ожил и продолжил давать показания, исповедоваться.

— Муханкин просил вас присутствовать при его смертной казни, которую в итоге заменили на пожизненное заключение. Какие у вас с ним сложились отношения?

— Можно сказать, что замечательные. Я ему приносил бумагу, помог с переводом в камеру с хорошими условиями. Муханкин даже написал мне стихотворение, услышав, как дочка на день рождения передала мне поздравление по радио, с такими строками: «Да, мы на разных полюсах, я это знаю, но здравия ему желаю».

Наладить близкий контакт было нужно, чтобы преступник лучше раскрылся. Муханкину нравилось показывать, как он совершал преступления. Рассказывал и демонстрировал, словно герой фильма. Например, в Цимлянском районе, где он убил девочку, лазил в болото, нашёл её останки и обувь.

— Сейчас Муханкин отбывает срок в колонии «Чёрный дельфин». Он продолжает вам писать?

— Писал несколько раз, критиковал некоторых наших сотрудников, просил прислать ещё бумаги для жалоб. Но какие у меня могут быть после приговора отношения с человеком, у которого восемь жертв? О чём я с ним буду переписываться?

«Убийц защищать не пойду»

— Как вы думаете, можно ли было сделать так, чтобы люди не совершали таких преступлений?

— Наверное, если бы они изначально оказались в благоприятных условиях, то, возможно, не совершили бы все эти убийства. Я давно говорю, что, когда началась перестройка, то состояние, в котором находился народ, привело ко всеобщей озлобленности общества. Надо на государственном уровне бороться с нищетой и голодом, делать так, чтобы народ ни в чём не нуждался. Тогда будет гораздо меньше маньяков и убийц. Органам нужно тщательно следить за неблагополучными семьями.

Но если человек уже встаёт на преступный путь, то отловить маньяка крайне сложно. Это такие серые личности, они растворяются в массе. Например, серийный убийца Анатолий Сливко из Невинномысска жестоко убил семерых детей. А ведь был педагогом, никто бы на него не подумал.

— Какую тактику при нападении на жертву чаще всего используют насильники?

— Судя по рассказу самих маньяков, они делают так: специально находят места, где можно спрятаться, чтобы напасть на жертву и утащить её, например, в кусты. Осматривают, нет ли поблизости людей, потом выслеживают жертву и совершают внезапное нападение. Поэтому моя главная рекомендация — быть осторожным, избегать безлюдных мест.

— У вас была насыщенная профессиональная жизнь. Вы скучаете по работе следователя?

— Я как-то был в нашем Следственном комитете, оперативники могут подтвердить, что я до сих пор полон энергии. Чувствую, что готов хоть сейчас ввязаться в бой. И если бы меня пригласили на работу в Следственный комитет, то я бы согласился.

— Чем вы сейчас занимаетесь?

— Сейчас я работаю адвокатом. На процессы, как правило, не хожу, потому что не люблю это дело. Просто консультирую коллег-следователей, защищаю потерпевших, которых необоснованно обидели. Но если я убеждён, что это убийца, то за такие дела не брался и не буду никогда в жизни.

Гостевая книга • театр «Модерн»

Хотите бурю эмоций ? 
От полного отторжения до аплодисментов? Снова неприятия и долгих последующих размышлений ? Хотите спора с самим собой ? Сомнений , открытий и внутреннего конфликта ?

Тогда советую вам посмотреть спектакль «На дне» В Театре «Модерн http://www. modern-theatre.ru/»

Мне эта постановка «взорвала мозг » . 
Но ведь если театр оставляет нас равнодушным — то зачем он существует ?

Итак . Школьная классика . Горький «На дне» . Текст бережно сохранен , только сокращён ради придания динамики действию . Кстати, сразу отмечу , что бережное и уважительное отношение к тексту оригинала — отличительная черта Грымова — режиссера .

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

Что делает Грымов ? Социальную проблему ( нищета , отсутствие денег ) — то, что нам так старательно внушали в школе , режиссёр стирает напрочь .

Герои «Дна » в Модерне одеты в дорогие брендовые вещи , в руках у них айфоны , они меняют костюмы , Часы , ходят в спа-салон , на йогу и концерты классической музыки .

Где же дно ?

Оно на сцене ! Только теперь оно резче , пронзительнее, сложнее и больнее . Теперь дно не снаружи ( это достаточно легко изменить ) , теперь ДНО ВНУТРИ героев . И вот в этой ситуации , когда финансово-материальная сторона убрана решительной рукой дерзкого режиссера , становится отчетливее видно , что настоящее ДНО — это духовное .

Это дно почти непреодолимо , оно засасывает и поглощает , оно даёт тебе иллюзии , но взамен уничтожит твою душу .

На сцене — дорогая мебель и актеры в модной брендовой одежде . И они произносят те же самые фразы , что написал Горький . И становится страшно: Насколько же современно и актуально они звучат .

Прошло 150 лет со дня рождения писателя , а эти темы по-прежнему актуальны .

Кто ты?
К чему стремишься? 
Готов ли что-то менять ? 
Жива ли твоя душа ? Или же давно заложена в ломбард за выгодное замужество, кусок хлеба, дорогие часы?

Герои на сцене пьют дорогой коньяк . Но кто из них счастлив ?

Не сразу приняла и осознала образ Луки . Помним его у Горького? В театре же Лука — молодой модный, ухоженный молодой человек . Он заботится о внешнем больше , нежели о внутреннем . Его духовность — очень демонстративная и вся направлена наружу , а не вовнутрь .

Он , кажется , успокаивает умирающую Анну , но на самом деле видно его глубокое безразличие и ожидание того, когда это закончится . Он разговаривает со спившимся актером , но верит ли он сам тому, что говорит . 
Этот образ несомненно творческая находка режиссера и актёрская удача Александра Толмачева .

А ещё это невероятно правдивая примета нашего времени . Слишком часто, увы , мы для развития своей культуры посещаем необходимые выставки- концерты , а для развития духовности распеваем мантры или же сидим в позе лотоса . При этом забываем , что эти решения в формате ЛАЙТ делают и душу лайт , похожей на красивую и хорошо продаваемую этикетку кока-колы .

Спектакль идёт на малой сцене и актеров и зрителей разделяет настолько малое расстояние , что протяни руку- достанешь . И это отличный шанс насладиться прекрасной актерской игрой .

Насколько хорош в роли Сатина Владимир Левашев . Какое вхождение в образ , полная отдача и буря эмоций . И когда он произносит сакраментальное : Какую песню испортил, дурак…. это не о песне . А о том , как можно убить в себе человека , так и не дав ему спеть его прекрасную песню радости и любви к жизни .

Хочу отметить игру Виктории Лукиной ( В пьесе — Настя) . Как удивительно удалось актрисе показать то , как упорен человек обманывая самого себя , создавая иллюзию полной ощущениями и чувствами жизни . Ее уход в мир книжных персонажей не так безобиден как кажется , это сродни «уходу»Барона, закончившему счёты с жизнью и блистательно сыгранным на сцене Алексеем Багдасаровым .

Хорош и Васька Пепел . Кто он , герой Виктора Потапешкина ? Вор ли , промышляющий в дореволюционной Москве или бандит из недавних 90-Х ? Или житель сегодняшнего дня ? Увы, границы нет .

В том и боль от этого спектакля , что понимаешь — решение материальных проблем ещё не делает тебя априори счастливым , душа требует такого же труда и роста — иначе ДНО поглотит тебя и сделает своей частью .

От меня — однозначная рекомендация к просмотру . И не спешите выносить суждение . Ко мне осознание глубины постановки пришло лишь после принятия симбиоза авторского текста и эпатажной подачи .

Уверена , этот спектакль и этот театр не оставят вас равнодушными .

Не боитесь думать, сомневаться, удивляться, возмущаться и восхищаться ?- тогда вам сюда . 
В театр Модерн на спектакль Юрия Грымова » На дне»! http://www.modern-theatre.ru/

И ! Для всех театралов ! Не забывайте о том , что в Москве теперь живет новый дерзкий , очень сильный и интересный театр Модерн . 
Театр , в котором все , начиная от гардероба , воодушевлены желанием работать , где происходят открытия … 
Театр , о котором можно спорить , но уже невозможно не учитывать в театральной жизни столицы !

Sauvage Dior одеколон — аромат для мужчин 2015

Первый отзыв на Sauvage 2015 я написал, как время летит, 4 года назад — 19 октября 2015 года.

Пришло время написать более осознанный взгляд на этот далеко не простой, как кажется на первый взгляд, парфюм.
Timmy Trumpet & Dimatik — Punjabi

Итак Sauvage 2015 — противоречивый, наглый, энергичный, синтетический, насколько отвратительный живущим в прошлом, настолько привлекательный живущим в будущем.
Никаких интриг, прелюдий, жеманства — Саваж 2015 это джеб в челюсть на оттяжке, удар свергающий любую тушу в бездну нокаута.
Кибернизированный фужер в который добавили бергамот, странный, прохладно-острый сычуанський перец и огромную дозу амброксана.
Старики негодуют.
Молодежь ликует.
Кто виноват? Что делать?
Виноват конечно же дом Creed, так как именно он впервые создал удивительно летучий аромат Aventus, состоящий из огромной дозы амброксана и фруктов, тем самым совершив некую революцию в мире мужской парфюмерии.
Сорвав все топы продаж, Авентус подтолкнул многие дома к созданию парфюмерии на очень летучих молекулах.
Так и родился Саваж.

Раньше я думал, что он меня раздражает, ловя шлейф на улице. Пока не понял, что это не был не Саваж. Это были неуклюжие копии на Саваж. Хромые, убогие, они дымят вонючей второсортной химозиной и сейчас на каждом углу …
А что же Саваж 2015?
Я купил флакон. Однажды поймав в жару на солнцепеке от поджарого мужчины лет 50 морозный шлейф, я за секунду пришел к выводу: мне нравится, очень, я куплю себе флакон.

Стойкость: Саваж стойкий, еще бы не быть. Часов 9-12 с кожи и с одежды до стирки.
Шлейф: Тут всё неоднозначно, как и всех молекулярных парфюмов — носитель не слышит или редко слышит, а вокруг волны. Или допустим на улице не слышно, а зашел под лучи кондиционера и помещение заполнилось Саваж.

Парфюм универсальный, подойдет любому: и стар и млад, носи коль рад. Одно но — он не терпит быдла. Быдло пахнет подделками Саваж, а оригинал на быдле звучит еще хуже.
Наверное всё таки потому-что это француз, пусть и киборг.

Вывод: Стойкость 10 из 10, Шлейф 7 из 10, Композиция 10 из 10.
Не шедевр, но создан гениально!

Понимание байт-кода Python.

Узнайте о дизассемблировании Python… | Реза Багери

Узнайте о дизассемблировании байт-кода Python

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

В интерпретируемом языке исходный код не запускается напрямую на целевой машине. Есть еще одна программа, называемая интерпретатором, которая напрямую считывает и выполняет исходный код. Интерпретатор, специфичный для целевой машины, переводит каждый оператор исходного кода в машинный код и запускает его.

Python обычно называют интерпретируемым языком, однако он сочетает в себе компиляцию и интерпретацию. Когда мы выполняем исходный код (файл с расширением . py ), Python сначала компилирует его в байт-код.Байт-код — это низкоуровневое платформенно-независимое представление исходного кода, однако он не является двоичным машинным кодом и не может быть запущен целевой машиной напрямую. Фактически, это набор инструкций для виртуальной машины, которая называется виртуальной машиной Python (PVM).

После компиляции байт-код отправляется на выполнение в PVM. PVM — это интерпретатор, который запускает байт-код и является частью системы Python. Байт-код не зависит от платформы, но PVM зависит от целевой машины.Реализацией по умолчанию языка программирования Python является CPython, который написан на языке программирования C. CPython компилирует исходный код Python в байт-код, и этот байт-код затем выполняется виртуальной машиной CPython.

Создание файлов байт-кода

В Python байт-код хранится в файле .pyc . В Python 3 файлы байт-кода хранятся в папке с именем __pycache__ . Эта папка создается автоматически, когда вы пытаетесь импортировать другой созданный вами файл:

 import file_name 

Однако она не будет создана, если мы не импортируем другой файл в исходный код. В этом случае мы все еще можем создать его вручную. Чтобы скомпилировать отдельные файлы с file_1.py до file_n.py из командной строки, мы можем написать:

 python -m compileall file_1.py ... file_n.py 

Все сгенерированные файлы pyc будут храниться в папке __pycache__ . Если вы не укажете имена файлов после compileall, , он скомпилирует все файлы исходного кода python в текущей папке.

Мы также можем использовать функцию compile () для компиляции строки, содержащей исходный код Python.Синтаксис этой функции:

compile ( source , filename , mode , flag , dont_inherit , optimize )

Мы сосредотачиваемся только на первых трех аргументах, которые необходимы (остальные необязательны). source — это исходный код для компиляции, который может быть строкой, объектом Bytes или объектом AST. имя_файла — это имя файла, из которого взят исходный код. Если исходный код не из файла, вы можете написать все, что захотите, или оставить пустую строку. mode может быть:

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

'eval' : принимает одно выражение и компилирует его в байт-код, который, наконец, возвращает значение этого выражения

'single' : принимает только одно оператор (или несколько операторов, разделенных ; ). Если последний оператор является выражением, то результирующий байт-код печатает repr () значения этого выражения в стандартный вывод.

Например, чтобы скомпилировать некоторые операторы Python, мы можем написать:

 s = '' '
a = 5
a + = 1
print (a)
' ''
compile (s, "", "exec")

или эквивалентно напишите:

 compile ("a = 5 \ na + = 1 \ nprint (a)", "", "exec") 

Чтобы оценить выражение, мы можем написать:

 compile ("a + 7 "," "," eval ") 

Этот режим выдает ошибку, если у вас нет выражения:

 # Это не работает: 
compile (" a = a + 1 "," "," eval " )

Здесь a = a + 1 не является выражением и ничего не возвращает, поэтому мы не можем использовать режим eval . Однако мы можем использовать режим single для его компиляции:

 compile ("a = a + 1", "", "single") 

Но что возвращает compile ? Когда вы запускаете функцию компиляции , Python возвращает:

 <объект кода <модуль> в 0x000001A1DED95540, файл "", строка 1> 

Итак, функция компиляции возвращает объект кода (адрес после на может быть другой на твоей машине).

Объект кода

Функция compile () возвращает объект кода Python.Все в Python - это объект. Например, вы определяете целочисленную переменную, ее значение хранится в объекте int , и вы можете легко проверить его тип с помощью функции type () :

 a = 5 
type (a) # Результат: int

Аналогичным образом байт-код, сгенерированный функцией компиляции, сохраняется в объекте code .

 c = compile ("a = a + 1", "", "single") 
type (c) # Вывод: code

Объект кода содержит не только байт-код, но и некоторую другую информацию, необходимую для CPython для запуска байт-кода (о них мы поговорим позже). Кодовый объект может быть выполнен или оценен путем передачи его в функцию exec () или eval () . Итак, мы можем написать:

 exec (compile ("print (5)", "", "single")) # Вывод: 5 

Когда вы определяете функцию в Python, она создает для нее объект кода, и вы может получить к нему доступ с помощью атрибута __code__ . Например, мы можем написать:

 def f (n): 
return n
f .__ code__

И результат будет:

  ", строка 1> 

Как и любые другие объекты, объект кода имеет некоторые атрибуты, и для получения байт-кода, хранящегося в объекте кода, вы можете использовать его атрибут co_code :

 c = compile (" print (5) " , "", "одиночный") 
c.co_code

Результат:

 b'e \ x00d \ x00 \ x83 \ x01F \ x00d \ x01S \ x00 '

Результатом является -байтовый литерал с префиксом b'. Это неизменяемая последовательность байтов, имеющая тип байтов . Каждый байт может иметь десятичное значение от 0 до 255. Таким образом, байтовый литерал представляет собой неизменяемую последовательность целых чисел от 0 до 255. Каждый байт может быть показан с помощью символа ASCII, код символа которого совпадает со значением байта, или он может быть отображается в начале \ x , за которым следуют два символа.Преобразование \ x в начале означает, что следующие два символа интерпретируются как шестнадцатеричные цифры кода символа. Например:

 print (c.co_code [0]) 
chr (c.co_code [0])

дает:

 101 
'e'

, поскольку первый элемент имеет десятичное значение 101 и может быть отображается с символом e , код символа ASCII которого равен 101. Или:

 print (c.co_code [4]) 
chr (c.co_code [4])

дает:

 131 
'\ x83'

, поскольку 4-й элемент имеет десятичное значение 131. Шестнадцатеричное значение 131 - 83. Таким образом, этот байт может отображаться с символом, код которого равен \ x83 .

Эти последовательности байтов могут интерпретироваться CPython, но они не удобны для человека. Итак, нам нужно понять, как эти байты отображаются на фактические инструкции, которые будут выполняться CPython. В следующем разделе мы собираемся дизассемблировать байт-код в некоторую понятную для человека инструкцию, чтобы увидеть, как байт-код выполняется CPython.

Детали байт-кода

Прежде чем переходить к дальнейшим деталям, важно отметить, что детали реализации байт-кода обычно меняются между версиями Python.Поэтому то, что вы видите в этой статье, может не подходить для всех версий Python. Фактически, он включает в себя изменения, произошедшие в версии 3.6, и некоторые детали могут быть недействительными для более старых версий. Код в этой статье был протестирован с Python 3.7.

Байт-код можно рассматривать как серию инструкций или низкоуровневую программу для интерпретатора Python. После версии 3.6 Python использует 2 байта для каждой инструкции. Один байт предназначен для кода этой инструкции, которая называется кодом операции , а один байт зарезервирован для ее аргумента , который называется oparg . Каждый код операции имеет понятное для человека имя, которое называется имя операции . Инструкции байт-кода имеют следующий общий формат:

 код операции oparg 
код операции oparg
.
.
.

У нас уже есть коды операций в нашем байт-коде, и нам просто нужно сопоставить их с соответствующими именами операций. Есть модуль под названием dis , который может помочь в этом. В этом модуле есть список под названием opname , в котором хранятся все opnames. i -й элемент этого списка дает имя операции для инструкции, код операции которой равен i .

Некоторым инструкциям не нужен аргумент, поэтому они игнорируют байт после кода операции. Коды операций, значение которых меньше определенного числа, игнорируют свой аргумент. Это значение хранится в dis.HAVE_ARGUMENT и в настоящее время равно 90. Таким образом, коды операций> = dis.HAVE_ARGUMENT имеют аргумент, а коды операций < dis.HAVE_ARGUMENT игнорируют его.

Например, предположим, что у нас есть короткий байт-код b'd \ x00Z \ x00d \ x01S \ x00 ', и мы хотим его разобрать. Этот байт-код представляет собой последовательность из четырех байтов. Мы можем легко показать их десятичное значение:

 байт-код = b'd \ x00Z \ x00d \ x01S \ x00 '
для байта в байт-коде:
print (byte, end =' ')

Результат будет:

 100 0 90 0100 1 83 0 

Первые два байта байт-кода - это 100 0 . Первый байт - это код операции. Чтобы получить его opname, мы можем написать (сначала нужно импортировать dis ):

 dis.opname [100] 

и результат LOAD_CONST . Поскольку код операции больше dis.HAVE_ARGUMENT , у него есть oparg, который является вторым байтом 0 . Таким образом, 100 0 преобразуется в:

 LOAD_CONST 0 

Последние два байта в байт-коде равны 83 0 . Снова мы пишем dis.opname [83] и получаем RETURN_VALUE . 83 меньше 90 ( dis.HAVE_ARGUMENT ), поэтому этот код операции игнорирует oparg, а 83 0 разбирается в:

 RETURN_VALUE 

Кроме того, некоторые инструкции могут иметь слишком большой аргумент, чтобы поместиться в по умолчанию один байт.Для обработки этих инструкций существует специальный код операции 144 . Его имя операции - EXTENDED_ARG , и оно также хранится в dis.EXTENDED_ARG . Этот код операции является префиксом любого кода операции, аргумент которого превышает один байт. Например, предположим, что у нас есть код операции 131 (его имя операции - CALL_FUNCTION ), а его oparg должен быть 260. Итак, это должно быть:

 CALL_FUNCTION 260 

Однако максимальное число, которое может хранить байт, равно 255, а 260 не помещается в байт. Таким образом, этот код операции имеет префикс EXTENDED_ARG :

 EXTENDED_ARG 1 
CALL_FUNCTION 4

Когда интерпретатор выполняет EXTENDED_ARG , его oparg (равный 1) сдвигается влево на восемь бит и сохраняется во временной переменной. Назовем его extended_arg (не путайте его с именем opname EXTENDED_ARG ):

 extened_arg = 1 << 8 # то же, что и 1 * 256 

Таким образом, двоичное значение 0b1 (двоичное значение 1) равно преобразован в 0b100000000 .Это похоже на умножение 1 на 256 в десятичной системе, и extened_arg будет равно 256. Теперь у нас есть два байта в extened_arg . Когда интерпретатор переходит к следующей инструкции, это двухбайтовое значение добавляется к его oparg (здесь 4) с использованием побитового или .

 extened_arg = extened_arg | 4 
# То же, что и extened_arg + = 4

Это похоже на добавление значения oparg к extened_arg . Итак, теперь у нас есть:

 extened_arg = 256 + 4 = 260 

, и это значение будет использоваться как фактический oparg CALL_FUNCTION .Таким образом, на самом деле

 EXTENDED_ARG 1 
CALL_FUNCTION 4

интерпретируется как:

 EXTENDED_ARG 1 
CALL_FUNCTION 260

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

Теперь мы можем сосредоточиться на самом oparg. Что это означает? Фактически значение каждого oparg зависит от его кода операции. Как упоминалось ранее, объект кода хранит некоторую информацию, отличную от байт-кода.Доступ к этой информации можно получить с помощью различных атрибутов объекта кода, и нам нужны некоторые из этих атрибутов, чтобы расшифровать значение каждого oparg. Это следующие атрибуты: co_consts , co_names , co_varnames , co_cellvars и co_freevars .

Атрибуты объекта кода

Я объясню значение этих атрибутов на примере. Предположим, у вас есть объект кода этого исходного кода:

 # Листинг 1 
s = '' '
a = 5
b =' text '
def f (x):
return x
f (5)
' ''
c = compile (s, "", "exec")

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

1- co_consts : кортеж, содержащий литералы, используемые байт-кодом.Здесь c.co_consts возвращает:

 (5, 'text', , 'f', None) 

Итак, литералы 5 и 'text ' и имя функции ' f ' все хранятся в этом кортеже. Кроме того, тело функции f хранится в отдельном кодовом объекте и обрабатывается как литерал, который также хранится в этом кортеже. Помните, что режим exec в compile () генерирует байт-код, который в конечном итоге возвращает None . Это значение None также сохраняется как литерал. Фактически, если вы скомпилируете выражение в режиме eval следующим образом:

 s = "3 * a" 
c1 = compile (s, "", "eval")
c1.co_consts # Результат будет (3,)

Нет больше не будет включаться в кортеж co_consts . Причина в том, что это выражение возвращает свое окончательное значение, а не None .

Если вы попытаетесь получить co_const для объектного кода функции, например:

 def f (x): 
a = x * 2
верните
f.__code __. co_consts

Результатом будет (None, 2) . Фактически, возвращаемое значение по умолчанию для функции - None , и оно всегда добавляется как литерал. Как я объясню позже, для эффективности Python не проверяет, всегда ли вы собираетесь достичь оператора return или нет, поэтому None всегда добавляется как возвращаемое значение по умолчанию.

2- co_names : кортеж, содержащий имена, используемые байт-кодом, которые могут быть глобальными переменными, функциями и классами, а также атрибутами, загруженными из объектов.Например, для объектного кода в листинге 1 c.co_names дает:

 ('a', 'b', 'f') 

3- co_varnames : кортеж, содержащий локальные имена, используемые байт-кодом (сначала аргументы, затем локальные переменные). Если мы попробуем использовать его для объектного кода из листинга 1, он даст пустой кортеж. Причина в том, что локальные имена определены внутри функций, а функция в листинге 1 хранится как отдельный объект кода, поэтому ее локальные переменные не будут включены в этот кортеж.Чтобы получить доступ к локальным переменным функции, мы должны использовать этот атрибут для объекта кода этой функции. Итак, сначала напишем этот исходный код:

 def f (x): 
z = 3
t = 5
def g (y):
return t * x + y
return g
a = 5
b = 1
h = f (a)

Теперь f . __ code__ дает объект кода f , а f .__ code __. co_varnames дает:

 ('x', 'z', 'g') 

Почему т не включены? Причина в том, что t не является локальной переменной f .Это нелокальная переменная, поскольку к ней обращается закрытие g внутри f . Фактически, x также является нелокальной переменной, но поскольку это аргумент функции, он всегда включается в этот кортеж. Чтобы узнать больше о замыканиях и нелокальных переменных, вы можете обратиться к этой статье.

4- co_cellvars : кортеж, содержащий имена нелокальных переменных. Это локальные переменные функции, к которой имеют доступ ее внутренние функции. Так ф.__code __. co_cellvars дает:

 ('t', 'x') 

5- co_freevars : Кортеж, содержащий имена свободных переменных. Свободные переменные - это локальные переменные внешней функции, к которым обращается ее внутренняя функция. Таким образом, этот атрибут следует использовать с кодовым объектом закрытия h . Теперь h .__ code __. Co_freevars дает тот же результат:

 ('t', 'x') 

Теперь, когда мы знакомы с этими атрибутами, мы можем вернуться к opargs.Значение каждого oparg зависит от его кода операции. У нас есть разные категории кодов операций, и для каждой категории oparg имеет разное значение. В модуле dis есть несколько списков, в которых указаны коды операций для каждой категории:

1– dis.hasconst : этот список равен [100]. Таким образом, только код операции 100 (его имя операции - LOAD_CONST) находится в категории hasconst . Oparg этого кода операции дает индекс элемента в кортеже co_consts .Например, в байт-коде листинга 1, если у нас есть:

 LOAD_CONST 1 

, тогда oparg является элементом co_consts с индексом 1. Таким образом, мы должны заменить 1 на co_consts [1] , который равен равно 'текст' . Таким образом, инструкция будет интерпретироваться как:

 LOAD_CONST 'text' 

Точно так же есть некоторые другие списки в модуле dis , которые определяют другие категории для кодов операций:

2- dis.hasname : oparg для кодов операций в этом списке - это индекс элемента в co_names

3- dis.haslocal : oparg для кодов операций в этом списке - это индекс элемента в co_varnames

4- dis.hasfree : oparg для кодов операций в этом списке - это индекс элемента в co_cellvars + co_freevars

5- dis.hascompare : oparg для кода операции в этом списке , - индекс элемента кортежа dis.cmp_op . Этот кортеж содержит операторы сравнения и членства, такие как < или ==

6- dis.hasjrel : oparg для кодов операций в этом списке следует заменить на offset + 2 + oparg , где offset. - это индекс байта в последовательности байт-кода, который представляет код операции.

У объекта кода есть еще один важный атрибут, который следует здесь обсудить. Он называется co_lnotab , в котором хранится информация о номере строки байт-кода.Это массив байтов со знаком, который хранится в байтовом литерале и используется для сопоставления смещений байт-кода с номерами строк исходного кода. Поясню на примере. Предположим, что ваш исходный код состоит только из трех строк и был скомпилирован в байт-код из 24 байтов:

 1 0 LOAD_CONST 0 
2 STORE_NAME 0

2 4 LOAD_NAME 0
6 LOAD_CONST 1
8 INPLACE_ADD
10 STORE_NAME 0

3 12 LOAD_NAME 1
14 LOAD_NAME 0
16 CALL_FUNCTION 1
18 POP_TOP
20 LOAD_CONST 2
22 RETURN_VALUE

Теперь у нас есть отображение смещения байт-кода на номера строк, как в этой таблице:

Смещение байт-кода всегда начинается с 0.Объект кода имеет атрибут с именем co_firstlineno , который дает номер строки для нулевого смещения. В этом примере co_firstlineno равно 1. Вместо того, чтобы сохранять смещение и номера строк буквально, Python сохраняет только приращения от одной строки к другой (исключая первую строку). Таким образом, предыдущая таблица превращается в:

Эти два столбца приращения заархивированы вместе в такой последовательности:

 4 1 8 1 

Каждое число хранится в байтах, а вся последовательность сохраняется как байтовый литерал в . co_lnotab кодового объекта.Итак, если вы проверите значение co_lnotab , вы получите:

 b '\ x04 \ x01 \ x08 \ x01' 

, который является байтовым литералом для предыдущей последовательности. Таким образом, имея атрибуты co_lnotab и co_firstlineno , вы можете получить сопоставление смещений байт-кода с номерами строк исходного кода. co_lnotab - это последовательность байтов со знаком. Таким образом, каждый байт со знаком в нем может принимать значение от -128 до 127 (эти значения по-прежнему хранятся в байте, который принимает значения от 0 до 255. Но значение от 128 до 255 считается отрицательным числом). Отрицательное приращение означает, что номер строки уменьшается (эта функция используется в оптимизаторах). Но что произойдет, если шаг строки больше 127? В этом случае приращение строки будет разделено на 127 и несколько дополнительных байтов, и эти дополнительные байты будут сохранены с нулевым приращением смещения (если оно меньше -128, оно будет разделено на -128 и некоторые дополнительные байты с приращение нулевого смещения). Например, предположим, что смещение байт-кода относительно номера строки выглядит следующим образом:

Тогда приращение смещения относительно приращения номера строки должно быть:

139 равно 127 + 12.Таким образом, предыдущая строка должна быть записана как

и должна храниться как 8 127 0 12 . Таким образом, значение co_lnotab будет: b '\ x08 \ x7f \ x00 \ x0c' .

Дизассемблирование байт-кода

Теперь, когда мы знакомы со структурой байт-кода, мы можем написать простую программу дизассемблера. Сначала мы пишем функцию генератора, чтобы распаковать каждую инструкцию и получить смещение, код операции и oparg:

Эта функция считывает следующую пару байтов из байт-кода.Первый байт - это код операции. Сравнивая этот код операции с dis.HAVE_ARGUMENT , функция решает, следует ли принимать второй байт в качестве аргумента или игнорировать его. Значение extended_arg будет добавлено к oparg с использованием побитового или ( | ). Первоначально он равен нулю и не влияет на oparg. Если код операции равен dis.EXTENDED_ARG , его oparg будет сдвинут влево на восемь бит и сохранен во временной переменной с именем extended_arg .

На следующей итерации эта временная переменная будет добавлена ​​к следующему аргументу oparg и добавит к нему один байт. Этот процесс продолжается, если следующий код операции снова будет dis.EXTENDED_ARG , и каждый раз добавляет один байт к extended_arg . Наконец, когда он достигает другого кода операции, extended_arg будет добавлено к его oparg и обнулено.

Функция find_linestarts возвращает словарь, который содержит номер строки исходного кода для каждого смещения байт-кода.

Сначала он разделил литерал co_lnotab байтов на две последовательности. Один - это приращение смещения, а другое - приращение номера строки. Номер строки для смещения 0 находится в co_firstlineno . Приращения добавляются к этим двум числам, чтобы получить смещение байт-кода и соответствующий ему номер строки. Если приращение номера строки больше или равно 128 (0x80), оно будет считаться уменьшением.

Функция get_argvalue возвращает понятное для человека значение каждого oparg.Сначала он проверяет, к какой категории принадлежит код операции, а затем выясняет, на что указывает oparg.

Функция findlabels находит все смещения в байт-коде, которые являются целями перехода, и возвращает список этих смещений. Цели прыжка будут обсуждаться в следующем разделе.

Теперь мы можем использовать все эти функции для дизассемблирования байт-кода. Функция disassemble берет объект кода и дизассемблирует его:

Сначала она распаковывает смещение, код операции и oparg для каждой пары байтов в байт-коде объекта кода.Затем он находит соответствующие номера строк исходного кода и проверяет, является ли смещение целью перехода. Наконец, он находит opname и значение oparg и распечатывает всю информацию. Как упоминалось ранее, каждое определение функции хранится в отдельном кодовом объекте. Итак, в конце функция рекурсивно вызывает себя, чтобы разобрать все определения функций в байт-коде. Вот пример использования этой функции. Изначально у нас есть этот исходный код:

 a = 0 
, в то время как a <10:
print (a)
a + = 1

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

 s = '' 'a = 0 
, а a <10:
print (a)
a + = 1
' ''
c = compile (s, "", "exec")
дизассемблировать (c)

Результат:

 1 0 LOAD_CONST 0 (0) 
2 STORE_NAME 0 (a)

2 4 SETUP_LOOP 28 (to 34)
>> 6 LOAD_NAME 0 ( a)
8 LOAD_CONST 1 (10)
10 COMPARE_OP 0 (<)
12 POP_JUMP_IF_FALSE 32

3 14 LOAD_NAME 1 (печать)
16 LOAD_NAME 0 (a)
18 CALL_FUNCTION 1
20 POP_TOP

4 22 LOAD a)
24 LOAD_CONST 2 (1)
26 INPLACE_ADD
28 STORE_NAME 0 (a)
30 JUMP_ABSOLUTE 6
>> 32 POP_BLOCK
>> 34 LOAD_CONST 3 (Нет)
36 RETURN_VALUE

Итак, 4 строки исходного кода преобразуются в 38 байтов байт-кода или 19 строк байт-кода. В следующем разделе я объясню значение этих инструкций и то, как они будут интерпретироваться CPython.

Модуль dis имеет функцию с именем dis () , которая может дизассемблировать объект кода аналогичным образом. Фактически, функция disassmble в этой статье является упрощенной версией функции dis.dis . Поэтому вместо того, чтобы писать disassemble (c) , мы могли бы написать dis.dis (c) , чтобы получить аналогичный результат.

Дизассемблирование файла pyc

Как упоминалось ранее, когда исходный код компилируется, байт-код сохраняется в файле pyc .Этот байт-код можно разобрать аналогичным образом. Однако важно отметить, что файл pyc содержит некоторые метаданные плюс объект кода в формате marshal . Формат маршала используется для внутренней сериализации объектов Python. Размер метаданных зависит от версии Python и для версии 3. 7 составляет 16 байт. Поэтому, когда вы читаете файл pyc , сначала вы должны прочитать метаданные, а затем загрузить объект кода с помощью модуля marshal .Например, чтобы дизассемблировать файл pyc с именем u1.cpython-37.pyc в папке __pycache__ , мы можем написать:

Итак, мы узнали, как дизассемблировать инструкции байт-кода. Теперь мы можем сосредоточиться на значении этих инструкций и на том, как они выполняются CPython. CPython, который является реализацией Python по умолчанию, использует виртуальную машину на основе стека . Итак, сначала мы должны познакомиться со стеком.

Стек и куча

Стек - это структура данных с порядком LIFO (последний вошел - первым ушел).Он имеет две основные операции:

  • push: добавляет элемент в стек
  • pop: удаляет последний добавленный элемент

Таким образом, последний элемент, добавленный или помещенный в стек, является первым элементом, который будет удален или вытолкнут. Преимущество использования стека для хранения данных заключается в том, что память управляется за вас. Чтение и запись в стек выполняются очень быстро, однако размер стека ограничен.

Данные в Python представлены как объекты, хранящиеся в частной куче.Доступ к данным в куче немного медленнее по сравнению со стеком, однако размер кучи ограничен только размером виртуальной памяти. Элементы кучи не зависят друг от друга и могут быть доступны случайным образом в любое время. В Python все является объектом, а объекты всегда хранятся в куче. Это всего лишь ссылка (или указатель) на объект, который хранится в стеке.

CPython использует стек вызовов для запуска программы Python. Когда функция вызывается в Python, новый фрейм помещается в стек вызовов, и каждый раз, когда вызов функции возвращается, его фрейм удаляется.Модуль, в котором выполняется программа, имеет самый нижний фрейм, который называется глобальным фреймом или фреймом модуля.

Каждый фрейм имеет стек оценки , в котором выполняется функция Python. Аргументы функции и ее локальные переменные помещаются в этот стек оценки. CPython использует стек оценки для хранения параметров, необходимых для любых операций, а также результатов этих операций. Перед запуском этой операции все необходимые параметры помещаются в оценочный стек.Затем запускается операция, и появляются ее параметры. По завершении операции результат возвращается в стек оценки.

Все объекты хранятся в куче, а оценочный стек во фреймах имеет дело со ссылками на них. Таким образом, ссылки на эти объекты могут быть временно помещены в стек оценки для использования в последующих операциях. Большинство инструкций байт-кода Python управляют стеком оценки в текущем кадре. В этой статье всякий раз, когда мы говорим о стеке, это означает стек оценки в текущем кадре или стек оценки в глобальном кадре, если мы не находимся в области видимости каких-либо функций.

Позвольте мне начать с простого примера и разобрать байт-код следующего исходного кода:

 a = 1 
b = 2
c = a + b

Для этого мы можем написать:

 s = '' 'a = 1 
b = 2
c = a + b
' ''
c = compile (s, "", "exec")
дизассемблируем (c)

и получаем:

 1 0 LOAD_CONST 0 ( 1) 
2 STORE_NAME 0 (a)

2 4 LOAD_CONST 1 (2)
6 STORE_NAME 1 (b)

3 8 LOAD_NAME 0 (a)
10 LOAD_NAME 1 (b)
12 BINARY_ADD
14 STORE_NAME 2 (c)
16 LOAD_CONST 2 (None)
18 RETURN_VALUE

Кроме того, мы можем проверить некоторые другие атрибуты объекта кода:

 c. co_consts 
# вывод: (1, 2, None)
c.co_names
# вывод: ('a', 'b', 'c')

Здесь код выполняется в модуле, так что мы внутри глобальный фрейм. Первая инструкция - LOAD_CONST 0 . Команда

  LOAD_CONST   consti  

помещает в стек значение co_consts [consti] . Итак, мы помещаем в стек co_consts [0] (что равно 1 ).

Важно отметить, что стек работает со ссылками на объекты.Поэтому всякий раз, когда мы говорим, что инструкция помещает объект или значение объекта в стек, это означает, что помещается ссылка (или указатель) на этот объект. То же самое происходит, когда объект или его значение извлекаются из стека. Снова появляется его ссылка. Интерпретатор знает, как получить или сохранить данные объекта, используя эти ссылки.

Инструкция

  STORE_NAME   namei  

выталкивает верхнюю часть стека и сохраняет ее в объект, ссылка на который хранится в co_names [namei] объекта кода. Таким образом, STORE_NAME 0 помещает элемент на вершину стека (который равен 1 ) и сохраняет его в объекте. Ссылка на этот объект - co_names [0] , то есть a . Эти две инструкции являются эквивалентом байт-кода a = 1 в исходном коде. b = 2 преобразуется аналогично, и теперь интерпретатор создал объекты a и b . Последняя строка исходного кода - c = a + b . Инструкция

  BINARY_ADD  

извлекает два верхних элемента стека ( 1 и 2 ), складывает их вместе и помещает результат ( 3 ) в стек.Итак, теперь 3 находится на вершине стека. После этого STORE_NAME 2 выталкивает вершину стека в локальный объект (указанный) c . Теперь помните, что compile в режиме exec компилирует исходный код в байт-код, который в итоге возвращает None . Инструкция LOAD_CONST 2 помещает co_consts [2] = None в стек, а инструкция

  RETURN_VALUE  

возвращается с вершиной стека вызывающей функции.Конечно, здесь мы находимся в области видимости модуля и функции вызывающего объекта нет, поэтому None - это конечный результат, который остается на вершине глобального стека. На рисунке 1 показаны все операции с байт-кодом со смещениями от 0 до 14 (снова следует отметить, что в стек помещаются ссылки на объекты, а не на объекты или их значения. На рисунке это не показано явно).

Функции, глобальные и локальные переменные

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

 #Listing 2 
s = '' 'a = 1
b = 2
def f (x):
global b
b = 3
y = x + 1
return y
f (4)
print (a)
'' '
c = compile (s, "", "exec")
disassemble (c)

Результат:

 1 0 LOAD_CONST 0 (1) 
2 STORE_NAME 0 (a)

2 4 LOAD_CONST 1 (2)
6 STORE_GLOBAL 1 (b)

3 8 LOAD_CONST 2 (<объект кода f в 0x00000218C2E758A0, файл "", строка 3>)
10 LOAD_CONST 3 ('f')
12 MAKE_FUNCTION 0
14 STORE_NAME 2 (f)

8 16 LOAD_NAME 2 (f)
18 LOAD_CONST 4 (4)
20 CALL_FUNCTION 1
22 POP_TOP

_NAME

9 24 LOAD 3 (печать )
26 LOAD_NAME 0 (a)
28 CALL_FUNCTION 1
30 POP_TOP
32 LOAD_CONST 5 (Нет)
34 RETURN_VALUE

Дизассемблирование <объекта кода f в 0x00000218C2E758A0, файл "", строка 3>:

5 0 (LOAD_C) STORE_GLOBAL 0 (b)

6 4 LOAD_FAST 0 (x)
6 LOAD_CONST 2 (1)
8 BINARY_ADD
10 STORE_FAST 1 (y)

7 12 LOAD_FAST 1 (y)
14 RETURN_VALUE

дополнительно

9 можно проверить некоторые другие атрибуты объекта кода:

 c. co_consts 
# вывод: (1, 2, <объект кода f в 0x00000218C2E758A0, файл "", строка 3>, 'f', 4, None) c.co_names
# Вывод: ('a', 'b' , 'f', 'print')

В первой строке (смещения 0 и 2) константа 1 сначала помещается в стек оценки глобального кадра с использованием LOAD_CONST 0 . Затем STORE_NAME 0 открывает его и сохраняет в объекте.

Во второй строке константа 2 помещается в стек с использованием LOAD_CONST 1 .Однако для присвоения этой ссылки используется другое имя операции. Инструкция

  STORE_GLOBAL   namei  

выталкивает верхнюю часть стека и сохраняет ее в объекте, ссылка на который хранится в co_names [namei] . Таким образом, 2 хранится в объекте, на который ссылается b . Это считается глобальной переменной. Но почему эта инструкция не использовалась для и ? Причина в том, что a - это глобальная переменная внутри функции f . Если переменная определена в области модуля и никакие функции не обращаются к ней, она будет сохранена и загружена с использованием STORE_NAME и LOAD_NAME . В области видимости модуля нет различия между глобальными и локальными переменными.

В третьей строке определена функция f . Тело функции компилируется в отдельный объект кода с именем <объект кода f в 0x00000218C2E758A0, файл "", строка 3> и помещается в стек. Затем строковый объект, который является именем этой функции 'f' , помещается в стек (фактически, ссылки на них помещаются).Инструкция

  MAKE_FUNCTION   argc  

используется для создания функции. Ему нужны некоторые параметры, которые следует поместить в стек. Имя функции должно быть вверху стека, а объект кода функции - под ним. В этом примере его oparg равен нулю, но он может иметь другие значения. Например, если определение функции имело аргумент ключевого слова, например:

 def f (x = 5): 
global b
b = 3
y = x + 1
return y

Тогда дизассемблированный байт-код для строки 2 будет :

 2 4 LOAD_CONST 5 ((5,)) 
6 LOAD_CONST 1 (<объект кода f в 0x00000218C2E75AE0, файл "", строка 2>)
8 LOAD_CONST 2 ('f')
10 MAKE_FUNCTION 1

oparg из 1 для MAKE_FUNCTION указывает, что функция имеет некоторые аргументы ключевого слова, и кортеж, содержащий значения по умолчанию, должен быть помещен в стек перед объектом кода функции (здесь это (5,) ). После создания функции MAKE_FUNCTION помещает новый объект функции в стек. Затем по смещению 14 STORE_NAME 2 выталкивает объект функции и сохраняет его как объект функции, на который ссылается f .

Теперь давайте заглянем внутрь объекта кода f (x) , который начинается со строки 5. Оператор global a не преобразуется в отдельную инструкцию в байт-коде. Это только указывает компилятору, что и следует рассматривать как глобальную переменную.Таким образом, STORE_GLOBAL 0 будет использоваться для изменения его значения. Инструкция

  LOAD_GLOBAL   namei  

помещает ссылку на объект, на который указывает co_names [namei] , в стек. Затем он сохраняется в b с использованием STORE_GLOBAL 0 . Инструкция

  LOAD_FAST   var_num  

помещает в стек ссылку на объект, ссылка на который составляет co_varnames [var_num] . В объекте кода функции f атрибут co_varnames содержит:

 ('x', 'y') 

Итак, LOAD_FAST 0 помещает в стек x . Затем 1 помещается в стек. BINARY_ADD извлекает x и 1 , складывает их вместе и помещает результат в стек. Инструкция

  STORE_FAST   var_num  

выталкивает верхнюю часть стека и сохраняет ее в объекте, ссылка на который хранится в co_varnames [var_num] .Итак, STORE_FAST 1 выводит результат и сохраняет его в объекте, ссылка на который составляет y . LOAD_FAST и STORE_FAST используются с локальными переменными функций. Таким образом, они не используются в области модуля. С другой стороны, LOAD_GLOBAL и STORE_GLOBAL используются для глобальных переменных, доступ к которым осуществляется внутри функций. Наконец, LOAD_FAST 1 поместит значение y на вершину стека, а RETURN_VALUE вернет его вызывающей стороне функции, которая является модулем.

а как вызывается эта функция? Если вы посмотрите на байт-код строки 8, сначала LOAD_NAME 2 помещает в стек объект функции, ссылка на который f . LOAD_CONST 4 помещает свой аргумент ( 4 ) в стек. Инструкция

  CALL_FUNCTION   argc  

вызывает вызываемый объект с позиционными аргументами. Его oparg, argc указывает количество позиционных аргументов.Вверху стека находятся позиционные аргументы, причем крайний правый аргумент находится наверху. Ниже аргументов находится вызываемый объект функции.

CALL_FUNCTION сначала извлекает из стека все аргументы и вызываемый объект. Затем он выделит новый фрейм в стеке вызовов, заполнит локальные переменные для вызова функции и выполнит байт-код функции внутри этого фрейма. Как только это будет сделано, кадр будет извлечен из стека вызовов, а в предыдущем кадре возвращаемое значение функции будет помещено поверх стека оценки. Если предыдущего кадра нет, он будет помещен в стек оценки глобального кадра.

В нашем примере у нас только один позиционный аргумент, поэтому инструкция будет иметь вид CALL_FUNCTION 1 . После этого инструкция

  POP_TOP  

помещает элемент на вершину стека. Это потому, что нам больше не нужно возвращаемое значение функции. На рисунке 2 показаны все операции с байт-кодом со смещениями от 16 до 22. Команды байт-кода внутри f (x) показаны красным.

Рисунок 2

Встроенные функции

В строке 9 дизассемблированного байт-кода листинга 2 мы хотим напечатать (a) . print также является функцией, но это встроенная функция Python. Имя функции - это ссылка на вызываемый объект. Итак, сначала он помещается в стек, а затем передается его аргумент. Наконец, он будет вызываться с использованием CALL_FUNCTION . print вернет None , и после этого возвращенное значение будет извлечено из стека.

Python использует свои встроенные функции для создания структур данных. Например, следующая строка:

 a = [1,2,3] 

будет преобразована в:

 1 0 LOAD_CONST 0 (1) 
2 LOAD_CONST 1 (2)
4 LOAD_CONST 2 (3)
6 BUILD_LIST 3
8 STORE_NAME 0 (a)

Первоначально каждый элемент списка помещается в стек. Затем вызывается инструкция

  BUILD_LIST   count  

для создания списка с использованием count элементов из стека и помещает полученный объект списка в стек.Наконец, объект в стеке будет извлечен и сохранен в куче, и a будет его ссылкой.

EXTENDED_ARG

Как упоминалось ранее, некоторые инструкции могут иметь аргумент, слишком большой, чтобы поместиться в один байт по умолчанию, и они будут иметь префикс инструкции EXTENDED_ARG . Вот пример. Предположим, мы хотим напечатать 260 * символов. Мы могли бы просто написать print ('*' * 260) . Однако вместо этого я напишу что-нибудь необычное:

 s = 'print (' + '"*",' * 260 + ')' 
c = compile (s, "", "exec")
disassemble (c)

Здесь s содержит функцию print , которая принимает 260 аргументов, каждый из которых является символом * .Теперь посмотрите на получившийся дизассемблированный байт-код:

 1 0 LOAD_NAME 0 (печать) 
2 LOAD_CONST 0 ('*')
4 LOAD_CONST 0 ('*')
. .
. .
. . 518 LOAD_CONST 0 ('*')
520 LOAD_CONST 0 ('*')
522 EXTENDED_ARG 1
524 CALL_FUNCTION 260
526 POP_TOP
528 LOAD_CONST 1 (None)
530 RETURN_VALUE

сначала помещается в стек

. .Затем передаются его 260 аргументов. Затем CALL_FUNCTION должен вызвать функцию. Но ему нужно количество аргументов (целевой функции) в качестве аргумента. Здесь это число 260, что больше максимального числа, которое может занимать байт. Помните, что oparg - это только один байт. Таким образом, CALL_FUNCTION имеет префикс EXTENDED_ARG . Фактический байт-код:

 522 EXTENDED_ARG 1 
524 CALL_FUNCTION 4

Как упоминалось ранее, аргумент EXTENDED_ARG будет сдвинут влево на восемь битов или просто умножен на 256 и добавлен к оператору следующего кода операции.Таким образом, oparg CALL_FUNCTION будет интерпретироваться как 256 + 4 = 260 (обратите внимание, что то, что показывает функция disassemble , - это интерпретированный oparg, а не фактический oparg в байт-коде).

Условные операторы и переходы

Рассмотрим следующий исходный код с оператором if-else :

 s = '' 'a = 1 
if a> = 0:
b = a
else:
b = -a
'' '
c = compile (s, "", "exec")
disassemble (c)

Дизассемблированный байт-код:

 1 0 LOAD_CONST 0 (1) 
2 STORE_NAME 0 (a)

2 4 LOAD_NAME 0 (a)
6 LOAD_CONST 1 (0)
8 COMPARE_OP 5 (> =)
10 POP_JUMP_IF_FALSE 18

3 12 LOAD_NAME 0 (a)
14 STORE_NAME 1 (b)
16 JUMP_FORWARD 6 (до 24 )

5 >> 18 LOAD_NAME 0 (a)
20 UNARY_NEGATIVE
22 STORE_NAME 1 (b)
>> 24 LOAD_CONST 2 (Нет)
26 RETURN_VALUE

Здесь есть несколько новых инструкций. В строке 2 объект, на который ссылается a , помещается в стек, а затем помещается литерал 0 . Инструкция

  COMPARE_OP   oparg  

выполняет логическую операцию. Название операции можно найти в cmp_op [oparg] . Значения cmp_op хранятся в списке с именем dis.cmp_op . Инструкция сначала выталкивает два верхних элемента стека. Мы называем первый TOS1 , а второй TOS2 .Затем логическая операция, выбранная с помощью oparg , выполняется над ними (TOS2 cmp_op [oparg] TOS1) , и результат помещается на вершину стека. В этом примере TOS1 = 0 и TOS2 = значение . Кроме того, oparg - это 5 и cmp_op [5] = '≥' . Таким образом, cmp_op проверит a≥0 и сохранит результат (истинный или ложный) поверх стека.

Инструкция

  POP_JUMP_IF_FALSE   target  

выполняет условный переход. Сначала он выталкивает верхнюю часть стека. Если элемент на вершине стека является ложным, он устанавливает счетчик байт-кода на target . Счетчик байт-кода показывает текущее смещение байт-кода, который выполняется. Таким образом, он переходит к смещению байт-кода, которое равно целевому, и выполнение байт-кода продолжается оттуда. Смещение 18 в байт-коде является целью перехода, поэтому в дизассемблированном байт-коде перед ним стоит >> . Команда

  JUMP_FORWARD   delta  

увеличивает счетчик байт-кода на delta .В предыдущем байт-коде смещение этой инструкции равно 16, и мы знаем, что каждая инструкция занимает 2 байта. Итак, когда эта инструкция завершена, счетчик байт-кода будет 16 + 2 = 18 . Здесь дельта = 6 , а 18 + 6 = 24 , поэтому происходит переход к смещению 24 . Смещение 24 является целью перехода и также имеет знак >> .

Теперь мы можем увидеть, как оператор if-else преобразуется в байт-код. cmp_op проверяет, соответствует ли a≥0 .Если результат ложный, POP_JUMP_IF_FALSE переходит с на смещение 18, которое является началом блока else . Если это правда, то будет выполнен блок if , а затем JUMP_FORWARD перейдет к смещению 24 и не выполнит блок else .

Теперь давайте посмотрим на более сложное логическое выражение. Рассмотрим следующий исходный код:

 s = '' 'a = 1 
c = 3
, если a> = 0 и c == 3:
b = a
else:
b = -a
' ''
c = compile (s, "", "exec")
disassemble (c)

Здесь у нас есть логические и .Дизассемблированный байт-код:

 1 0 LOAD_CONST 0 (1) 
2 STORE_NAME 0 (a)

2 4 LOAD_CONST 1 (3)
6 STORE_NAME 1 (c)

3 8 LOAD_NAME 0 (a)
10 LOAD_CONST 2 ( 0)
12 COMPARE_OP 5 (> =)
14 POP_JUMP_IF_FALSE 30
16 LOAD_NAME 1 (c)
18 LOAD_CONST 1 (3)
20 COMPARE_OP 2 (==)
22 POP_JUMP_IF_FALSE 30

_NAME

4 24 LOAD95 0 (a) 26 STORE_NAME 2 (b)
28 JUMP_FORWARD 6 (to 36)

6 >> 30 LOAD_NAME 0 (a)
32 UNARY_NEGATIVE
34 STORE_NAME 2 (b)
>> 36 LOAD_CONST 3 (Нет)
38 RETURN_VALUE

В Python и есть оператор короткого замыкания. Таким образом, при оценке X и Y он оценивает только Y , если X истинно. Это легко увидеть в байт-коде. В строке 3 сначала оценивается левый операнд и . Если (a≥0) ложно, он не оценивает второй операнд и переходит к смещению 30, чтобы выполнить блок else . Однако, если это правда, второй операнд (b == 3) также будет оцениваться.

Циклы и стек блоков

Как упоминалось ранее, в каждом кадре есть стек оценки.Кроме того, в каждом кадре есть блоков, стек . Он используется CPython для отслеживания определенных типов структур управления, таких как циклы, с блоками и try / за исключением блоков . Когда CPython хочет войти в одну из этих структур, новый элемент помещается в стек блоков, а когда CPython выходит из этой структуры, элемент для этой структуры выталкивается из стека блоков. Используя стек блоков, CPython знает, какая структура активна в данный момент. Поэтому, когда он достигает break или continue statement, он знает, какие структуры должны быть затронуты.

Давайте посмотрим, как циклы реализованы в байт-коде. Рассмотрим следующий код и его дизассемблированный байт-код:

 s = '' 'для i в диапазоне (3): 
print (i)
' ''
c = compile (s, "", "exec")
disassemble ( в) ------------------------------------------------ -------------------- 1 0 SETUP_LOOP 24 (до 26)
2 LOAD_NAME 0 (диапазон)
4 LOAD_CONST 0 (3)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 12 (до 24)
12 STORE_NAME 1 (i)

2 14 LOAD_NAME 2 (печать)
16 LOAD_NAME 1 (i)
18 CALL_FUNCTION 1
20 POP_TOP
22 JUMP_ABSOLUTE 10
>> 24 POP_BLOCK
>> 26 LOAD_CONST 1 (Нет)
28 RETURN_VALUE

Инструкция

  SETUP_LOOP   delta  

выполняется перед запуском цикла.Эта инструкция помещает новый элемент (который также называется блоком) в стек блоков. дельта добавляется к счетчику байт-кода, чтобы определить смещение следующей инструкции после цикла. Здесь смещение SET_LOOP равно 0 , поэтому счетчик байт-кода равен 0 + 2 = 2 . Кроме того, дельта составляет 24 , поэтому смещение следующей инструкции после цикла составляет 2 + 24 = 26 . Это смещение сохраняется в блоке, который помещается в стек блоков.Кроме того, в этом блоке хранится текущее количество элементов в стеке оценки.

После этого должна выполняться функция диапазон (3) . Его oparg ( 3 ) помещается перед именем функции. Результатом является итерация . Итерируемые объекты могут сгенерировать итератор , используя инструкцию:

  GET_ITER  

Он берет итератор на вершину стека и подталкивает его итератор. Инструкция:

  FOR_ITER   delta  

предполагает наличие итератора на вершине стека. Он вызывает свой метод __next __ () . Если он дает новое значение, это значение помещается в верхнюю часть стека (над итератором). Внутри цикла верх стека сохраняется в i после этого, и выполняется функция print . Затем выталкивается верхняя часть стека, которая является текущим значением итератора. После этого инструкция

  JUMP_ABSOLUTE   target  

устанавливает счетчик байт-кода на target и переходит к смещению target .Таким образом, он переходит к смещению 10 и снова запускает FOR_ITER , чтобы получить следующее значение итератора. Если итератор указывает, что других доступных элементов нет, верх стека выталкивается, а счетчик байтового кода увеличивается на дельта . Здесь дельта = 12 , поэтому после завершения цикла он переходит к смещению 24. При смещении 24 инструкция

  POP_BLOCK  

удаляет текущий блок из вершины стека блоков. Смещение следующей инструкции после цикла сохраняется в блоке (здесь 26).Таким образом, интерпретатор перейдет к этому смещению и продолжит выполнение оттуда. На рисунке 3 в качестве примера показаны операции с байт-кодом со смещениями 0, 10, 24 и 26 (на самом деле на рисунках 1 и 2 мы показали только стек оценки в каждом кадре).

Рисунок 3

Но что произойдет, если мы добавим в этот цикл оператор break ? Рассмотрим следующий исходный код и его дизассемблированный байт-код:

 s = '' 'для i в диапазоне (3): 
break
print (i)
' ''
c = compile (s, "", "exec")
разобрать (c) --------------------------------------------- ----------------------- 1 0 SETUP_LOOP 26 (до 28)
2 LOAD_NAME 0 (диапазон)
4 LOAD_CONST 0 (3)
6 CALL_FUNCTION 1
8 GET_ITER
>> 10 FOR_ITER 14 (до 26)
12 STORE_NAME 1 (i)

2 14 BREAK_LOOP

3 16 LOAD_NAME 2 (печать)
18 LOAD_NAME 1 (i)
20 CALL_FUNCTION 1
22 POP_TOP 10
24 JUMP_ABSOLUTE
>> 26 POP_BLOCK
>> 28 LOAD_CONST 1 (Нет)
30 RETURN_VALUE

Мы только добавили оператор break к предыдущему ious loop. Этот оператор преобразуется в

  BREAK_LOOP  

Этот код операции удаляет эти лишние элементы из стека оценки и выталкивает блок из вершины стека блоков. Вы должны заметить, что другие инструкции цикла все еще используют стек оценки. Поэтому, когда цикл прерывается, элементы, которые ему принадлежат, должны быть удалены из стека оценки. В этом примере объект итератора все еще находится на вершине стека. Помните, что блок в стеке блоков хранит количество элементов, которые существовали в стеке оценки перед запуском цикла.

Итак, зная это число, BREAK_LOOP выталкивает эти дополнительные элементы из стека оценки. Затем происходит переход к смещению, которое хранится в текущем блоке стека блоков (здесь 28). Это смещение следующей инструкции после цикла. Таким образом, цикл прерывается, и выполнение продолжается оттуда.

Создание объекта кода

Объект кода - это объект типа код , и его можно создавать динамически. Модуль types может помочь с динамическим созданием новых типов, а класс CodeType () в этом модуле возвращает новый объект кода:

типов
.CodeType (co_argcount, co_kwonlyargcount, 
co_nlocals, co_stacksize, co_flags,
co_code , co_consts, co_names,
co_varnames, co_filename, co_name,
co_firstlineno, co_lnotab, freevars = None,
cellvars = None)

Аргументы образуют все атрибуты объекта кода. Вы уже знакомы с некоторыми из этих аргументов (например, co_varnames и co_firstlineno ). freevars и cellvars являются необязательными, так как они используются в замыканиях, и не все функции используют их (дополнительную информацию о них см. В этой статье). Другие атрибуты объясняются на примере следующей функции:

 def f (a, b, * args, c, ** kwargs): 
d = 1
def g ():
return 1
g ()
return 1

co_argcount : Если объект кода является объектом функции, количество принимаемых аргументов (не включая аргументы только по ключевым словам, * или ** аргументов). Для функции f это 2 .

co_kwonlyargcount : если объект кода является объектом функции, количество аргументов, содержащих только ключевые слова (не включая ** arg). Для функции f это 1 .

co_nlocals : количество локальных переменных плюс имя функций, определенных в объекте кода (аргументы также считаются локальными переменными). Фактически, это количество элементов в co_varnames , которое равно ('a', 'b', 'c', 'args', 'kwargs', 'd', 'g') .Так что 7 для f .

co_stacksize : показывает наибольшее количество элементов, которые будут помещены в стек оценки этим объектом кода. Помните, что некоторые коды операций должны помещать некоторые элементы в стек оценки. Этот атрибут показывает самый большой размер, до которого когда-либо будет увеличиваться стек в результате операций с байт-кодом. В этом примере это 2 . Позвольте мне объяснить причину этого. Если вы разобрали байт-код этой функции, вы получите:

 2 0 LOAD_CONST 1 (1) 
2 STORE_FAST 5 (d)

3 4 LOAD_CONST 2 (<объект кода g в 0x0000028A62AB1D20, файл " ", строка 3>)
6 LOAD_CONST 3 ('f. .g ')
8 MAKE_FUNCTION 0
10 STORE_FAST 6 (g)

5 12 LOAD_FAST 6 (g)
14 CALL_FUNCTION 0
16 POP_TOP

6 18 LOAD_CONST 1 (1)
20 RETURN_VALUE
20 RETURN_VALUE

0005 25 строка 2, один элемент помещается в стек с использованием LOAD_CONST и будет извлечен с помощью STORE_FAST . Строки 5 и 6 аналогичным образом помещают один элемент в стек и вставляют его позже.Но в строке 3 два элемента помещаются в стек для определения внутренней функции g : ее кодовый объект и ее имя. Таким образом, это максимальное количество элементов, которое будет помещено в стек оценки этим объектом кода, и оно определяет размер стека.

co_flags : целое число, биты которого указывают на то, принимает ли функция переменное количество аргументов, является ли функция генератором и т. Д. В нашем примере это значение равно 79 .Двоичное значение 79 равно 0b1001111 . Он использует систему с прямым порядком байтов, в которой байты записываются слева направо с возрастанием и . Итак, первый бит - первый справа. Вы можете обратиться к этой ссылке, чтобы узнать значение этих битов. Например, третий бит справа представляет флаг CO_VARARGS . Когда это 1 , это означает, что объект кода имеет переменный позиционный параметр ( * args -like).

co_filename : строка, определяющая файл, в котором присутствует функция. В данном случае это '' , поскольку я запускал сценарий в записной книжке Jupyter.

co_name : имя, с которым был определен этот объект кода. Вот имя функции 'f' .

Внедрение байт-кода

Теперь, когда мы полностью знакомы с объектом кода, мы можем начать изменять его байт-код.Важно отметить, что объект кода неизменен. Итак, однажды созданный, мы не можем его изменить. Предположим, мы хотим изменить байт-код следующей функции:

 def f (x, y): 
return x + yc = f .__ code__

Здесь мы не можем напрямую изменить байт-код объекта кода функции. Вместо этого нам нужно создать новый объект кода, а затем назначить его этой функции. Для этого нам понадобится еще несколько функций. Функция disassemble может дизассемблировать байт-код в некоторые понятные для человека инструкции.Мы можем изменять их по своему усмотрению, но затем нам нужно собрать его обратно в байт-код, чтобы назначить его новому объекту кода. Вывод команды disassemble представляет собой отформатированную строку, которую легко читать, но трудно изменить. Поэтому я добавлю новую функцию, которая может дизассемблировать байт-код в список инструкций. Он очень похож на disassemble , однако его вывод представляет собой список.

Мы можем попробовать это на предыдущей функции:

 disassembled_bytecode = disassemble_to_list (c) 

Теперь disassembled_bytecode равно:

 [['LOAD_FAST', 'x'], 
['LOAD_FAST', 'y '],
[' BINARY_ADD '],
[' RETURN_VALUE ']]

Теперь мы можем легко изменить инструкции этого списка.Но нам также нужно собрать его обратно в байт-код:

Функция get_oparg похожа на обратную функцию get_argvalue . Он принимает значение argvalue, которое является понятным для человека значением oparg, и возвращает соответствующий oparg. Ему нужен объект кода в качестве аргумента, поскольку атрибуты объекта кода, такие как co_consts , необходимы для преобразования значения argvalue в oparg.

Функция build берет объект кода и список дизассемблированных байт-кодов и собирает их обратно в байт-код. Он использует dis.opname для преобразования имени операции в код операции. Затем он вызывает get_oparg для преобразования значения argvalue в oparg. Наконец, он возвращает байтовый литерал списка байт-кода. Теперь мы можем использовать эти новые функции для изменения байт-кода предыдущей функции f . Сначала мы меняем одну из инструкций в disassembled_bytecode :

 disassembled_bytecode [2] = ['BINARY_MULTIPLY'] 

Инструкция

  BINARY_MULTIPLY  

выталкивает два верхних элемента стека и умножает их вместе результат в стек.Теперь мы собираем модифицированный дизассемблированный байт-код:

 new_co_code = assembly (disassembled_bytecode, c.co_consts, 
c.co_varnames, c.co_names,
c.co_cellvars + c.co_freevars)

После этого мы создаем новый объект кода:

 типов импорта 
nc = types.CodeType (c.co_argcount, c.co_kwonlyargcount,
c.co_nlocals, c. co_stacksize, c.co_flags,
new_co_code, c.co_consts, c.co_names,
c.co_varnames, c.co_varnames , c.co_name,
c.co_firstlineno, c.co_lnotab,
c.co_freevars, c.co_cellvars)
f .__ code__ = nc

Мы используем все атрибуты f для его создания и заменяем только новый байт-код ( new_co_code ). Затем мы назначаем новый объект кода f . Теперь, если мы снова запустим f , он не суммирует свои аргументы. Вместо этого он умножит их вместе:

 f (2,5) # Результат будет 10, а не 7 

Внимание : Типы . Функция CodeType имеет два необязательных аргумента для freevars и cellvars , однако: при их использовании следует соблюдать осторожность.Как упоминалось ранее, атрибуты co_cellvars и co_freevars объекта кода используются только тогда, когда объект кода принадлежит функции, которая имеет свободные переменные или нелокальные переменные. Таким образом, функция должна быть замыканием или внутри нее должно быть определено замыкание. Например, рассмотрим следующую функцию:

 def func (x): 
def g (y):
return x + y
return g

Теперь проверьте его объект кода:

 c = func .__ code__ 
c.co_cellvars # Вывод: ('x',)

Фактически, эта функция имеет одну нелокальную переменную x , поскольку к этой переменной обращаются ее внутренние функции. Теперь мы можем попробовать воссоздать его кодовый объект, используя те же атрибуты:

 nc = types.CodeType (c.co_argcount, c.co_kwonlyargcount, 
c.co_nlocals, c.co_stacksize, c.co_flags,
new_co_code, c.co_consts, c .co_names,
c.co_varnames, c.co_filename, c.co_name,
c.co_firstlineno, c.co_lnotab,
cellvars = c.co_cellvars,
freevars = c.co_freevars)

Но если мы проверим тот же атрибут нового объекта кода

 nc.co_cellvars Результат будет: () 

Он окажется пустым. Итак, types.CodeType не может создать один и тот же объект кода. Если вы попытаетесь назначить этот объект кода функции и выполнить эту функцию, вы получите сообщение об ошибке (это было проверено на Python 3.7.4).

Оптимизация кода

Понимание инструкций байт-кода может помочь нам в оптимизации исходного кода.Рассмотрим следующий исходный код:

 setup1 = '' 'import math 
mult = 2
def f ():
total = 0
i = 1
для i в диапазоне (1, 200):
total + = mult * math.log (i)
вернуть итог
'' 'setup2 =' '' import math
def f ():
log = math.log
mult = 2
total = 0
для i в диапазоне (1, 200) :
total + = mult * log (i)
return total
'' '

Здесь мы определяем функцию f () для вычисления простого математического выражения.Это было определено двумя разными способами. В setup1 мы используем глобальную переменную mult внутри f () и напрямую используем функцию log () из модуля math . В setup2 , mult является локальной переменной f () . Кроме того, math.log сначала сохраняется в локальной переменной log . Теперь мы можем сравнить производительность этих функций:

 t1 = timeit.timeit (stmt = "f ()", setup = setup1, number = 100000) 
t2 = timeit.timeit (stmt = "f ()", setup = setup2, number = 100000)
print ("t1 =", t1)
print ("t2 =", t2)
----------- -------------------------------------------------- -------
t1 = 3.80761210086
t2 = 3.223011

14383

Вы можете получить разные числа для t1 и t2 , но суть в том, что setup2 быстрее, чем setup1 . Теперь давайте сравним их байт-код, чтобы понять, почему он быстрее. Мы просто смотрим на строку 7 в дизассемблированном коде setup1 и setup2 .Это байт-код для этой строки: total + = mult * log (i) .

В setup1 мы имеем:

 7 24 LOAD_FAST 0 (всего) 
26 LOAD_GLOBAL 1 (mult)
28 LOAD_GLOBAL 2 (математический)
30 LOAD_METHOD 3 (log)
32 LOAD_FAST 1 (i)
34 1 CALL_MET
36 BINARY_MULTIPLY
38 INPLACE_ADD
40 STORE_FAST 0 (итого)
42 JUMP_ABSOLUTE 20
>> 44 POP_BLOCK

Но в setup2 мы получаем:

 7 30 LOAD_FAST 2 (итого) 
32 LOAD_FAST LOAD_FAST 0 (журнал)
36 LOAD_FAST 3 (i)
38 CALL_FUNCTION 1
40 BINARY_MULTIPLY
42 INPLACE_ADD
44 STORE_FAST 2 (всего)
46 JUMP_ABSOLUTE 26
>> 48 POP_BLOCK
Как вы видите в обоих настройках1 mult и math загружаются с использованием LOAG_GLOBAL , но в setup2 , mult и log загружаются с использованием LOAD_FAST . Таким образом, две инструкции LOAD_GLOBAL были заменены на LOAD_FAST . Дело в том, что LOAD_FAST , как следует из названия, намного быстрее, чем LOAD_GLOBAL . Мы упоминали, что имена глобальных и локальных переменных хранятся в co_names и co_varnames . Но как интерпретатор CPython находит значения при выполнении скомпилированного кода?

Локальные переменные хранятся в массиве в каждом кадре (который не показан на предыдущих рисунках для упрощения).Мы знаем, что имена локальных переменных хранятся в co_varnames . Их значения будут храниться в этом массиве в том же порядке. Поэтому, когда интерпретатор видит инструкцию типа LOAD_FAST 1 (mult) , он считывает элемент этого массива по индексу 1 .

Глобальные и встроенные функции модуля хранятся в словаре. Мы знаем, что их имена хранятся в co_names . Поэтому, когда интерпретатор видит инструкцию типа LOAD_GLOBAL 1 (mult) , он сначала получает имя этой глобальной переменной из co_names [1] . Затем он будет искать это имя в словаре, чтобы получить его значение. Это намного более медленный процесс по сравнению с простым поиском в массиве локальных переменных. В результате LOAD_FAST быстрее, чем LOAD_GLOBAL , и замена LOAD_GLOBAL на LOAD_FAST может улучшить производительность. Это можно сделать, просто сохранив встроенные и глобальные переменные в локальных переменных или напрямую изменив инструкции байт-кода.

Пример: определение констант в Python

В этом примере показано, как использовать внедрение байт-кода для изменения поведения функций.Мы собираемся написать декоратор, который добавляет в Python оператор const . В некоторых языках программирования, таких как C, C ++ и JavaScript, есть ключевое слово const . Если переменная объявлена ​​как const с использованием этого ключевого слова, то изменение ее значения недопустимо, и мы больше не можем изменить значение этой переменной в исходном коде.

Python не имеет оператора const, и я не утверждаю, что действительно необходимо иметь такое ключевое слово в Python. Кроме того, определение констант может быть выполнено без использования инъекции байт-кода.Итак, это всего лишь пример, чтобы показать вам, как привести в действие инъекцию байт-кода. Во-первых, позвольте мне показать, как вы можете его использовать. Ключевое слово const предоставляется с помощью декоратора функции с именем const . После того, как вы украсите функцию const , вы можете объявить переменную внутри нее как константу, используя ключевое слово const. (. в конце является частью ключевого слова). Вот пример:

 @const 
def f (x):
const. A = 5
return A * x
f (2) # Результат: 10

Переменная A внутри f теперь является константой.Теперь, если вы попытаетесь переназначить эту переменную внутри f , будет возбуждено исключение:

 @const 
def f (x):
const. A = 5
A = A + 1
возврат A * x
----------------------------------- --------------------------------- # Возникает исключение:
ConstError : 'A' - константа и не может быть переназначен!

Когда переменная объявлена ​​как константа, ей должно быть присвоено ее начальное значение, и она будет локальной переменной этой функции.

Теперь позвольте мне показать вам, как это было реализовано. Предположим, я определяю такую ​​функцию (без декораций):

 def f (x): 
const. A = 5
A = A + 1
return A * x

Он будет правильно скомпилирован. Но если вы попытаетесь выполнить эту функцию, вы получите ошибку:

 f (2) 
------------------------------ -------------------------------------- NameError : имя 'const' не определено

Теперь давайте посмотрим на дизассемблированный байт-код этой функции:

 2 0 LOAD_CONST 1 (5) 
2 LOAD_GLOBAL 0 (const)
4 STORE_ATTR 1 (A)

3 6 LOAD_FAST 1 (A)
8 LOAD_CONST 2 ( 1)
10 BINARY_ADD
12 STORE_FAST 1 (A)

4 14 LOAD_FAST 1 (A)
16 LOAD_FAST 0 (x)
18 BINARY_MULTIPLY
20 RETURN_VALUE

Когда Python пытается скомпилировать функцию const , требуется как глобальная переменная, поскольку она не была определена в функции. Переменная A считается атрибутом глобальной переменной A . Фактически, конст. A = 1 совпадает с const. A = 1 , поскольку Python игнорирует пробелы между оператором точки и именем атрибута. Конечно, у нас действительно нет глобальной переменной с именем A в исходном коде. Но Python не будет проверять это во время компиляции. Только в процессе выполнения окажется, что имя const не определено. Так что наш исходный код будет принят при компиляции.Но нам нужно изменить его байт-код перед выполнением объекта кода этой функции. Сначала нам нужно создать функцию для изменения байт-кода:

Эта функция получает список инструкций байт-кода, сгенерированный assembly_to_list в качестве аргумента. Он имеет два списка с именами констант и индексов , в которых хранятся имена переменных, объявленных как const, и смещение, по которому они были назначены впервые. Первый цикл просматривает список инструкций байт-кода и находит все инструкции ['LOAD_GLOBAL', 'const'] . Имя переменной должно быть в следующей инструкции. В этом примере следующая инструкция - ['STORE_ATTR', 'A'] , имя - A . Это имя и смещение этой инструкции хранятся в константах и индексах . Теперь нам нужно избавиться от глобальной переменной const и ее атрибута и вместо этого создать локальную переменную с именем A . Инструкция

  NOP  

- это код «ничего не делать».Когда интерпретатор достигает NOP , он игнорирует его. Мы не можем просто удалить код операции из списка инструкций, поскольку удаление одной инструкции уменьшает смещение всех следующих инструкций. Теперь, если в байт-коде есть скачки, их целевое смещение тоже должно измениться. Так что намного проще просто заменить ненужную инструкцию на NOP . Теперь мы заменяем ['LOAD_GLOBAL', 'const'] на NOP , а затем заменяем ['STORE_ATTR', 'A'] на ['STORE_FAST', 'A'] . Последний байт-код выглядит следующим образом:

 2 0 LOAD_CONST 1 (5) 
2 NOP
4 STORE_FAST 1 (A)

3 6 LOAD_FAST 1 (A)
8 LOAD_CONST 2 (1)
10 BINARY_ADD
12 STORE_FAST 1 (A) )

4 14 LOAD_FAST 1 (A)
16 LOAD_FAST 0 (x)
18 BINARY_MULTIPLY
20 RETURN_VALUE

Теперь строка 2 эквивалентна a = 2 в исходном коде, и выполнение этого байт-кода не вызывает любая ошибка времени выполнения.Цикл также проверяет, не объявлена ​​ли одна и та же переменная как константа дважды. Поэтому, если переменная, объявленная как const, уже существует в списке констант , она вызовет настраиваемое исключение. Теперь осталось только убедиться, что переменные const не переназначены.

Второй цикл снова просматривает список инструкций байт-кода, чтобы найти любое переназначение постоянных переменных. Любая инструкция вроде ['STORE_GLOBAL', 'A'] или ['STORE_FAST', 'A'] означает, что переназначение находится в исходном коде, поэтому она вызовет настраиваемое исключение, чтобы предупредить пользователя. Смещение начального присвоения константы требуется, чтобы убедиться, что начальное присвоение не рассматривается как переназначение.

Как упоминалось ранее, байт-код следует изменить перед выполнением кода. Таким образом, перед вызовом функции f необходимо вызвать функцию add_const . По этой причине мы помещаем его внутрь декоратора. Функция декоратора const получает в качестве аргумента целевую функцию f . Сначала он изменит байт-код f , используя add_const , а затем создаст новый объект кода с измененным байт-кодом.Этот кодовый объект будет присвоен f .

Когда мы создаем новый объект кода, необходимо изменить некоторые из его атрибутов. В исходной функции const - глобальная переменная, а A - атрибут, поэтому оба они были добавлены в кортеж co_names , и их следует удалить из co_names нового объекта кода. Кроме того, когда такой атрибут, как A , превращается в локальную переменную, его имя должно быть добавлено в кортеж co_varnames . Атрибут co_nlocals указывает количество локальных переменных (плюс определенные функции) и также должен быть обновлен. Остальные атрибуты остаются прежними. Наконец, декоратор возвращает целевую функцию с новым объектом кода, и теперь целевая функция готова к выполнению.

Дизассемблирование байт-кода Python - Питер Голдсборо

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

Базовое использование

Основная функция, с которой вы будете взаимодействовать, когда захотите дизассемблировать код Python, - это dis.dis . Он принимает либо функцию, метод, класс, модуль, строку кода, генератор или последовательность байтов необработанного байт-кода и распечатывает разборку этого объекта кода в stdout (если не указан явный аргумент file ). В случае класса он будет дизассемблировать каждый метод (также статические методы и методы класса). Для модуля он разбирает все функции в этом модуле.

Давайте посмотрим на это на практике. Возьмите следующий код:

  импорт

класс Foo (объект):
  def __init __ (сам):
    проходят

  def foo (self, x):
    вернуть x + 1

def bar ():
  х = 5
  у = 7
  г = х + у
  вернуть z

def main ():
  dis.dis (bar) # разбирает `bar`
  dis.dis (Foo) # разбирает каждый метод в `Foo`
  

Это напечатает:

  14 0 НАГРУЗКА_КОНСТ 1 (5)
             3 STORE_FAST 0 (x)

15 6 LOAD_CONST 2 (7)
             9 STORE_FAST 1 (г)

16 12 LOAD_FAST 0 (x)
            15 LOAD_FAST 1 (г)
            18 BINARY_ADD
            19 STORE_FAST 2 (z)

17 22 LOAD_FAST 2 (z)
            25 RETURN_VALUE

Разборка __init__:
 8 0 LOAD_CONST 0 (Нет)
             3 RETURN_VALUE

Разборка foo:
11 0 LOAD_FAST 1 (x)
             3 LOAD_CONST 1 (1)
             6 BINARY_ADD
             7 RETURN_VALUE
  

Кроме того, мы можем дизассемблировать весь модуль из командной строки, используя python -m dis module_file. py . В любом случае, на этом этапе нам, вероятно, следует обсудить формат вывода дизассемблирования. Возвращаются следующие столбцы:

  1. Исходная строка кода, на которую ссылается разборка.
  2. Адрес инструкции байт-кода.
  3. Название инструкции.
  4. Индекс аргумента в имени блока кода и таблице констант.
  5. Удобное для человека отображение индекса аргумента (4) на фактическое значение или имя, на которое ссылаются.

Для (4) важно понимать, что все объектов кода в Python, то есть изолированные блоки кода, такие как функции, имеют внутреннее имя и таблицы констант . Эти таблицы представляют собой просто списки, где таблица констант будет содержать константы, такие как строковые литералы, числа или специальные значения, такие как None , которые появляются хотя бы один раз в блоке кода, тогда как таблица имен будет содержать список имен переменных. Эти имена переменных затем являются ключами в словаре, сопоставляющем такие символы с фактическими значениями. Причина, по которой аргументы инструкции являются индексами в таблицах, а не значениями, хранящимися в этих таблицах, заключается в том, что аргументы могут иметь одинаковую длину (всегда два байта). Как вы понимаете, хранение строк переменной длины в байт-коде напрямую делает продвижение счетчика программы намного более сложным.

Объекты кода

Я только что упомянул концепцию объектов кода . Но что это? Проще говоря, объекты кода - это скомпилированное представление части кода.Затем это представление может быть понято интерпретатором cPython, который является наиболее распространенной и популярной реализацией языка программирования Python. Он написан на C и использует эти объекты кода для построчного выполнения кода Python. Рядом с cPython существует ряд других компиляторов и интерпретаторов Python, включая PyPy (который использует JIT-компилятор, что часто делает его намного быстрее, чем cPython) и Jython.

Есть несколько способов создания объектов кода. Самый простой - получить доступ к члену __code__ , который содержит все функции и методы (помните, что методы - это просто функции с дополнительным аргументом экземпляра):

  def foo ():
  проходят


В [1]: foo.__код__
Out [1]: <код объекта foo по адресу 0x109d419c0, файл «<строка>», строка 1>
  

В качестве альтернативы вы можете просто скомпилировать любой фрагмент кода самостоятельно, используя встроенную команду compile . Эта функция вернет объект кода. Принимает следующие параметры:

  1. Строка или объект ast .
  2. Имя файла, в котором написан код, или описательная строка, если такой файл не существует (например, «<строка>» ).
  3. Режим компиляции, который может быть одной из следующих трех строк:
    • 'exec' : позволяет анализировать несколько выражений.
    • 'eval' : позволяет анализировать только одно выражение (без пробелов).
    • 'single' : интерпретирует и оценивает выражение как интерактивная оболочка. Это означает, что код будет оценен, и любой результат, отличный от None , будет напечатан.

Например:

  code_object = compile ('x = 5; x + = 1', '', 'exec') # ОК
code_object = compile ('x = 5; x + = 1', '', 'eval') # Ошибка
  

Эти объекты кода предоставляют всю информацию, необходимую интерпретатору Python, например cPython, для выполнения кода, представленного объектом.Для этого у объекта кода есть несколько интересных атрибутов:

  • co_code : фактическая байтовая строка байт-кода.
  • co_consts : Константы, доступные инструкциям («таблица констант»).
  • co_names : глобальные символы, доступные для инструкций.
  • co_nlocals : количество локальных символов (функций и переменных).
  • co_varnames : список локальных символов (функций и переменных).
  • co_argcount : если объект кода является объектом функции, количество аргументов, которые она принимает.

Например, мы можем увидеть это, когда создадим следующую функцию:

  def foo ():
  х = 5
  def bar ():
    проходят
  вернуть len ([x])
  

Кодовый объект foo будет иметь константы ( co_consts ) None и 5 , имена локальных переменных ( co_varnames ) bar и x и доступные символы ( co_names ) лен :

  В [1]: foo.__code __. co_consts
Выход [1]: (Нет, 5)
В [2]: foo .__ code __. Co_varnames
Выход [2]: ('bar', 'x')
В [3]: foo .__ code __. Co_names
Out [3]: ('len',)
  

Понимание дизассемблированного кода

Теперь, когда мы знаем, что такое объекты кода и что у них есть таблицы имен и констант, мы можем лучше понять вывод dis.dis () . Возьмем разборку следующей функции:

  у = 5
функция def ():
  x = len ([1, 2, 3])
  г = х + у
  вернуть z
  

то есть:

  В [1]: дис.дисфункция)
Из [1]:
  3 0 LOAD_GLOBAL 0 (длина)
              3 LOAD_CONST 1 (1)
              6 LOAD_CONST 2 (2)
              9 LOAD_CONST 3 (3)
             12 BUILD_LIST 3
             15 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
             18 STORE_FAST 0 (x)

  4 21 LOAD_FAST 0 (x)
             24 LOAD_GLOBAL 1 (г)
             27 BINARY_ADD
             28 STORE_FAST 1 (z)

  5 31 LOAD_FAST 1 (z)
             34 RETURN_VALUE
  

Например, самая первая строка показывает инструкцию LOAD_GLOBAL .Это первая инструкция байт-кода третьей строки функции Python, как указано во втором и первом столбцах соответственно. Как мы также можем видеть, он загружает имя с индексом 0. Напомним, что этот индекс ссылается на список co_names , связанный с объектом дизассемблированного кода. Последний столбец len - это просто дружеский намек от dis.dis () относительно значения co_names [0] .

Но что делает LOAD_GLOBAL ? Ну загружает глобальный.В данном случае глобальная функция (символ) len . Это было очевидно, но что более интересно, так это , где загружает глобальные данные. Ответ на этот вопрос заключается в том, как устроен интерпретатор cPython: он полностью основан на стеке. Это означает, что в стек добавляются любые символы функций или методов, константы или имена переменных. Такие операции, как двоичное сложение (инструкция байт-кода BINARY_ADD , которую вы видите) будут затем работать с этим стеком, выталкивая два значения и помещая результат обратно в стек.Инструкции вызова функции ( CALL_FUNCTION ) аналогичным образом выталкивают символ функции и аргументы из стека, выполняют функцию (которая помещает новый кадр в стек) и, в конечном итоге, помещают возвращаемое значение вызываемой функции обратно в стек.

Полный список инструкций, понятных cPython, можно найти здесь. Вот несколько интересных:

  • LOAD_FAST : помещает локальную переменную по заданному индексу в стек.
  • STORE_GLOBAL : вставляет вершину стека в переменную с именем, указанным в данном индексе.
  • BINARY_ADD : извлекает два верхних значения из стека, складывает их и помещает результат обратно в стек.
  • BUILD_LIST <размер> : извлекает из стека множество элементов размером и выделяет новый список.
  • CALL_FUNCTION <#arguments> : Извлекает #arguments множества аргументов из стека.Младший байт этого числа определяет количество позиционных аргументов, старший байт - количество аргументов ключевого слова. Затем извлекает символ функции (саму себя) из стека, выполняет вызов и помещает возвращаемое значение функции обратно в стек.
  • MAKE_FUNCTION <#arguments> : для создания объекта функции потребляет:
    • Все аргументы по умолчанию в позиционном порядке,
    • Кодовый объект функции,
    • Полное имя функции, из стека и помещает созданный объект функции в стек.
  • POP_TOP : просто выталкивает (например, «удаляет») значение наверху стека.

Практические примеры

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

Исследование оптимизаций

Один простой случай - это расследование, когда ваш компилятор Python выполняет определенные оптимизации, а когда нет. Например, возьмите следующий код:

  я = 1 + 2
f = 3.4 * 5,6
s = 'Привет' + 'Мир!'

Я = я * 3 * 4
F = f / 2/3
S = s + 'Пока!'
  

Делаем разборку показывает нам (комментарии мои):

  ### i
2 0 LOAD_CONST 10 (3)
            3 STORE_FAST 0 (i)

### f

3 6 LOAD_CONST 11 (19.04)
            9 STORE_FAST 1 (f)

### s

4 12 LOAD_CONST 12 ('Привет, мир!')
           15 STORE_FAST 2 (с)

### Я

6 18 LOAD_FAST 0 (i)
           21 LOAD_CONST 7 (3)
           24 BINARY_MULTIPLY
           25 LOAD_CONST 8 (4)
           28 BINARY_MULTIPLY
           29 STORE_FAST 3 (I)

### F

7 32 LOAD_FAST 1 (f)
           35 LOAD_CONST 2 (2)
           38 BINARY_TRUE_DIVIDE
           39 LOAD_CONST 7 (3)
           42 BINARY_TRUE_DIVIDE
           43 STORE_FAST 4 (F)

### S

8 46 LOAD_FAST 2 (с)
           49 LOAD_CONST 9 («Пока!»)
           52 BINARY_ADD
           53 STORE_FAST 5 (S)
           56 LOAD_CONST 0 (Нет)
  

Как видите, компилятор легко выполнит простые арифметические операции «во время компиляции», такие как сложение 1 + 2 или сокращение выражения 3. 4 * 5.6 к константе 19.04 . Компилятор даже немедленно выполнит конкатенацию строк. Однако, как только задействуется одна переменная (см. I или f ), компилятор прекращает оптимизацию и загружает имя переменной, а также все константы по отдельности и выполняет двоичные операции одну за другой в естественном порядке. операции.

Когда самообладание - это плохо

Теперь давайте рассмотрим более реалистичный случай, когда разборка действительно дала бы вам понимание.В Python мы обычно используем диапазон и итераторы для перебора последовательности чисел. Однако мы можем подумать, что вызов диапазона и использование итератора может быть слишком дорогостоящим по сравнению с циклом, подобным C-образному свертыванию. То есть мы хотим сравнить:

с

  я = 0
пока я <х:
  я + = 1
  

Давайте сделаем это и профилируем их:

  В [1]: timeit. timeit ('for i in range (x): pass', globals = dict (x = 100))
Из [1]: 1.49653139401

В [2]: timeit.timeit ('i = 0 \ n while i  

Как видим, встроенная версия намного шустрее. Давайте разберем код и попробуем понять, почему. Сначала для диапазона петля:

  1 0 SETUP_LOOP 20 (до 23)
            3 LOAD_NAME 0 (диапазон)
            6 LOAD_NAME 1 (x)
            9 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
           12 GET_ITER
      >> 13 FOR_ITER 6 (до 22)
           16 STORE_NAME 2 (i)
           19 JUMP_ABSOLUTE 13
      >> 22 POP_BLOCK
      >> 23 LOAD_CONST 0 (Нет)
           26 RETURN_VALUE
  

Тогда для собственной версии:

  1 0 LOAD_CONST 0 (0)
            3 STORE_NAME 0 (i)

2 6 SETUP_LOOP 26 (до 35)
      >> 9 LOAD_NAME 0 (i)
           12 LOAD_NAME 1 (x)
           15 COMPARE_OP 0 (<)
           18 POP_JUMP_IF_FALSE 34
           21 LOAD_NAME 0 (i)
           24 LOAD_CONST 1 (1)
           27 INPLACE_ADD
           28 STORE_NAME 0 (i)
           31 JUMP_ABSOLUTE 9
      >> 34 POP_BLOCK
      >> 35 LOAD_CONST 2 (Нет)
           38 RETURN_VALUE
  

Как мы видим, версия, использующая встроенную функцию range , имеет меньше инструкций, чем вариант «накатать самостоятельно». В конечном итоге это сводится к тому, что версия, использующая диапазон , будет лучше использовать внутренние реализации итераторов C, в то время как наша настраиваемая версия требует повторной интерпретации INPLACE_ADD , COMPARE_OP , POP_JUMP_IF_FALSE и других операций, что требует больше времени.

Динамический поиск

Наконец, проверка дизассемблирования кода может дать нам довольно интересное представление о том, как свойства динамического языка Python влияют на его производительность.Напомним, что символ в Python необходимо повторно оценивать при каждом вызове во время выполнения. Это означает, что даже если мы дважды используем одну и ту же глобальную переменную в одной строке кода, ее придется извлекать из глобального пространства имен дважды, индивидуально, независимо от предыдущих или будущих загрузок, учитывая, что она может очень хорошо изменить значение и / или введите между оценками. В конечном итоге это означает, что кэширование «глубоко определенных» имен (с большим количеством точек) и сохранение их в локальных переменных может немного улучшить производительность нашего кода.

В качестве простого примера предположим, что мы хотим просуммировать синусы диапазона значений - множества значений. Наивный способ сделать это будет следующим:

  def first ():
  всего = 0
  для x в диапазоне (1000):
    всего + = math.sin (x)
  общая сумма возврата
  

Когда мы разбираем это с помощью dis.dis (first) , мы получаем следующее:

  2 0 LOAD_CONST 1 (0)
            3 STORE_FAST 0 (всего)

3 6 SETUP_LOOP 39 (до 48)
            9 LOAD_GLOBAL 0 (диапазон)
           12 LOAD_CONST 2 (1000)
           15 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
           18 GET_ITER
      >> 19 FOR_ITER 25 (к 47)
           22 STORE_FAST 1 (x)

4 25 LOAD_FAST 0 (всего)
           28 LOAD_GLOBAL 1 (математика)
           31 LOAD_ATTR 2 (грех)
           34 LOAD_FAST 1 (x)
           37 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
           40 INPLACE_ADD
           41 STORE_FAST 0 (всего)
           44 JUMP_ABSOLUTE 19
      >> 47 POP_BLOCK

5 >> 48 LOAD_FAST 0 (всего)
           51 RETURN_VALUE
  

Важный бит - строка 4. Как видите, поскольку мы полностью квалифицируем math.sin , мы должны:

  1. LOAD_GLOBAL символ модуля math .
  2. LOAD_ATTR символ sin .

Где (2) в конечном итоге означает поиск по словарю (хеш-таблице). Понимая динамическую природу Python, мы можем предположить, что кэширование и сохранение символа math.sin в локальной переменной может значительно улучшить производительность нашего кода в тесном внутреннем цикле.Посмотрим:

  по умолчанию в секунду ():
  грех = math.sin
  всего = 0
  для x в диапазоне (1000):
    всего + = грех (х)
  общая сумма возврата
  

и в разобранном виде:

  2 0 LOAD_GLOBAL 0 (математика)
            3 LOAD_ATTR 1 (грех)
            6 STORE_FAST 0 (грех)

3 9 LOAD_CONST 1 (0)
           12 STORE_FAST 1 (всего)

4 15 SETUP_LOOP 36 (по 54)
           18 LOAD_GLOBAL 2 (диапазон)
           21 LOAD_CONST 2 (1000)
           24 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
           27 GET_ITER
      >> 28 FOR_ITER 22 (к 53)
           31 STORE_FAST 2 (x)

5 34 LOAD_FAST 1 (всего)
           37 LOAD_FAST 0 (грех)
           40 LOAD_FAST 2 (x)
           43 CALL_FUNCTION 1 (1 позиционная, 0 пар ключевых слов)
           46 INPLACE_ADD
           47 STORE_FAST 1 (всего)
           50 JUMP_ABSOLUTE 28
      >> 53 POP_BLOCK

6 >> 54 LOAD_FAST 1 (всего)
           57 RETURN_VALUE
  

Если мы теперь посмотрим на внутренний цикл (строка 5), мы увидим, что мы заменили последовательность LOAD_GLOBAL , LOAD_ATTR на LOAD_FAST - поиск одиночной локальной переменной. Как следует из названия, это будет намного быстрее (предположительно). Давайте проверим:

  В [1]: timeit.timeit ('f ()', globals = dict (f = first), number = 100000)
Выход [1]: 27.1769976962

В [2]: timeit.timeit ('f ()', globals = dict (f = second), number = 100000)
Выход [2]: 15.568848820985295
  

Ну, я не знаю вашего определения «намного быстрее», но множитель (почти) два в моей книге намного быстрее.

Интерпретация байт-кода

Дизассемблированные инструкции байт-кода уже достаточно низкоуровневые (файл.к.а. здорово). Однако мы можем пойти еще глубже и понять сам код байт - то есть двоичное или шестнадцатеричное представление инструкций в скомпилированном и собранном байт-коде. Для этого давайте определим функцию и еще немного поработаем с ее свойством __code__ :

  функция def ():
  х = 5
  l = [1, 2]
  вернуть len (l) + x
  

Через function .__ code__ мы можем получить доступ к объекту кода, связанному с функцией. Кроме того, функция .__ code __. Co_code возвращает фактический байт-код:

  In [1]: function .__ code __. Co_code
Out [1]: b'd \ x01 \ x00} \ x00 \ x00d \ x02 \ x00d \ x03 \ x00g \ x02 \ x00} \ x01 \ x00t \ x00 \ x00 | \ x01 \ x00 \ x83 \ x01 \ x00 | \ x00 \ x00 \ x17S '
  

Да! Байтов! Как раз то, что я люблю на завтрак. Но что мы можем сделать из этих восхитительных кусочков байт-кода? Что ж, мы знаем, что эти байты определяют инструкции, некоторые из которых принимают аргументы, а некоторые нет. Каждая инструкция будет занимать один байт, а аргументы (например, индексы в имени и таблице констант) будут занимать дополнительные байты.Кроме того, к счастью, модуль dis (а также модуль кода операции ) предоставляет таблицу opname и карту opmap . Первый представляет собой простой список, составленный таким образом, что его индексация с помощью кода операции инструкции возвращает имя ( мнемоника ) этой инструкции. Последний, dis.opmap , сопоставляет мнемонику инструкций с номерами их байт-кода:

  В [1]: dis.opname [69]
Выход [1]: 'GET_YIELD_FROM_ITER'

В [2]: дис.opmap ['LOAD_CONST']
Вых [2]: 100
  

Итак, если мы знаем значение байта, описывающее определенную инструкцию, теперь мы знаем, как получить имя инструкции. Осталось только интерпретировать аргументы этих инструкций. Для этого нам нужно знать, принимает ли инструкция аргументы в первую очередь. Чтобы получить эту информацию, мы можем использовать dis.hasconst , dis.hasname , dis.hasjrel и dis.hasjabs и другие. Каждый из них представляет собой списки в модуле dis , которые содержат байт-коды, принимающие либо постоянный аргумент, либо аргумент имени, либо цель относительного / абсолютного перехода или другой тип параметра.Например, dis.hasnargs также является таким списком, содержащим все коды операций, относящиеся к вызовам функций, такие как CALL_FUNCTION , CALL_FUNCTION_VAR (для функций, принимающих * args ) или CALL_FUNCTION_KW (для функций, принимающих * * kwargs ). Примечательно, что если инструкция вообще принимает аргументы, она может принимать только один аргумент, занимающий ровно 16 бит (два байта).

Запись собственного байт-кода

Для практики и развлечения мы теперь можем сами написать некоторый байт-код.Скажем, например, мы хотели записать в байт-коде x = 1 + 2 . Это равносильно первой загрузке констант 1 и 2 в стек с использованием LOAD_CONST с кодом операции 100 ( 0x64 ). Затем мы выполняем BINARY_ADD (код операции 23 / 0x17 ), который выталкивает эти два значения из стека и возвращает результат (3) наверх. Затем мы выполним STORE_NAME (код операции 125 / 0x7d ), чтобы извлечь результат из стека и сохранить его в единственной переменной, которая у нас будет, т.е.е. с индексом 0. Это равняется:

  0 LOAD_CONST 0 # 0x64
3 LOAD_CONST 1 # 0x64
6 BINARY_ADD # 0x17
7 STORE_NAME 0 # 0x7d

consts = (1, 2)
имена = ('х',)
  

или в (десятичных) байтах:

  100 0 0 # consts [0]
100 1 0 # consts [1]
23 # без аргументов
125 0 0 # имен [0]
  

Обратите внимание, что компилятор фактически оптимизирует 1 + 2 до константы 3 , как вы заметите, если вы запустите dis. dis ('x = 1 + 2') .Кроме того, когда вы запустите dis.dis , вы увидите, что код, похоже, возвращает None в конце. Это просто деталь реализации cPython, связанная с тем, что он написан на C и всегда требует возвращаемого значения (даже для кода вне функций).

Дизассемблирование также сообщает нам, что одна инструкция LOAD_CONST занимает три байта. Один для кода операции и два для 16-битного параметра. Наконец, важно отметить, что аргументы хранятся в формате little endian .Вот почему LOAD_CONST 1 - это 100 1 0 , синус 0b00000001 0b00000000 - 1 с прямым порядком байтов.

Написание дизассемблера

Учитывая всю эту информацию, мы теперь более чем готовы написать собственный небольшой дизассемблер байт-кода. Мы просто возьмем байтовую строку, например, возвращенную атрибутом co_code объекта кода, и переберем ее байты. Нам нужно будет прочитать байты для декодирования инструкций и выбрать правильное количество последующих байтов для аргументов. Кроме того, мы хотим использовать co_lnotab и co_firstlineno для перекрестных ссылок между байт-кодом и исходным, дизассемблированным кодом. Вы можете найти небольшую реализацию (~ 300 строк), следуя этим идеям, здесь.

Outro

Многие люди любят Python за скорость разработки, которую обеспечивает высокоуровневый интерфейс программирования с высокой степенью абстракции. Тем не менее, каждый язык программирования высокого уровня обладает огромной глубиной загадок низкоуровневой реализации, которые дают вам представление о внутренней работе языка и позволяют принимать более обоснованные решения относительно критически важных для производительности оптимизаций.При этом не забывайте, что существует множество других языков, таких как современный C ++, Rust, Go и даже Java, которые будут быстрее, чем даже наиболее оптимизированный код Python (обычно), и обеспечат достаточно интуитивно понятные интерфейсы. Таким образом, рассматривайте то, что вы только что узнали, как полезные знания для редких крайних случаев и забавные мелочи, о которых можно поговорить с бабушкой за воскресным чаем, но не как приглашение разобрать всю вашу кодовую базу и оптимизировать отдельные инструкции.

Ресурсы

Вот еще несколько ресурсов и ссылок, в которых я черпал вдохновение и знания:

Что понимается под разборкой и разборкой труднодоступных Apple AirPods?

Беспроводные наушники Apple объявили вместе с iPhone 7/7 Plus о прекращении производства разъема для наушников « AirPods ». Появился в интернет-магазине 14 декабря 2016 г. настоящий момент Подождите 6 недель Это продукт, которому уделяется повышенное внимание.Отчет о том, что ценные AirPods были куплены и разобраны в спешке, был выпущен iFixit.

AirPods Teardown - iFixit
https://www.ifixit.com/Teardown/AirPods+Teardown/75578

AirPods разделен на две части: беспроводной наушник и специальный чехол.

Сначала разложим беспроводной наушник.

Поскольку пластиковый корпус снаружи беспроводного наушника не имеет стыковочной поверхности, мы разрезаем его ножом и разбираем. Перед резкой сниму AirPods рентгеном и проверю внутри.

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

Режьте ножом в тепле.

Сделав надрез по окружности ножом, можно вскрыть киркой.

Внутри есть мелкие детали, такие как фундамент и кабель.

Обрежьте нижнюю часть наушника ножом, чтобы вынуть внутренние части.Металлическая часть, прикрепленная к нижней части наушника, является частью, которая становится точкой контакта с аккумулятором, когда помещается в специальный футляр.

Поскольку он затвердел с помощью клея, удалить металлические части было довольно сложно. Внутри есть аккумулятор.

Затем я отрежу длинную часть, которая становится туловищем AirPods по вертикали.

Осторожно разрежьте пластиковый корпус пинцетом ... ...

Удачно вытащив антенну и аккумулятор.

Аккумулятор, установленный в беспроводных наушниках, составляет 93 мВтч, что составляет около 1% емкости аккумулятора iPhone 7.

Далее мы снимем плату, кабели и т. Д.

Красная часть изображения представляет собой микросхему W1 производства Apple, которая улучшает соединение Bluetooth и качество звука. Оранжевая часть - аудиокодек Maxim Integrated « 98730 EWJ » Желтая часть - PSoC Cypress Semiconductor CY8C4146FN Должно быть. На этом разложение беспроводных наушников закончено.

Далее следует разбирать особый кейс. Как и в случае с беспроводным наушником, специальный футляр также разрезается ножом и разбирается.

При снятии верхней части спецкейса вылез ленточный кабель.

Светодиодный индикатор для уведомления о сопряжении, который был прикреплен к концу ленточного кабеля.

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

Аккумулятор подключается к плате на корпусной части.

То, что было установлено, было литий-ионным аккумулятором китайского производства с 1,52 Втч при 3,81 В.

Удалите кнопки сопряжения, установленные на задней панели ... ...

Удалите очень маленький винт и отделите разъем молнии.

Основная плата закреплена на герметичность лентой, и ее трудно снять.

На удаленной основной плате микросхема зарядки аккумулятора NXP «1610 A 3» была помещена в оранжевую часть, ST «Micro Electroics» STM32L072 «В желтой части - ARM произвела» Cortex-M 0 + процессор «Был установлен на рынке.

На этом разборка-разборка завершена. Сложность ремонта, оцененная по разборке, составила «0 баллов» наивысшей сложности из 10 баллов (10 баллов - самая легкая). Наивысшая оценка сложности ремонта: «Невозможно отремонтировать интерьер, не разрушив корпус». Когда AirPods ломается, неплохо было бы принести их в Apple Store, не ремонтируя их самостоятельно.

Изучение K240: руководство по разборке и анализу

Здесь я документирую процесс, с помощью которого я разбираю и анализирую K240 исполняемый файл. Вы можете найти эту информацию для разборки других игр Amiga, а также другие игры, в которых использовался процессор 68000, особенно Atari ST и Игры Megadrive / Genesis.

Разборка

Дизассемблирование - это процесс, который разбирает исполняемую программу и превращает ее в серию инструкций на языке ассемблера, которые можно проанализировать, если вы знаю 68к асм.

Основной инструмент - неинтерактивный дизассемблер под названием IRA. (Прочти меня скачать). Он все еще находится в активной разработке и может быть скомпилирован для работы в Linux (или любой другой платформа с инструментами GNU), Windows и MacOSX, с включенными двоичными файлами для работы на AmigaOS 2+, OS4 и MorphOS.

Disassembly с IRA имеет ту проблему, что он не знает автоматически, что исполняемый код и что такое данные. Сначала вы хотите запустить его с помощью -PREPROC функция, которая угадывает границы кода / данных и генерирует файл конфигурации для использовать в будущих разборках.

  ira -A -KEEPZH -NEWSTYLE -COMPAT = bi -PREPROC playk240
  

Однако предположения IRA иногда ошибаются, поэтому вам придется вручную отредактируйте файл .cnf, чтобы исправить это. Все последующие разборки теперь должны используйте файл конфигурации вместо препроцессора:

  ira -A -KEEPZH -NEWSTYLE -COMPAT = bi -CONFIG playk240
  

Определение того, что такое код, а что такое данные, требует некоторого технического чутья.Сравните предварительно обработанный файл с необработанным. В предварительно обработанном asm, если вы видите множество записей данных (например, DC.L ) с метками внутри них и шестнадцатеричный числа, которые представляют собой общие инструкции 68k asm (например, 4e75 ), то есть вероятно, код ошибочно идентифицирован как данные. И наоборот, в необработанном asm (т.е. ни -PREPROC, ни -CONFIG), если вы видите много строк DC.L в середина кода, или ORI # 0 (0000 долларов в шестнадцатеричном формате интерпретируется как инструкция), то есть вероятно, данные неправильно интерпретируются как код. Также часто декларации EXT_ предлагать данные, неверно интерпретированные как код.

Вам не нужен идеальный дизассемблер только для того, чтобы проанализировать и понять код, но чем ближе ты подойдешь, тем лучше. Это помогает, если вы собираетесь модифицировать nad пересборка программы.

Есть и другие известные инструменты, но я не использовал их для этого. проект:

  • ReSource v6.06 - коммерческий интерактивный дизассемблер на Amiga с 1994 года. Он больше не обновляется и его трудно достать, но многие программисты Amiga используют это как их основной дизассемблер.Также есть Ресурсное руководство.
  • IDA Pro - популярный интерактивный дизассемблер, поддерживающий множество платформ. Однако это коммерческое и дорогой. Есть бесплатная версия, но она не поддерживает 68000.
  • Ghidra - интерактивный дизассемблер, написанный NSA и выпущен для широкой публики в 2019 году. Сторонняя надстройка называется ghidra_amiga_ldr позволяет загружать исполняемые файлы Amiga.
  • Radare2 - это бесплатный интерактивный дизассемблер с графическим интерфейсом под названием Cutter.Он поддерживает процессор 68000, но еще не имеет загрузчика файлов фрагментов Amiga.

Преимущество ReSource в том, что он автоматически пытается определить тип переменных и т. п. в зависимости от того, как на них ссылаются, и ставит перед ним префикс строка типа.

Обучение и справочная информация

Очевидно, чтобы понять дизассемблированный код, вам необходимо изучить 68000 ассемблер, если вы еще не знаете его. Самая коммерческая Amiga игры, включая K240, писались непосредственно на 68k ассемблере, а не на язык высокого уровня, такой как C или AMOS Basic, поэтому нет значимого способа «Декомпилировать» такие игры на язык более высокого уровня.

Некоторые полезные ресурсы включают:

Анализ

Разобраться в такой сложной игре, как K240, непросто. Во-первых, как и большинство Игры для Amiga, K240 изначально собирался без отладочных символов, что означает, что у вас нет имен переменных, с которыми можно было бы работать. Также нет комментариев, которые могут вам помочь.

Однако есть несколько подходов, которые могут помочь.

  • Попробуйте найти текстовые строки. Здесь может помочь опция IRA -TEXT = 1 .
  • Попробуйте найти известные числа или их шестнадцатеричные эквиваленты.Примеры включают известные цены на корабли и начальную стоимость валюты.
  • Во многих играх Amiga использовался генератор случайных чисел, засеянный из текущего положение луча ЭЛТ на экране. Ищите ссылки на ВХПОСР ($ DFF006) или, возможно, VPOSR ($ DFF004). Случайность, как правило, используется больше в стратегические игры и ролевые игры, поэтому, как только вы определите генератор случайных целых чисел функцию, вы можете использовать это, чтобы сосредоточиться на коде, определяющем правила игры.
  • Ссылки на четыре аудиоканала от AUD0LCH до AUD3LCH (от $ DFF0A0 до $ DFF0D0) может идентифицировать события, вызывающие звук, например удары или повреждение.
  • Ссылки на вызовы библиотеки Amiga из игр, совместимых с ОС, могут определить полезные информация, в частности, чтение / запись файлов в dos. library . JSR (-552, Ан) это открытая библиотека, и с dos.library мы можем искать `JSR` до -30 (открытый), -42 (чтение) и -48 (запись). В частности, области памяти, записанные в файл сохранения игры обычно содержит все состояние игры.

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

В IRA есть два основных подхода к этому. Один из способов - просто найти-заменить все варианты использования этого имени метки в файле дизассемблирования (.asm) и добавлять комментарии напрямую. Это просто, но иногда вызывает ошибки, например когда вы по ошибке даете двум ярлыкам одно и то же имя.

Другой способ в текущей версии IRA - добавить названия ярлыков и комментарии. в файл .cnf, затем повторно запустите IRA, чтобы добавить их в .asm. Это помогает избежать баги, и позволяет повторно разобрать (например,г. для исправления ошибок границы кода / данных) без потери прогресса. Имена ярлыков и комментарии добавляются в .cnf с директивами SYBOL, COMMENT и BANNER (см. ira_config.doc ). Другой полезные директивы включают TEXT, JMPB, JMPW, JMPL и EQU.

Повторная сборка

Способ проверить, какие части кода делают, - это изменить код и перестроить его. снова в рабочую программу.

Идеальный инструмент для этого - VASM, ассемблер, который все еще активен. разработка и позволит вам компилировать исполняемые файлы Amiga.

Чтобы разобрать так, чтобы VASM правильно перестроил, Ридми IRA предлагает сначала разобрать со следующими вариантами:

-compat = bi
Включает два флага совместимости, которые помогают обеспечить правильную сборку программ. изначально собрана некоторыми сборщиками. -кипж
Сохранять нулевые фрагменты, то есть пустые разделы. K240 это не нужно, но некоторые программы может использовать это.

Для повторной сборки документы IRA рекомендуют:

  vasmm68k_mot -no-opt -Fhunkexe -nosym -O playk240 playk240. 68k.asm
  

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

Параметр -nosym отключает символы отладки. Если вы его не укажете, vasm построит все ваши имена ярлыков в исполняемый файл, что увеличит размер файла. Однако, если вы используете отладчик WinUAE для просмотра кода, вам действительно нужно чтобы исключить эту опцию, потому что вы хотите, чтобы имена меток не менялись.

Отладка

Еще один полезный инструмент для понимания программного обеспечения Amiga - отладчик WinUAE, что позволит вам пройти через программу. Нажмите Shift + F12, чтобы приостановить эмулятор. и загрузите отладчик, затем введите команду «h», чтобы получить список команд. Возможно, вам потребуется включить отладчик в параметрах.

Некоторые советы по отладке сборки в WinUAE есть в эта ветка.

Вы также можете экспортировать состояние сохранения (сначала включить несжатые состояния сохранения) и анализировать или заклинать редактировать это.

Анализ других файлов

Maptapper - это инструмент для копирования Amiga. графика из дампа памяти. Настройте WinUAE для создания только несжатого сохранения заявляет, и убедитесь, что ваша эмулируемая Amiga не имитирует больше оперативной памяти, чем необходимо, так что у вас не будет много места для поиска.

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

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


«Вернуться на главную страницу

ПЕРСОНАЖЕЙ РАЗОБРАННО !: ПОНИМАНИЕ, КАК ФАНАТЫ ИНТЕРПРЕТАЮТ ПЕРСОНАЖИ ИЗ TRANSMEDIA SUPERHERO FRANCHISES

Используйте этот идентификатор для цитирования или ссылки на этот элемент: https: // scholarbank.nus.edu.sg/handle/10635/165234

Title: ПЕРСОНАЖИ РАЗОБРАННЫ !: ПОНИМАНИЕ, КАК ФАНАТЫ ИНТЕРПРЕТАЮТ ПЕРСОНАЖИ С ФРАНЧАЙЗЫ TRANSMEDIA SUPERHERO Авторы: GAN JING YING Ключевые слова: супергерой
создание смысла
трансмедиа
когнитивная нарратология
карта значения
Дата выдачи: 18 апреля 2019 г. Образец цитирования: GAN JING YING (18.04.2019).ПЕРСОНАЖИ РАЗОБРАННЫ !: ПОНИМАНИЕ, КАК ФАНАТЫ ИНТЕРПРЕТАЮТ ПЕРСОНАЖИ С ФРАНЧАЙЗ TRANSMEDIA SUPERHERO. Репозиторий ScholarBank @ NUS. Abstract: Франшиза о супергероях трансмедиа представляет своим читателям несколько версий одного и того же персонажа в разных повествовательных цепочках в разных текстах и ​​медиа. С таким большим количеством разных вариантов одного и того же персонажа, как фанаты концептуализируют своих любимых персонажей и что влияет на эти личные конструкции персонажей? Это исследование показало, что через двенадцать полуструктурированных глубинных интервью с самопровозглашенными фанатами их характеры в высшей степени индивидуализированы, подвижны и неотделимы от личных предпочтений и опыта фанатов.Эти конструкции персонажей состоят из черт, событий и связанных с ними повторяющихся символов и могут быть изменены, когда фанат встречает новую информацию посредством перечитывания, личного опыта или достаточно мощного текста. Поклонники также могут когнитивно управлять несколькими различными версиями своего любимого персонажа, сохраняя при этом конструкции своих персонажей отдельно от них. Опираясь на карту материи Эллин Кашак, я предполагаю, что конструкция персонажа веера является продуктом взаимодействий между различными материями на карте материи человека, что приводит к построению персонажа, неотделимому от личности фаната. URI: https://scholarbank.nus.edu.sg/handle/10635/165234
Встречается в коллекциях: Бакалаврские диссертации

Показать всю запись об элементе

Файлы в этом элементе:

Файл Описание Размер Формат Настройки доступа Версия
GAN JING YING.pdf653.02 kB Adobe PDF

ЗАПРЕЩЕНО

Нет Войти

Элементы в DSpace защищены авторским правом, все права защищены, если не указано иное.

Дезагрегация белков молекулярными шаперонами

Темная сторона сворачивания белков. Неправильная укладка и агрегация основных клеточных белков - фундаментальная проблема для всех живых организмов.Агрегация даже несущественных белков может привести к изнурительным заболеваниям, таким как диабет II типа, болезнь Альцгеймера, Хантингтона и Паркинсона. Важно отметить, что на сворачивание и агрегацию белков сильно влияет механизм контроля качества клеточных белков, включающий сети молекулярных шаперонов. Не совсем понятно, как именно различные системы шаперонов взаимодействуют для разборки и реактивации агрегированных белков и как действие молекулярных шаперонов влияет на прогрессирование заболевания.

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

Мы используем различные подходы для изучения дезагрегазных систем как бактерий, так и дрожжей. В одном подходе мы используем флуоресцентно меченые, склонные к агрегации белки, такие как RuBisCO, с измерениями BAS одной частицы для измерения популяционной кинетики разборки белковых агрегатов молекулярными шаперонами.

Как молекулярные шаперонные сети разбирают белковые агрегаты? Интересно, что разные организмы организуют свой аппарат дезагрегации белков по-разному, причем большинство бактерий, многие одноклеточные эукариоты и растения используют основную бикаперонную сеть, состоящую из системы Hsp70, связанной с системой Hsp100.Напротив, высшие эукариоты, по-видимому, используют исключительно дезагрегазную систему на основе Hsp70. Не совсем понятно, как именно эти системы молекулярных шаперонов взаимодействуют для разрушения белковых агрегатов.

В качестве примера того, что может делать система Hsp70, мы исследовали разборку флуоресцентных клатриновых оболочек дрожжевой системой молекулярных шаперонов Hsc70, состоящей из Ssa1p и Swa2p. Клатриновые оболочки - это большие белковые каркасы, которые используются для создания эндоцитарных пузырьков. Они представляют собой идеальную систему для проверки способности БАВ количественно определять разборку наночастиц.Анализ поведения распада больших флуоресцентных вспышек показывает, что процесс разборки покрытия очень кооперативен. См. JBC, 288: 26721 (2013).

Произошла ошибка при установке пользовательского файла cookie

Этот сайт использует файлы cookie для повышения производительности. Если ваш браузер не принимает файлы cookie, вы не можете просматривать этот сайт.


Настройка вашего браузера для приема файлов cookie

Существует множество причин, по которым cookie не может быть установлен правильно. Ниже приведены наиболее частые причины:

  • В вашем браузере отключены файлы cookie. Вам необходимо сбросить настройки своего браузера, чтобы он принимал файлы cookie, или чтобы спросить вас, хотите ли вы принимать файлы cookie.
  • Ваш браузер спрашивает вас, хотите ли вы принимать файлы cookie, и вы отказались. Чтобы принять файлы cookie с этого сайта, нажмите кнопку «Назад» и примите файлы cookie.
  • Ваш браузер не поддерживает файлы cookie. Если вы подозреваете это, попробуйте другой браузер.
  • Дата на вашем компьютере в прошлом.Если часы вашего компьютера показывают дату до 1 января 1970 г., браузер автоматически забудет файл cookie. Чтобы исправить это, установите правильное время и дату на своем компьютере.
  • Вы установили приложение, которое отслеживает или блокирует установку файлов cookie. Вы должны отключить приложение при входе в систему или проконсультироваться с системным администратором.

Почему этому сайту требуются файлы cookie?

Этот сайт использует файлы cookie для повышения производительности, запоминая, что вы вошли в систему, когда переходите со страницы на страницу.Чтобы предоставить доступ без файлов cookie потребует, чтобы сайт создавал новый сеанс для каждой посещаемой страницы, что замедляет работу системы до неприемлемого уровня.


Что сохраняется в файле cookie?

Этот сайт не хранит ничего, кроме автоматически сгенерированного идентификатора сеанса в cookie; никакая другая информация не фиксируется.

Как правило, в cookie-файлах может храниться только информация, которую вы предоставляете, или выбор, который вы делаете при посещении веб-сайта.Например, сайт не может определить ваше имя электронной почты, пока вы не введете его. Разрешение веб-сайту создавать файлы cookie не дает этому или любому другому сайту доступа к остальной части вашего компьютера, и только сайт, который создал файл cookie, может его прочитать.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *