فهرست مقاله
۱. دو جایگاهِ خواندن، یک منبع

فرض کنید دو مرحلهٔ بررسیِ دادهٔ مهندسی باید یک دنباله را با سرعتِ مصرفِ متفاوت بخوانند. اگر فقط دو نام به همان تکرارگر بدهیم، دو جایگاهِ مستقل ایجاد نشده است: درخواستِ مرحلهٔ دوم ادامهٔ همان جایگاهِ مشترک را میگیرد. در این درس، itertools.tee واقعی را رویِ چهار مقدارِ تألیفیِ ۱۰۰۰، ۱۰۱۰، ۹۹۰ و ۱۰۲۰ میلیولت اجرا میکنیم. اینها اندازهگیریِ دستگاه نیستند؛ ترتیبِ درخواستها نیز برنامهٔ صریحِ آزمایش است، نه زمانِ واقعیِ اجرای دو کارگر.
قراردادِ رسمیِ tee از یک ورودی، تکرارگرهایِ دارای پیشرویِ مستقل میسازد. استقلال به جایگاهِ خواندن مربوط است؛ دو دریافتِ مستقل از حسگر یا دو اجرایِ دوبارهٔ منبع نداریم. اگر مصرفکنندهٔ صفر دو مقدار بخواند، مصرفکنندهٔ یک هنوز میتواند نخستین مقدار را بگیرد. دادهٔ لازم برای این خواندنِ عقبتر، از مقادیری است که قبلاً از منبع آمدهاند.
در عمرِ گروههایِ groupby، جلو رفتنِ تکرارگرِ بیرونی میتوانست گروهِ قبلی را غیرقابلمصرف کند. اینجا مسئله متفاوت است: دو مصرفکنندهٔ زنده باید هر دو ترتیبِ کامل را نگه دارند. فقط محدود بودنِ منبع یا تنبل بودنِ کد، این قرارداد را ثابت نمیکند؛ جایگاهها، مقدارهایِ تحویلشده و اثرِ درخواست رویِ منبع را جدا مشاهده میکنیم.
اندیسِ مصرفکننده هویتِ دستگاه یا شمارهٔ کانال نیست. مقدارها واحدِ میلیولت دارند، اما جایگاه، عقبماندگی و شمارِ درخواست، شمارِ بیبُعدِ رکوردند. در دادهٔ این تمرین هیچ برچسبِ زمانی نداریم؛ فاصلهٔ دو جایگاه را نمیتوان میلیثانیه نامید یا از آن تأخیرِ واقعیِ یک مسیرِ پردازش را استخراج کرد.
۲. برداشتِ موفق را از درخواست جدا کنیم

دو جایگاهِ موفق را p₀ و p₁ مینامیم؛ هرکدام شمارِ مقدارهایِ تحویلشده به همان مصرفکننده است. برای منبعِ سالمِ این آزمایش و بدونِ خواندنِ بیرونی، شمارِ برداشتِ موفق از منبع برابرِ بیشینهٔ این دو جایگاه است. دلیل روشن است: درخواستِ مصرفکنندهٔ عقبتر میتواند مقدارِ موجود را بگیرد؛ درخواستِ مصرفکنندهای که به انتهایِ پیشوندِ مشترک رسیده، برای تحویلِ مقدارِ تازه باید یک برداشتِ تازه انجام دهد. توقفِ انتهایِ منبع، مقدارِ تازهای نمیسازد.
برنامهٔ درخواستِ نمونه صفر، صفر، یک، صفر، یک، یک، یک، صفر است. خروجیِ هشت درخواست بهترتیب ۱۰۰۰، ۱۰۱۰، ۱۰۰۰، ۹۹۰، ۱۰۱۰، ۹۹۰، ۱۰۲۰ و ۱۰۲۰ میشود. پس از درخواستِ سوم، جایگاهها دو و یکاند، اما منبع فقط دو مقدار تحویل داده است. در پایان، هر دو جایگاه چهار و برداشتِ موفقِ منبع چهار است؛ هشت خروجیِ مصرفکننده را هشت دریافتِ منبع نمیشماریم.
این رابطه دربارهٔ برداشتِ موفق است، نه تمامِ فراخوانیهایِ next. پروتکلِ تکرارگرِ پایتون میخواهد پس از StopIteration، درخواستهایِ بعدی هم پایان بدهند؛ نمیگوید متدِ منبع دیگر هرگز فراخوانی نمیشود. در شاهدِ تکمقداریِ ما، مصرفکنندهٔ جلوتر پایان میبیند و عقبتر هنوز مقدارِ ذخیرهشده را میگیرد. یک برداشتِ موفق و چهار تلاشِ منبع ثبت میشود. این شمارِ تلاش، مشاهدهٔ CPython 3.14.5 است، نه وعدهٔ عمومیِ همهٔ پیادهسازیها.
۳. عقبماندگیِ منطقی، اندازهٔ حافظه نیست

اختلافِ بیشینه و کمینهٔ جایگاهها را عقبماندگیِ منطقی تعریف میکنیم: تعدادِ مقدارهایِ پیشوندِ گرفتهشده که هنوز به هر دو مصرفکننده تحویل نشدهاند. در نمونه، این اختلاف بهترتیب یک، دو، یک، دو، یک، صفر، یک و صفر است. اگر یک مصرفکننده همهٔ ۳۲ مقدارِ مجازِ مثال را پیش از دیگری بخواند، اختلاف ۳۲ میشود؛ با رسیدنِ دومی به انتها، دوباره صفر میشود. خواندنِ نوبتیِ یکیدرمیان، اختلاف را در همین برنامه حداکثر یک نگه میدارد.
این عدد، بایتِ تخصیصیافته یا شمارِ دقیقِ شیءهایِ باقیمانده در حافظه نیست. ساختارهایِ داخلی میتوانند قطعه و فضایِ نگهداریِ اضافی داشته باشند؛ صفر شدنِ اختلاف بهتنهایی زمانِ آزاد شدنِ حافظه را اثبات نمیکند. مستنداتِ tee نیازِ احتمالی به فضایِ کمکی را گوشزد میکند. بخشِ tee در منبعِ ثابتِ CPython نیز دادهٔ مشترک و اندیسِ هر مصرفکننده را نشان میدهد؛ این خواندن، اندازهگیریِ تخصیصگر یا بررسیِ تمامِ مفسر نیست.
دو مصرفکننده مرجعِ همان شیء را میگیرند، نه کپیِ عمیق. در شاهدِ جدا، مصرفکنندهٔ نخست فهرستِ هفت را به هفت و هشت تغییر میدهد و دومی همان شیء را میبیند؛ منبع یک بار آن را تحویل داده است. رابطِ اصلیِ درس فقط عددهایِ صحیحِ تغییرناپذیر میپذیرد؛ شاهدِ فهرست، آزمایشِ جداگانهٔ معنای اشتراک است. همچنین اگر کسی اصلِ تکرارگر را بیرون از tee مصرف کند، میتواند دادهای را از مسیر بردارد؛ این استفاده بیرونِ فرضِ دو جایگاهِ ماست.
هر دو مصرفکننده در تمامِ برنامهٔ درخواست زنده میمانند و منبع فقط پایانِ عادی دارد. حذفِ مصرفکننده، استثنایِ دیگری در منبع، منبعِ بیپایان یا دستکاریِ بیرونی، قراردادِ آزمایشِ تازه میخواهد. عقب ماندنِ یک مرحله را اینجا مشاهده میکنیم؛ tee خودش سیاستِ محدودسازیِ این اختلاف یا فشارِ برگشتیِ سامانه را طراحی نمیکند.
۴. کدِ کامل و مرجعِ مستقلِ جایگاهها

کدِ کامل را در فایلِ tee_consumer_lag.py ذخیره و با Python اجرا کنید. ورودیِ داده تاپلِ دقیقِ حداکثر ۳۲ عددِ صحیح با قدرمطلقِ حداکثر دههزار میلیولت است. برنامهٔ درخواست نیز تاپلِ حداکثرِ ۱۲۸ اندیسِ صحیحِ صفر یا یک است. bool، زیرکلاس، شناور، NaN، رشته و نوعِ ظرفِ اشتباه پذیرفته نمیشوند. همهٔ این کنترلها پیش از ساختنِ منبع انجام میشوند؛ این سقفها قراردادِ آموزشیِ ما هستند و به خودِ tee نسبت داده نمیشوند.
"""Actual stdlib tee, two live cursors and bounded synthetic integer mV data."""
from itertools import tee, product
import json
import platform
class ObservedSource:
"""Authored finite source: count attempts separately from returned items."""
def __init__(self, rows):
self.rows = rows
self.position = 0
self.attempts = 0
self.successes = 0
def __iter__(self):
return self
def __next__(self):
self.attempts += 1
if self.position == len(self.rows):
raise StopIteration
value = self.rows[self.position]
self.position += 1
self.successes += 1
return value
def trace_consumers(rows_mv, schedule):
if type(rows_mv) is not tuple or len(rows_mv) > 32:
raise ValueError('rows_tuple_cap')
if any(type(x) is not int or not -10000 <= x <= 10000 for x in rows_mv):
raise ValueError('row_integer_mv')
if type(schedule) is not tuple or len(schedule) > 128:
raise ValueError('schedule_tuple_cap')
if any(type(x) is not int or x not in (0, 1) for x in schedule):
raise ValueError('consumer_index')
source = ObservedSource(rows_mv)
consumers = tee(source, 2)
before = (source.successes, source.attempts)
positions = [0, 0]
events = []
for who in schedule:
try:
value = next(consumers[who])
except StopIteration:
status, value = 'stop', None
else:
status = 'value'
positions[who] += 1
events.append({'consumer': who, 'status': status, 'value_mv': value,
'positions': tuple(positions), 'source_successes': source.successes,
'logical_lag': max(positions) - min(positions),
'source_next_attempts_observed': source.attempts})
return {'before_requests': before, 'events': events,
'source_successes': source.successes,
'source_next_attempts_observed': source.attempts,
'successful_consumer_positions': tuple(positions),
'logical_lag': max(positions) - min(positions)}
def prefix_oracle(rows, schedule):
"""Valid authored fixtures only; materialized prefixes, no tee or next."""
result = []
for end, who in enumerate(schedule, 1):
prefix = schedule[:end]
requested = prefix.count(who)
positions = tuple(min(prefix.count(i), len(rows)) for i in (0, 1))
success = requested <= len(rows)
result.append({'consumer': who, 'status': 'value' if success else 'stop',
'value_mv': rows[requested - 1] if success else None,
'positions': positions, 'source_successes': max(positions),
'logical_lag': max(positions) - min(positions)})
return result
def logical_events(report):
return [{k: v for k, v in event.items() if k != 'source_next_attempts_observed'}
for event in report['events']]
def rejects(call, message):
try:
call()
except ValueError as exc:
return str(exc) == message
return False
def run():
checks = {}
def check(name, condition):
assert type(condition) is bool and condition, name
checks[name] = condition
rows = (1000, 1010, 990, 1020)
schedule = (0, 0, 1, 0, 1, 1, 1, 0)
report = trace_consumers(rows, schedule)
check('literal_interleaving_preserves_both_sequences_with_four_source_successes',
tuple(x['value_mv'] for x in report['events']) == (1000, 1010, 1000, 990, 1010, 990, 1020, 1020)
and tuple(x['logical_lag'] for x in report['events']) == (1, 2, 1, 2, 1, 0, 1, 0)
and report['source_successes'] == 4 and report['successful_consumer_positions'] == (4, 4))
check('tee_construction_does_not_request_an_item_from_this_source', report['before_requests'] == (0, 0))
cases = 0
for n in range(4):
fixture = tuple(10 + i for i in range(n))
for size in range(7):
for pattern in product((0, 1), repeat=size):
assert logical_events(trace_consumers(fixture, pattern)) == prefix_oracle(fixture, pattern)
cases += 1
check('508_bounded_schedules_match_independent_materialized_prefix_oracle_one_family', cases == 508)
alias_source = iter(rows)
aliases = (alias_source, alias_source)
first_alias, second_alias = next(aliases[0]), next(aliases[1])
check('two_aliases_to_one_original_iterator_are_not_two_tee_cursors',
(first_alias, second_alias) == (1000, 1010)
and report['events'][2]['value_mv'] == 1000)
mutable = [7]
shared_source = ObservedSource((mutable,))
left, right = tee(shared_source)
a = next(left)
a.append(8)
b = next(right)
same_shared_object = a is b is mutable
check('cached_items_are_shared_references_not_deep_copies_or_second_source_evaluations',
same_shared_object and b == [7, 8] and shared_source.successes == 1)
original = iter((10, 20, 30))
a, b = tee(original)
first = next(a)
bypass = next(original)
replay = next(b)
following = next(a)
check('foreign_direct_source_consumption_breaks_the_declared_two_cursor_model',
(first, bypass, replay, following) == (10, 20, 10, 30))
full_rows = tuple(range(32))
skew = trace_consumers(full_rows, (0,) * 32 + (1,) * 32)
check('one_reader_can_create_32_logically_pending_values_then_catch_up',
skew['events'][31]['logical_lag'] == 32 and skew['logical_lag'] == 0
and skew['source_successes'] == 32 and skew['successful_consumer_positions'] == (32, 32))
lockstep = trace_consumers(full_rows, (0, 1) * 32)
check('alternating_readers_bound_logical_lag_by_one_not_memory_bytes',
max(x['logical_lag'] for x in lockstep['events']) == 1 and lockstep['logical_lag'] == 0
and lockstep['source_successes'] == 32)
end = trace_consumers((123,), (0, 0, 1, 1, 0))
check('slow_reader_replays_cached_value_after_leader_stop_and_exhaustion_stays_stopped',
tuple(x['status'] for x in end['events']) == ('value', 'stop', 'value', 'stop', 'stop')
and end['source_successes'] == 1 and end['successful_consumer_positions'] == (1, 1)
and end['source_next_attempts_observed'] > end['source_successes'] + 1)
empty = trace_consumers((), (0, 1, 0))
check('empty_data_empty_schedule_zero_and_duplicate_values_keep_distinct_positions',
all(x['status'] == 'stop' for x in empty['events']) and empty['source_successes'] == 0
and trace_consumers(rows, ())['events'] == []
and tuple(x['value_mv'] for x in trace_consumers((0, 0), (1, 1, 0, 0))['events']) == (0, 0, 0, 0))
check('unrequested_future_schedule_does_not_change_observed_prefix',
all(trace_consumers(rows, schedule[:cut])['events'] == report['events'][:cut]
for cut in range(len(schedule) + 1)))
class IntSubclass(int):
pass
bad_rows = (([1], 'rows_tuple_cap'), (tuple(range(33)), 'rows_tuple_cap'),
((True,), 'row_integer_mv'), ((1.0,), 'row_integer_mv'),
((float('nan'),), 'row_integer_mv'), ((float('inf'),), 'row_integer_mv'),
(('1',), 'row_integer_mv'), ((IntSubclass(1),), 'row_integer_mv'),
((10001,), 'row_integer_mv'), ((-10001,), 'row_integer_mv'))
check('input_rows_exact_tuple_integer_mv_type_and_32_item_cap',
all(rejects(lambda xs=xs: trace_consumers(xs, ()), error) for xs, error in bad_rows)
and trace_consumers((-10000, 10000), (0, 0))['source_successes'] == 2)
bad_schedules = (([0], 'schedule_tuple_cap'), ((0,) * 129, 'schedule_tuple_cap'),
((True,), 'consumer_index'), ((0.0,), 'consumer_index'),
((IntSubclass(1),), 'consumer_index'), ((-1,), 'consumer_index'),
((2,), 'consumer_index'), (('0',), 'consumer_index'))
check('schedule_exact_tuple_two_consumer_indices_and_128_request_cap',
all(rejects(lambda pattern=pattern: trace_consumers(rows, pattern), error) for pattern, error in bad_schedules)
and len(trace_consumers((), (0,) * 128)['events']) == 128)
altered = trace_consumers(rows, schedule)
altered['events'][0]['value_mv'] = -9999
check('input_immutable_fixtures_and_fresh_reports_do_not_share_result_dicts',
rows == (1000, 1010, 990, 1020) and schedule == (0, 0, 1, 0, 1, 1, 1, 0)
and trace_consumers(rows, schedule) == report and report['events'][0]['value_mv'] == 1000)
assert len(checks) == 14
return {'python': platform.python_version(), 'implementation': platform.python_implementation(),
'dependencies': 'standard library only; actual itertools.tee executed',
'profile': 'two fixed live consumers; exacttuple0..32 integer mV values bounded +/-10000; exacttuple0..128 requests exactint0or1; source has no exceptions except stickyStopIteration and no outside consumption',
'main_rows_mv': rows, 'main_request_schedule': schedule, 'main': report,
'exhaustion_observation_current_implementation': end,
'maximum_skew_logical_lag_values': 32,
'oracle_schedule_cases_one_family': cases,
'shared_mutable_reference_demo': {'same_object': same_shared_object, 'note': 'See named check; direct labfixture separate from integer-only public API.'},
'checks': checks, 'unique_checks': len(checks), 'all_passed': all(checks.values()),
'limits': 'Logical lag is not allocated bytes or exact retained objects. No threads, GC, close/cleanup, performance, IO, live sensor or physical measurement. Exact failed-next attempt counts are implementation observations, not a portable tee API promise.'}
if __name__ == '__main__':
print(json.dumps(run(), ensure_ascii=True, indent=2))
مرجعِ مستقل بهجایِ tee، رویِ هر پیشوندِ برنامه تعدادِ درخواستِ هر مصرفکننده را میشمارد و آن را به طولِ داده محدود میکند. مقدارِ موردِ انتظار از اندیسِ تاپل میآید؛ تابعِ مرجع نه next را اجرا میکند، نه بافرِ tee را بازسازی میکند و نه خروجیِ تابعِ اصلی را مرجع میگیرد. برای طولِ دادهٔ صفر تا سه و همهٔ برنامههایِ صفر تا شش درخواست، ۵۰۸ سناریو داریم؛ همهٔ آنها فقط یک گروهِ آزموناند. مرجع، ورودیِ معتبرِ تألیفی فرض میکند و اعتبارسنجِ عمومی نیست.
اجرایِ واقعی با CPython 3.14.5 و کتابخانهٔ استاندارد، چهارده گروهِ موفق و خطایِ استانداردِ خالی دارد. نسخهٔ مستنداتِ خواندهشده 3.14.8 است؛ منبعِ پیادهسازی به بازبینیِ ثابتِ انتشارِ 3.14.5 متصل است. کتابخانهٔ نصبشده واقعاً اجرا شده، اما منبع را دوباره کامپایل نکردهایم و برابریِ تمامِ باینری با آن فایل را اثبات نمیکنیم. آزمایش تکنخ است و دربارهٔ ایمنیِ همزمانی، زمانِ اجرا، جمعآوریِ زباله یا بستنِ منبع نتیجهای ندارد.
{
"python": "3.14.5",
"implementation": "CPython",
"dependencies": "standard library only; actual itertools.tee executed",
"profile": "two fixed live consumers; exacttuple0..32 integer mV values bounded +/-10000; exacttuple0..128 requests exactint0or1; source has no exceptions except stickyStopIteration and no outside consumption",
"main_rows_mv": [
1000,
1010,
990,
1020
],
"main_request_schedule": [
0,
0,
1,
0,
1,
1,
1,
0
],
"main": {
"before_requests": [
0,
0
],
"events": [
{
"consumer": 0,
"status": "value",
"value_mv": 1000,
"positions": [
1,
0
],
"source_successes": 1,
"logical_lag": 1,
"source_next_attempts_observed": 1
},
{
"consumer": 0,
"status": "value",
"value_mv": 1010,
"positions": [
2,
0
],
"source_successes": 2,
"logical_lag": 2,
"source_next_attempts_observed": 2
},
{
"consumer": 1,
"status": "value",
"value_mv": 1000,
"positions": [
2,
1
],
"source_successes": 2,
"logical_lag": 1,
"source_next_attempts_observed": 2
},
{
"consumer": 0,
"status": "value",
"value_mv": 990,
"positions": [
3,
1
],
"source_successes": 3,
"logical_lag": 2,
"source_next_attempts_observed": 3
},
{
"consumer": 1,
"status": "value",
"value_mv": 1010,
"positions": [
3,
2
],
"source_successes": 3,
"logical_lag": 1,
"source_next_attempts_observed": 3
},
{
"consumer": 1,
"status": "value",
"value_mv": 990,
"positions": [
3,
3
],
"source_successes": 3,
"logical_lag": 0,
"source_next_attempts_observed": 3
},
{
"consumer": 1,
"status": "value",
"value_mv": 1020,
"positions": [
3,
4
],
"source_successes": 4,
"logical_lag": 1,
"source_next_attempts_observed": 4
},
{
"consumer": 0,
"status": "value",
"value_mv": 1020,
"positions": [
4,
4
],
"source_successes": 4,
"logical_lag": 0,
"source_next_attempts_observed": 4
}
],
"source_successes": 4,
"source_next_attempts_observed": 4,
"successful_consumer_positions": [
4,
4
],
"logical_lag": 0
},
"exhaustion_observation_current_implementation": {
"before_requests": [
0,
0
],
"events": [
{
"consumer": 0,
"status": "value",
"value_mv": 123,
"positions": [
1,
0
],
"source_successes": 1,
"logical_lag": 1,
"source_next_attempts_observed": 1
},
{
"consumer": 0,
"status": "stop",
"value_mv": null,
"positions": [
1,
0
],
"source_successes": 1,
"logical_lag": 1,
"source_next_attempts_observed": 2
},
{
"consumer": 1,
"status": "value",
"value_mv": 123,
"positions": [
1,
1
],
"source_successes": 1,
"logical_lag": 0,
"source_next_attempts_observed": 2
},
{
"consumer": 1,
"status": "stop",
"value_mv": null,
"positions": [
1,
1
],
"source_successes": 1,
"logical_lag": 0,
"source_next_attempts_observed": 3
},
{
"consumer": 0,
"status": "stop",
"value_mv": null,
"positions": [
1,
1
],
"source_successes": 1,
"logical_lag": 0,
"source_next_attempts_observed": 4
}
],
"source_successes": 1,
"source_next_attempts_observed": 4,
"successful_consumer_positions": [
1,
1
],
"logical_lag": 0
},
"maximum_skew_logical_lag_values": 32,
"oracle_schedule_cases_one_family": 508,
"shared_mutable_reference_demo": {
"same_object": true,
"note": "See named check; direct labfixture separate from integer-only public API."
},
"checks": {
"literal_interleaving_preserves_both_sequences_with_four_source_successes": true,
"tee_construction_does_not_request_an_item_from_this_source": true,
"508_bounded_schedules_match_independent_materialized_prefix_oracle_one_family": true,
"two_aliases_to_one_original_iterator_are_not_two_tee_cursors": true,
"cached_items_are_shared_references_not_deep_copies_or_second_source_evaluations": true,
"foreign_direct_source_consumption_breaks_the_declared_two_cursor_model": true,
"one_reader_can_create_32_logically_pending_values_then_catch_up": true,
"alternating_readers_bound_logical_lag_by_one_not_memory_bytes": true,
"slow_reader_replays_cached_value_after_leader_stop_and_exhaustion_stays_stopped": true,
"empty_data_empty_schedule_zero_and_duplicate_values_keep_distinct_positions": true,
"unrequested_future_schedule_does_not_change_observed_prefix": true,
"input_rows_exact_tuple_integer_mv_type_and_32_item_cap": true,
"schedule_exact_tuple_two_consumer_indices_and_128_request_cap": true,
"input_immutable_fixtures_and_fresh_reports_do_not_share_result_dicts": true
},
"unique_checks": 14,
"all_passed": true,
"limits": "Logical lag is not allocated bytes or exact retained objects. No threads, GC, close/cleanup, performance, IO, live sensor or physical measurement. Exact failed-next attempt counts are implementation observations, not a portable tee API promise."
}
۵. چهار پرسش دربارهٔ اشتراک و پایان

پرسشِ اول: پس از درخواستهایِ صفر، صفر و یک، چند برداشتِ موفق داریم؟ پاسخ: دو؛ جایگاهها دو و یکاند و مصرفکنندهٔ دوم، مقدارِ نخستِ ذخیرهشده را میگیرد. اختلافِ منطقی یک است؛ این پاسخ، اندازهٔ حافظه را مشخص نمیکند.
پرسشِ دوم: اگر مصرفکنندهٔ نخست پایان ببیند، دومی هم فوراً تمام است؟ پاسخ: لزوماً نه؛ ممکن است دومی هنوز دادهٔ گرفتهشده ولی تحویلنگرفته داشته باشد. در شاهدِ تکمقداری، نخستین خواندنِ دومی پس از پایانِ اولی، همان ۱۲۳ را برمیگرداند؛ پس از مصرفِ آن، پایان خواهد دید.
پرسشِ سوم: دو خروجی، دو نسخهٔ مستقل از فهرستاند؟ پاسخ: خیر؛ شاهدِ هفت و هشت، هویتِ همان شیء را نشان میدهد. پیشرویِ مستقل با جداسازیِ دادهٔ تغییرپذیر فرق دارد. اگر جداسازی لازم باشد، باید سیاستِ کپی و هزینهٔ آن جدا تعریف شود.
پرسشِ چهارم: میتوان شمارِ تلاشها را همیشه برابرِ بیشینهٔ جایگاهها گرفت؟ پاسخ: خیر؛ آن برابری برای تحویلِ موفق است. تلاشِ پایان ممکن است تکرار شود و مقدارِ موفقِ تازهای نسازد. درسِ مولد و آخرین دستهٔ حسگر پوششِ انتهایِ داده را بررسی میکرد؛ اینجا قراردادِ دو مصرفکننده و پیشوندِ مشترک سنجیده شد، نه بازپخشِ دادهٔ زنده یا دریافتِ دوبارهٔ گذشته.
منابع و مطالعهٔ بیشتر
- Python3.14.8؛ قرارداد کاملtee و فضای کمکی
- Python3.14.8؛ پروتکل تکرارگر و پایان پایدار
- CPython3.14.5؛ منبع ثابت بخشtee و داده مشترک
بررسی منابع: ۲۷ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

