All streams
Search
Write a publication
Pull to refresh
35
23.1
Sergey Miryanov @zzzzzzerg

User

Send message

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

Можно конкретную цитату? Строго говоря, конечно, функция – это именованное лямбда-выражение.

Сами уж поищите, вы ловко виляете - понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте.

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

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

Ну вообще то там написано, что замыкания захватывают значения свободных переменных. И этим я хочу опровергнуть ваше утверждение "Лексическое замыкание означает просто сохранение лексического контекста имён свободных переменных. Сохраняются имена, а не значения.". Вы могли бы и сами это узнать, если бы прочитали ссылки, которые я привел.

То есть именно то, что я написал: лямбда-выражение – это функция; её можно преобразовать в значение (экземпляр) типа (класса) Action. Что и делает ваш List<Action>.

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

но она может быть инкапсулирована в объект класса Action или Func. Это в точности именно то, о чём я пишу всё это время, и с чем (как я понял) Вы и ваш товарищ спорите.

Вы неумело жонглируете словами, с этим я, и мой коллега по опасному бизнесу, и спорим.

Ниже IL (intermediate language) кода на C# из примера выше. Как видно ни <>c__DisplayClass0_0 ни <>c__DisplayClass0_1 не расширяют Action, но являются функциональными объектами.

.class private auto ansi '<Module>'
{
} // end of class <Module>

.class public auto ansi beforefieldinit HelloWorld.Program
	extends [System.Runtime]System.Object
{
	// Nested Types
	.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_0'
		extends [System.Runtime]System.Object
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Fields
		.field public int32 idx

		// Methods
		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x210c
			// Code size 8 (0x8)
			.maxstack 8

			IL_0000: ldarg.0
			IL_0001: call instance void [System.Runtime]System.Object::.ctor()
			IL_0006: nop
			IL_0007: ret
		} // end of method '<>c__DisplayClass0_0'::.ctor

	} // end of class <>c__DisplayClass0_0

	.class nested private auto ansi sealed beforefieldinit '<>c__DisplayClass0_1'
		extends [System.Runtime]System.Object
	{
		.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
			01 00 00 00
		)
		// Fields
		.field public int32 idx_copy
		.field public class HelloWorld.Program/'<>c__DisplayClass0_0' 'CS$<>8__locals1'

		// Methods
		.method public hidebysig specialname rtspecialname 
			instance void .ctor () cil managed 
		{
			// Method begins at RVA 0x210c
			// Code size 8 (0x8)
			.maxstack 8

			IL_0000: ldarg.0
			IL_0001: call instance void [System.Runtime]System.Object::.ctor()
			IL_0006: nop
			IL_0007: ret
		} // end of method '<>c__DisplayClass0_1'::.ctor

		.method assembly hidebysig 
			instance void '<Main>b__0' () cil managed 
		{
			// Method begins at RVA 0x2118
			// Code size 69 (0x45)
			.maxstack 3
			.locals init (
				[0] valuetype [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler
			)

			IL_0000: ldloca.s 0
			IL_0002: ldc.i4.4
			IL_0003: ldc.i4.2
			IL_0004: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::.ctor(int32, int32)
			IL_0009: ldloca.s 0
			IL_000b: ldarg.0
			IL_000c: ldfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_0011: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_0016: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted<int32>(!!0)
			IL_001b: nop
			IL_001c: ldloca.s 0
			IL_001e: ldstr " -> "
			IL_0023: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendLiteral(string)
			IL_0028: nop
			IL_0029: ldloca.s 0
			IL_002b: ldarg.0
			IL_002c: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_1'::idx_copy
			IL_0031: call instance void [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::AppendFormatted<int32>(!!0)
			IL_0036: nop
			IL_0037: ldloca.s 0
			IL_0039: call instance string [System.Runtime]System.Runtime.CompilerServices.DefaultInterpolatedStringHandler::ToStringAndClear()
			IL_003e: call void [System.Console]System.Console::WriteLine(string)
			IL_0043: nop
			IL_0044: ret
		} // end of method '<>c__DisplayClass0_1'::'<Main>b__0'

	} // end of class <>c__DisplayClass0_1


	// Methods
	.method public hidebysig static 
		void Main (
			string[] args
		) cil managed 
	{
		// Method begins at RVA 0x2050
		// Code size 160 (0xa0)
		.maxstack 3
		.locals init (
			[0] class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>,
			[1] class HelloWorld.Program/'<>c__DisplayClass0_0',
			[2] class HelloWorld.Program/'<>c__DisplayClass0_1',
			[3] int32,
			[4] bool,
			[5] valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>,
			[6] class [System.Runtime]System.Action
		)

		IL_0000: nop
		IL_0001: newobj instance void class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::.ctor()
		IL_0006: stloc.0
		IL_0007: newobj instance void HelloWorld.Program/'<>c__DisplayClass0_0'::.ctor()
		IL_000c: stloc.1
		IL_000d: ldloc.1
		IL_000e: ldc.i4.0
		IL_000f: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
		IL_0014: br.s IL_0059
		// loop start (head: IL_0059)
			IL_0016: newobj instance void HelloWorld.Program/'<>c__DisplayClass0_1'::.ctor()
			IL_001b: stloc.2
			IL_001c: ldloc.2
			IL_001d: ldloc.1
			IL_001e: stfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_0023: nop
			IL_0024: ldloc.2
			IL_0025: ldloc.2
			IL_0026: ldfld class HelloWorld.Program/'<>c__DisplayClass0_0' HelloWorld.Program/'<>c__DisplayClass0_1'::'CS$<>8__locals1'
			IL_002b: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_0030: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_1'::idx_copy
			IL_0035: ldloc.0
			IL_0036: ldloc.2
			IL_0037: ldftn instance void HelloWorld.Program/'<>c__DisplayClass0_1'::'<Main>b__0'()
			IL_003d: newobj instance void [System.Runtime]System.Action::.ctor(object, native int)
			IL_0042: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::Add(!0)
			IL_0047: nop
			IL_0048: nop
			IL_0049: ldloc.1
			IL_004a: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_004f: stloc.3
			IL_0050: ldloc.1
			IL_0051: ldloc.3
			IL_0052: ldc.i4.1
			IL_0053: add
			IL_0054: stfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx

			IL_0059: ldloc.1
			IL_005a: ldfld int32 HelloWorld.Program/'<>c__DisplayClass0_0'::idx
			IL_005f: ldc.i4.s 10
			IL_0061: clt
			IL_0063: stloc.s 4
			IL_0065: ldloc.s 4
			IL_0067: brtrue.s IL_0016
		// end loop

		IL_0069: nop
		IL_006a: ldloc.0
		IL_006b: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<class [System.Runtime]System.Action>::GetEnumerator()
		IL_0070: stloc.s 5
		.try
		{
			IL_0072: br.s IL_0085
			// loop start (head: IL_0085)
				IL_0074: ldloca.s 5
				IL_0076: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>::get_Current()
				IL_007b: stloc.s 6
				IL_007d: ldloc.s 6
				IL_007f: callvirt instance void [System.Runtime]System.Action::Invoke()
				IL_0084: nop

				IL_0085: ldloca.s 5
				IL_0087: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>::MoveNext()
				IL_008c: brtrue.s IL_0074
			// end loop

			IL_008e: leave.s IL_009f
		} // end .try
		finally
		{
			IL_0090: ldloca.s 5
			IL_0092: constrained. valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<class [System.Runtime]System.Action>
			IL_0098: callvirt instance void [System.Runtime]System.IDisposable::Dispose()
			IL_009d: nop
			IL_009e: endfinally
		} // end handler

		IL_009f: ret
	} // end of method Program::Main

	.method public hidebysig specialname rtspecialname 
		instance void .ctor () cil managed 
	{
		// Method begins at RVA 0x210c
		// Code size 8 (0x8)
		.maxstack 8

		IL_0000: ldarg.0
		IL_0001: call instance void [System.Runtime]System.Object::.ctor()
		IL_0006: nop
		IL_0007: ret
	} // end of method Program::.ctor

} // end of class HelloWorld.Program

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

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

  1. function.__closure__

  2. codeobject.co_freevars

  3. closure-variable

  4. Python Scope & the LEGB Rule: Resolving Names in Your Code – Real Python

Как вы сказали выше "Охотно верю, но надо просвещать людей." - просвещайтесь пожалуйста.

По поводу вашего вопроса вам уже ответили - там глобальный скоп - там нет свободных переменных и они не захватываются - __code__.co_names и function.__globals__ вам в помощь.

Что касается C#, то, насколько я понимаю, функциональный объект создаётся именно приведением лямбда-выражения к объектному типу Action.

Вы неправильно понимаете. Почитать можно тут Lambda expressions - Lambda expressions and anonymous functions - C# reference | Microsoft Learn , тут Built-in reference types - C# reference | Microsoft Learn и тут https://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx

Фишка не в List<Action>, а в том что создается функциональный объект, со своей памятью. Впрочем так же происходит и в случае питона - там также создается функциональный объект со своей памятью. В моем примере можете добавить вывод print(id(l)) чтобы в этом убедиться. Либо сделать dis и также убедиться что вызывается MAKE_FUNCTION.

Если под отладчиком посмотрите содержимое лямбды (l), то там можете увидеть __closure__, который содержит ссылки на свободные (захваченные) переменные (в нашем случае только одну ссылку idx_copy), а в __code__.co_freevars названия захваченных переменных. И если посмотреть на id переменных, которые лежат в __closure__, то можно заметить, что все они соответствуют последней копии idx_copy.

Я немного модифицировал пример - добавил две переменные в замыкание, чтобы было проще смотреть, что попадает в захваченные переменные.

def create():
    lambdas = []
    idx_copy = 0
    for idx in range(10):
        idx_copy = idx + 1
        print ('-', id(idx), id(idx_copy))
        lambdas.append(lambda : print(idx_copy, idx))

    return lambdas

import dis 
dis.dis(create)

def test():
    for l in create():
        l()
        print (id(l), [(c.cell_contents, id(c.cell_contents)) for c in l.__closure__], l.__code__.co_freevars)

test()

А вот так можно сделать небольшую ошибку и сломать свою лямбду:

def create():
    lambdas = []
    idx_copy = 0
    for idx in range(10):
        idx_copy = idx + 1
        print ('-', id(idx), id(idx_copy))
        lambdas.append(lambda : print(idx_copy, idx))

    del idx_copy

    return lambdas

Причем здесь указатели на функции я простите не понял. Мы все таки вели разговор про замыкания.

Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.

Там оба захватываются - полно онлайн компиляторов - можно и посмотреть.

Вот вам два примера:

lambdas = []
for idx in range(10):
    idx_copy = idx
    lambdas.append(lambda : print(idx_copy))

for l in lambdas:
    l()
using System;
using System.Collections.Generic;


namespace HelloWorld
{
	public class Program
	{
		public static void Main(string[] args)
		{
		  var lambdas = new List<Action>();
		  
		  for (var idx = 0; idx < 10; idx++)
		  {
		    var idx_copy = idx;
		    lambdas.Add(() => Console.WriteLine($"{idx} -> {idx_copy}"));
		  }
		  
		  foreach(var l in lambdas)
		    l();

		}
	}
}

Выбрал C# потому что ранее вы говорили про сборку мусора.

Ну посчитайте сколько у вас лямбд внутри цикла.

Позднее связывания к этом относится напрямую.

Они работают ожидаемо, если люди понимают, что такое лексическое замыкание и позднее связывание. У нас на собесах 8 из 10 человек про это не знаю, а код такой писать иногда приходится (чуть сложнее, чем передать индекс в лямбду) и рецепты надо помнить. Ваше высказывание про логично/нелогично приводит меня к мысли, что вы с подобным не сталкивались. Люди вон считали это багами - Issue 13652: Creating lambda functions in a loop has unexpected results when resolving variables used as arguments - Python tracker .

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

Вы наверное что-то мне пытаетесь доказать, но я не понимаю зачем. Хотите поспорить - напишите автору (автор в отличие от нас с вами написал свою первую книгу по питону 10+ лет назад).

Значит про проблемы вы немного не в курсе.

Думаю, что автор имеет в виду проблемы с захватом переменных замыканиям в циклах в Python. В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться. Ну и надо делать ссылку на авторский стиль.

Я настраивал как здесь описано - Obsidian+Github вместо Notion: синхронизация, бекап и версионность (3-в-1) / Хабр, но в итоге на ios заменил fit на git (писал у себя в постах если интересно). Дополнительные приложения открывать не надо - obsidian и плагин git все делают сами.

Попробуйте что-то на основе flamegraph, он вроде как должен существенно снижать размер профиля (но у меня все равно получались профили по несколько Гб с примерно таким же результатом как у вас). У того же остина довольно простой формат, котороый можно фильтровать перед анализом "глазками".

Если вы говорите про трейсинг, то возможно вам будет интересно посмотреть на pyroscope, сам его я не использовал (у нас немного другое направление), но выглядит он интересно.

Также большинство указанных в статье инструментов (в разделе Другие инструменты) умеют подцепляться к запущенным приложениям и строить либо FlameGraph, либо выгружать данные в формате Google Trace Event.

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

Для питона есть, в статье есть несколько ссылок, которые точно умеют - это austin, py-spy, pyinstrument.

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

Все так с .csv - у них хороший парсер для csv - отсюда и результат. Т.е. дело не только в том что они используют mmap, но и в том что остальной код лучше.

Вот у вас есть реальный опыт pandas/polars, судя по вашим комментариям он гораздо интереснее статьи.

Алексей, расскажите, пожалуйста, что вы имеете в виду, когда пишите "Архитектура базируется на NumPy — это ограничивает обработку больших массивов данных, помещающихся в память одного потока."

Rust позволяет эффективно управлять памятью и гарантирует отсутствие состояния гонки. - это просто общие слова, которые не имеют отношения к делу.

Если вы противопостовляете pandas + Dask и polars, надо говорить не про "Polars же написан на Rust — языке программирования, который славится своей безопасностью и высокой производительностью." - а про наличие streaming API из коробки (которое появилось не само по себе).

А вот чтобы было более честное сравнение - "Данные хранятся в колонках, так что чтение и обработка становятся более эффективными." - вы не пробовали те же тесты запустить в pandas если выбрано PyArrow в качестве сравнения? Сюда же и про mmap - оно ведь тоже не на равном месте появляется. Попробуйте так же работать с CSV или XLSX форматами :)

Что касается API, то у Polars свой собственный DataFrame API (иммутабельный, в отличие от Pandas), а еще есть поддержка SQL. У DuckDB — только SQL. А DuckDB то каким образом к pandas относится?

А в абзаце про слияние и join у вас написано про фильтрацию.

В целом вы могли бы и лучше, но явно халтурите.

Information

Rating
317-th
Registered
Activity