فهرست مقاله
گاهی یک تابعِ مولد، درست پیش از اجرای بدنهٔ with تنظیمی را عوض میکند و پس از آن، مقدارِ قبلی را برمیگرداند. ظاهرِ کوتاهِ این کد میتواند دو خطای مهم را پنهان کند: مولد باید دقیقاً یکبار مقدار تحویل بدهد، و گرفتنِ خطای بدنه بدونِ دوبارهبالابردنِ آن ممکن است ادامهٔ برنامه را مجاز کند. این درس همین مرز را با contextmanager واقعی و تنظیماتِ کاملاً مصنوعی بررسی میکند؛ هیچ حسگر، فایل یا ارتباطی باز نمیشود.
۱. یک مولد، دو سوی مرزِ with

طبق قراردادِ رسمیِ contextmanager، تابعِ تزئینشده باید تکرارگرِ مولدی بسازد که دقیقاً یک مقدار بدهد. کدِ پیش از yield هنگامِ ورود اجرا میشود؛ مقدارِ تحویلشده به هدفِ as میرسد؛ سپس بدنهٔ with اجرا و مولد برای خروج از محدوده دوباره ادامه داده میشود. این مولد، فهرستی از نتیجههای پردازش نیست و نباید برای خروجِ عادی، مقدارِ دومی تولید کند.
در مثال، Calibration فقط یک شیء محلی با تنظیمِ جاری و دفترِ رویداد است. تنظیم، یک tuple دوعضوی شاملِ بهرهٔ بدونِ واحد و آفستِ میلیولتی است. بهره از یک تا چهار و آفست از منفی هشت تا هشت پذیرفته میشود؛ هر دو عددِ صحیحِ دقیقاند. این اعداد، حدودِ آموزشیِ خودماناند و مشخصاتِ سختافزار نیستند. هیچ تبدیلِ ولتاژ یا اندازهگیریِ کالیبراسیون از آنها گزارش نمیکنیم.
ساختنِ مدیر با temporary_calibration، بدنهٔ مولد را هنوز اجرا نمیکند. بنابراین اعتبارسنجیِ درونِ این تابع هنگامِ ورود رخ میدهد، نه هنگامِ گرفتنِ شیء مدیر. در مقابل، پوشانندهٔ سناریوی تودرتو ورودیِ کاملِ خودش را پیشاپیش بررسی میکند. تفاوتِ این دو زمانِ اعتبارسنجی بخشی از قراردادِ کد است؛ فراخوانیِ موفقِ کارخانه بهتنهایی شاهدِ معتبر بودنِ تنظیم نیست.
۲. خطای بدنه در نقطهٔ yield برمیگردد

اگر بدنه استثنا بدهد، contextmanager آن را در نقطهٔ تعلیقِ yield دوباره داخلِ مولد بالا میبرد. پس try پیرامونِ همان نقطه میتواند خطای بدنه را ببیند؛ خطا الزاماً از دستورهای پیش از تحویل نیامده است. مستندات صریحاند: اگر فقط برای ثبتِ رویداد یا اقدامِ جانبی خطا را گرفتهایم، باید آن را دوباره بالا ببریم، وگرنه مدیر اعلام میکند خطا رسیدگی شده است.
دو شاهدِ logged_scope دقیقاً همین اختلاف را نشان میدهند. شاخهٔ عادی، ProbeFailure ساختگی را ثبت و با raise بدونِ آرگومان دوباره بالا میبرد؛ مصرفکننده همان شیء استثنا را میگیرد. شاخهٔ عمداً معیوبِ swallow=True پس از ثبت، بیخطا پایان مییابد؛ دستورِ بعد از with اجرا میشود، با آنکه بدنه قبلاً شکست خورده است. این شاخه، نمونهٔ هشداردهندهٔ قابلآزمایش است و سیاستِ پیشنهادیِ مدیریتِ خطا نیست.
در مشخصاتِ خودِ دستورِ with نیز خروجِ عادی و خروجِ استثنایی جدا هستند: اطلاعاتِ خطای بدنه به خروج داده میشود و نتیجهٔ درستِ خروج میتواند آن را سرکوب کند. «پاکسازی شد» و «خطا عبور کرد» دو مشاهدهٔ جدا هستند. حتی شاخهٔ سرکوبکنندهٔ ما تنظیم را برمیگرداند؛ این بازگردانی، خطای پنهانشده را به موفقیتِ پردازش تبدیل نمیکند.
۳. بازگرداندنِ تنظیماتِ مصنوعی با finally

مدیرِ اصلی مقدارِ قبلیِ تنظیم را نگه میدارد، تنظیمِ تازه را فعال میکند و همان زوجِ تغییرناپذیر را تحویل میدهد. در finally، مقدارِ ذخیرهشده بازگردانده و رویدادِ بازگردانی ثبت میشود. حالتِ پایه یک و صفر است. در شاهدِ دستی، ورودِ بیرونی دو و سه، ورودِ درونی چهار و منفی یک است؛ خروجِ درونی باید دو و سه و خروجِ بیرونی باید یک و صفر را بازگرداند.
اگر پس از مشاهدهٔ تنظیمِ درونی، خطا تزریق کنیم، رویدادِ بدنهٔ نهایی نداریم، اما هر دو بازگردانی با همان ترتیب انجام میشوند. همین انتظار را با فهرستِ دستی مقایسه میکنیم. شاهدِ جدا یک ValueError و شاهدِ دیگر زیرکلاسی ساختگی از BaseException میسازند تا نشان دهند finally فقط به خطای نامگذاریشدهٔ سناریو وابسته نیست. این آزمون، معادلِ شبیهسازیِ قطعِ فرایند یا خرابیِ سیستم نیست.
مقدارِ as، تصویرِ همان تنظیم در زمانِ تحویل است؛ اشارهگری به «همیشه آخرین تنظیم» نیست. اگر بدنه بعداً ویژگیِ محلیِ current را به زوجِ دیگری نسبت دهد، زوجِ قبلی عوض نمیشود. خروج، مقدارِ پیش از ورود را بازمیگرداند. این رفتار برای شیء محلیِ تحتِ مالکیتِ سناریو تعریف شده است؛ دستکاریِ همزمانِ دیگران، تغییرِ تنظیمِ دستگاه، تراکنش و بازگرداندنِ اثرِ بیرونی در دامنهٔ آن نیست.
۴. کدِ کامل و مرجعِ مستقلِ پشتهٔ تنظیمات

کد فقط کتابخانهٔ استاندارد میخواهد. مسیرِ اصلی، مدیرهای تازه را با فراخوانیِ بازگشتی تودرتو وارد میکند. مرجعِ مستقل، برای ورودیهای معتبر، با حلقه و پشتهٔ صریحِ مقدارهای قبلی انتظار را میسازد؛ از with، مولد یا تابعِ اجرای سناریو استفاده نمیکند. سه تنظیمِ مشخص و عمقِ صفر تا سه، همراهِ حالتِ بیخطا و هر عمقِ ممکنِ شکست، جمعاً ۱۴۲ سناریو دارند؛ این خانواده فقط یک گروهِ بررسی است.
"""Actual synchronous contextmanager on bounded synthetic calibration settings."""
from contextlib import contextmanager
from itertools import product
import json
import platform
import sys
class ProbeFailure(Exception):
pass
class SyntheticAbort(BaseException):
pass
class CleanupFailure(Exception):
pass
def validate_setting(setting):
if type(setting) is not tuple or len(setting) != 2:
raise TypeError("exact gain/offset tuple required")
gain, offset_mv = setting
if type(gain) is not int or type(offset_mv) is not int:
raise TypeError("exact integers required")
if not 1 <= gain <= 4 or not -8 <= offset_mv <= 8:
raise ValueError("own synthetic setting bounds")
class Calibration:
"""An owned local object, not a device or a concurrent configuration store."""
def __init__(self, setting=(1, 0)):
validate_setting(setting)
self.current = setting
self.events = []
@contextmanager
def temporary_calibration(state, setting):
# This generator body starts on enter, not when the factory is called.
if type(state) is not Calibration:
raise TypeError("exact local Calibration required")
validate_setting(setting)
previous = state.current
state.events.append(["enter", list(setting)])
state.current = setting
try:
yield state.current
finally:
state.current = previous
state.events.append(["restore", list(previous)])
def observe_nested(settings, fail_at=None):
"""Own eager bounded scenario validation; one fresh manager per level."""
if type(settings) is not tuple:
raise TypeError("exact settings tuple required")
if len(settings) > 3:
raise ValueError("at most three nested scopes")
for setting in settings:
validate_setting(setting)
if fail_at is not None and (type(fail_at) is not int
or not 0 <= fail_at < len(settings)):
raise ValueError("failure depth must identify an entered scope")
state = Calibration()
failure = ProbeFailure("synthetic body failure")
caught = None
def visit(depth):
if depth == len(settings):
state.events.append(["body", list(state.current)])
return
with temporary_calibration(state, settings[depth]) as active:
state.events.append(["observe", depth, list(active)])
if fail_at == depth:
raise failure
visit(depth + 1)
try:
visit(0)
except ProbeFailure as error:
caught = error
return {"events": state.events, "final_setting": list(state.current),
"terminal": "normal" if caught is None else type(caught).__name__,
"same_original_error": None if caught is None else caught is failure}
def explicit_stack_reference(settings, fail_at=None):
"""Trusted fixtures: sequential loops and saved values, no with/generator."""
active = (1, 0)
stack, events = [], []
count = len(settings) if fail_at is None else fail_at + 1
for depth in range(count):
stack.append(active)
active = settings[depth]
events.extend([["enter", list(active)], ["observe", depth, list(active)]])
if fail_at is None:
events.append(["body", list(active)])
while stack:
active = stack.pop()
events.append(["restore", list(active)])
return {"events": events, "final_setting": list(active),
"terminal": "normal" if fail_at is None else "ProbeFailure",
"same_original_error": None if fail_at is None else True}
@contextmanager
def logged_scope(state, setting, swallow=False):
"""The swallow=True branch is a deliberate counterexample, not our policy."""
with temporary_calibration(state, setting) as active:
try:
yield active
except ProbeFailure:
state.events.append(["caught"])
if not swallow:
raise
def logging_observation(swallow):
state, failure, caught = Calibration(), ProbeFailure("body"), None
continued = False
try:
with logged_scope(state, (2, 3), swallow=swallow):
raise failure
continued = True
except ProbeFailure as error:
caught = error
return {"events": state.events, "continued_after_with": continued,
"same_original_error": caught is failure,
"final_setting": list(state.current)}
@contextmanager
def zero_yields():
if False:
yield "unreachable"
@contextmanager
def two_yields():
yield "first"
yield "second"
@contextmanager
def second_yield_after_throw():
try:
yield "first"
except ProbeFailure:
yield "second"
@contextmanager
def failing_cleanup():
try:
yield "active"
finally:
raise CleanupFailure("synthetic cleanup failure")
def error_observation(manager, body_error=None):
"""Finite known fixtures only; not an application-wide exception policy."""
entered, caught = False, None
try:
with manager:
entered = True
if body_error is not None:
raise body_error
except BaseException as error:
caught = error
return {"body_entered": entered,
"error_class": None if caught is None else type(caught).__name__,
"same_body_error": body_error is not None and caught is body_error,
"context_is_body_error": body_error is not None and caught is not None
and caught.__context__ is body_error}
def rejected(kind, call):
try:
call()
except kind:
return True
return False
def run_example():
clean = observe_nested(((2, 3), (4, -1)))
hand_clean = {"events": [["enter", [2, 3]], ["observe", 0, [2, 3]],
["enter", [4, -1]], ["observe", 1, [4, -1]],
["body", [4, -1]], ["restore", [2, 3]],
["restore", [1, 0]]],
"final_setting": [1, 0], "terminal": "normal",
"same_original_error": None}
checks = {"literal_normal_nested_trace_and_restoration": clean == hand_clean}
failed = observe_nested(((2, 3), (4, -1)), fail_at=1)
hand_failed = {"events": hand_clean["events"][:4] + hand_clean["events"][5:],
"final_setting": [1, 0], "terminal": "ProbeFailure",
"same_original_error": True}
checks["literal_body_failure_unwinds_and_keeps_same_exception"] = failed == hand_failed
family_count, family_ok = 0, True
for depth in range(4):
for settings in product(((1, 0), (2, 3), (4, -1)), repeat=depth):
for fail_at in (None,) + tuple(range(depth)):
family_count += 1
family_ok = family_ok and (observe_nested(settings, fail_at)
== explicit_stack_reference(settings, fail_at))
checks["independent_explicit_stack_oracle_142_scenarios_one_group"] = (
family_count == 142 and family_ok)
state, ordinary = Calibration(), ValueError("unhandled ordinary body error")
other = error_observation(temporary_calibration(state, (4, 8)), ordinary)
checks["different_ordinary_exception_propagates_after_restoration"] = (
other["same_body_error"] and state.current == (1, 0)
and state.events == [["enter", [4, 8]], ["restore", [1, 0]]])
state, abort = Calibration(), SyntheticAbort("finite abort fixture")
abort_result = error_observation(temporary_calibration(state, (1, -8)), abort)
checks["baseexception_subclass_also_runs_local_finally"] = (
abort_result["same_body_error"] and state.current == (1, 0)
and state.events == [["enter", [1, -8]], ["restore", [1, 0]]])
reraised, swallowed = logging_observation(False), logging_observation(True)
log_trace = [["enter", [2, 3]], ["caught"], ["restore", [1, 0]]]
checks["logging_then_bare_raise_preserves_body_error"] = (
reraised == {"events": log_trace, "continued_after_with": False,
"same_original_error": True, "final_setting": [1, 0]})
checks["deliberate_swallow_counterexample_resumes_after_with"] = (
swallowed == {"events": log_trace, "continued_after_with": True,
"same_original_error": False, "final_setting": [1, 0]})
class IntChild(int):
pass
invalid_type = [[], (), (1,), (True, 0), (1, False), (1.0, 0), (IntChild(1), 0)]
invalid_value = [(0, 0), (5, 0), (1, -9), (1, 9)]
checks["strict_types_units_caps_and_preentry_rejections"] = (
all(rejected(TypeError, lambda s=s: Calibration(s)) for s in invalid_type)
and all(rejected(ValueError, lambda s=s: Calibration(s)) for s in invalid_value)
and rejected(ValueError, lambda: observe_nested(((1, 0),) * 4))
and rejected(ValueError, lambda: observe_nested((), 0))
and rejected(ValueError, lambda: observe_nested(((1, 0),), True))
and rejected(TypeError, lambda: observe_nested([]))
and observe_nested(((1, -8), (4, 8)))["final_setting"] == [1, 0])
zero = error_observation(zero_yields())
double = error_observation(two_yields())
after_throw = error_observation(second_yield_after_throw(), ProbeFailure("body"))
checks["zero_yield_is_protocol_runtimeerror_before_body"] = (
not zero["body_entered"] and zero["error_class"] == "RuntimeError")
checks["second_yield_on_normal_exit_is_protocol_runtimeerror"] = (
double["body_entered"] and double["error_class"] == "RuntimeError")
checks["yield_again_after_thrown_body_error_is_protocol_runtimeerror"] = (
after_throw["body_entered"] and after_throw["error_class"] == "RuntimeError"
and not after_throw["same_body_error"])
cleanup = error_observation(failing_cleanup(), ProbeFailure("body"))
checks["cleanup_failure_replaces_body_error_with_original_context"] = (
cleanup["body_entered"] and cleanup["error_class"] == "CleanupFailure"
and not cleanup["same_body_error"] and cleanup["context_is_body_error"])
state = Calibration()
manager = temporary_calibration(state, (0, 0))
deferred_before = state.current == (1, 0) and state.events == []
deferred_error = error_observation(manager)
checks["decorated_factory_defers_validation_until_enter"] = (
deferred_before and deferred_error["error_class"] == "ValueError"
and not deferred_error["body_entered"] and state.current == (1, 0)
and state.events == [])
state = Calibration()
with temporary_calibration(state, (2, 3)) as snapshot:
state.current = (4, 8) # A separate local assignment, not a hardware write.
preserved_snapshot = snapshot == (2, 3) and type(snapshot) is tuple
checks["yielded_immutable_snapshot_not_live_state_and_saved_value_restored"] = (
preserved_snapshot and snapshot == (2, 3) and state.current == (1, 0)
and state.events == [["enter", [2, 3]], ["restore", [1, 0]]])
assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values()), checks
return {"python": platform.python_version(), "implementation": platform.python_implementation(),
"scope": "actual synchronous contextlib; owned local synthetic state only",
"setting_units": {"gain": "dimensionless", "offset_mv": "millivolts"},
"own_bounds": {"gain_inclusive": [1, 4], "offset_mv_inclusive": [-8, 8],
"nested_scopes_max": 3},
"normal": clean, "body_failure": failed, "logging_reraise": reraised,
"deliberate_swallow_counterexample": swallowed,
"protocol_errors": {"zero_yield": zero, "two_yields": double,
"second_yield_after_throw": after_throw,
"cleanup_failure": cleanup, "deferred_validation": deferred_error},
"independent_reference_scenarios_one_group": family_count,
"unique_check_groups": len(checks), "checks": checks,
"limits": "No device/file/network, concurrency, async, rollback or crash guarantee; no exact diagnostic-message or same-manager-reuse error-class promise."}
if __name__ == "__main__":
sys.stdout.buffer.write((json.dumps(run_example(), ensure_ascii=True, indent=2,
allow_nan=False) + "\n").encode("utf-8"))
چهارده گروه، علاوه بر مرجع و شاهدهای دستی، صفر بار تحویل، تحویلِ دوم در خروجِ عادی و تحویلِ دوباره پس از گرفتنِ خطای بدنه را بررسی میکنند. این نقضهای پروتکل در اجرای نمونه RuntimeError دارند. متنِ انگلیسیِ پیام، معیارِ آزمون نیست. همچنین شاهدِ پاکسازیِ خراب عمداً CleanupFailure میدهد: خطای خروج جایِ خطای بدنه را میگیرد و همان خطای بدنه در __context__ دیده میشود. پس حفظِ اصلِ خطا را برای هر پاکسازیِ دلخواه وعده نمیدهیم.
{
"python": "3.14.5",
"implementation": "CPython",
"scope": "actual synchronous contextlib; owned local synthetic state only",
"setting_units": {
"gain": "dimensionless",
"offset_mv": "millivolts"
},
"own_bounds": {
"gain_inclusive": [
1,
4
],
"offset_mv_inclusive": [
-8,
8
],
"nested_scopes_max": 3
},
"normal": {
"events": [
[
"enter",
[
2,
3
]
],
[
"observe",
0,
[
2,
3
]
],
[
"enter",
[
4,
-1
]
],
[
"observe",
1,
[
4,
-1
]
],
[
"body",
[
4,
-1
]
],
[
"restore",
[
2,
3
]
],
[
"restore",
[
1,
0
]
]
],
"final_setting": [
1,
0
],
"terminal": "normal",
"same_original_error": null
},
"body_failure": {
"events": [
[
"enter",
[
2,
3
]
],
[
"observe",
0,
[
2,
3
]
],
[
"enter",
[
4,
-1
]
],
[
"observe",
1,
[
4,
-1
]
],
[
"restore",
[
2,
3
]
],
[
"restore",
[
1,
0
]
]
],
"final_setting": [
1,
0
],
"terminal": "ProbeFailure",
"same_original_error": true
},
"logging_reraise": {
"events": [
[
"enter",
[
2,
3
]
],
[
"caught"
],
[
"restore",
[
1,
0
]
]
],
"continued_after_with": false,
"same_original_error": true,
"final_setting": [
1,
0
]
},
"deliberate_swallow_counterexample": {
"events": [
[
"enter",
[
2,
3
]
],
[
"caught"
],
[
"restore",
[
1,
0
]
]
],
"continued_after_with": true,
"same_original_error": false,
"final_setting": [
1,
0
]
},
"protocol_errors": {
"zero_yield": {
"body_entered": false,
"error_class": "RuntimeError",
"same_body_error": false,
"context_is_body_error": false
},
"two_yields": {
"body_entered": true,
"error_class": "RuntimeError",
"same_body_error": false,
"context_is_body_error": false
},
"second_yield_after_throw": {
"body_entered": true,
"error_class": "RuntimeError",
"same_body_error": false,
"context_is_body_error": true
},
"cleanup_failure": {
"body_entered": true,
"error_class": "CleanupFailure",
"same_body_error": false,
"context_is_body_error": true
},
"deferred_validation": {
"body_entered": false,
"error_class": "ValueError",
"same_body_error": false,
"context_is_body_error": false
}
},
"independent_reference_scenarios_one_group": 142,
"unique_check_groups": 14,
"checks": {
"literal_normal_nested_trace_and_restoration": true,
"literal_body_failure_unwinds_and_keeps_same_exception": true,
"independent_explicit_stack_oracle_142_scenarios_one_group": true,
"different_ordinary_exception_propagates_after_restoration": true,
"baseexception_subclass_also_runs_local_finally": true,
"logging_then_bare_raise_preserves_body_error": true,
"deliberate_swallow_counterexample_resumes_after_with": true,
"strict_types_units_caps_and_preentry_rejections": true,
"zero_yield_is_protocol_runtimeerror_before_body": true,
"second_yield_on_normal_exit_is_protocol_runtimeerror": true,
"yield_again_after_thrown_body_error_is_protocol_runtimeerror": true,
"cleanup_failure_replaces_body_error_with_original_context": true,
"decorated_factory_defers_validation_until_enter": true,
"yielded_immutable_snapshot_not_live_state_and_saved_value_restored": true
},
"limits": "No device/file/network, concurrency, async, rollback or crash guarantee; no exact diagnostic-message or same-manager-reuse error-class promise."
}
نسخهٔ واقعیِ اجرا در خروجی ثبت میشود؛ صفحاتِ مرجع هنگامِ دسترسی در ۲۸ مهر ۱۴۰۵ نسخهٔ 3.14.8 را نشان میدهند و تاریخِ انتشارِ اصلیِ این صفحات معلوم نیست. مستندات، مدیرهای مولدی را یکبارمصرف میدانند؛ در این کد هر محدوده مدیرِ تازه میسازد و نوع یا متنِ خطای استفادهٔ دوباره از همان نمونه را قراردادِ قابلحمل معرفی نمیکنیم. نمونهٔ قدیمیِ یک صفحه، جایِ مشاهدهٔ رفتارِ نسخهٔ اجرا را نمیگیرد.
در درسِ ExitStack، مسئله ترتیبِ خروجِ چند منبع پس از ورودِ موفق بود. اینجا خودِ مدیرِ مولدی و بازگشتِ خطا به yield مهم است. درسِ پایانِ مولد نیز مقدارِ بازگشتی و خروجِ ناخواستهٔ StopIteration را جدا میکرد؛ آن قاعده با مجاز بودنِ یک تحویل در پروتکلِ مدیرِ زمینه یکی نیست.
۵. چهار تمرین دربارهٔ خروج، سرکوب و بازگردانی

تمرین اول: اگر خطا پس از مشاهدهٔ تنظیمِ درونی رخ دهد، تنظیمِ نهایی چیست؟ پاسخ: ابتدا زوجِ بیرونیِ دو و سه و سپس زوجِ پایهٔ یک و صفر بازمیگردد؛ بدنهٔ نهایی اجرا نشده است. در شاهدِ عادیِ ما، همان شیء ProbeFailure بیرون میآید؛ بازگردانیِ محلی به معنی انجامشدنِ محاسبهٔ بدنه نیست.
تمرین دوم: ثبتِ خطا بدونِ raise چه تغییری میدهد؟ پاسخ: اگر مولد بعد از گرفتنِ خطا عادی تمام شود، مدیر آن را رسیدگیشده اعلام میکند؛ دستورِ بعد از with اجرا میشود. هر دو شاخهٔ شاهد تنظیم را برمیگردانند، اما فقط شاخهٔ دوبارهبالابرنده همان خطا را به مصرفکننده میرساند.
تمرین سوم: چرا نمیتوان پس از خروج، تنظیمِ دوم را هم yield کرد؟ پاسخ: این پروتکل دقیقاً یک تحویل میخواهد؛ درخواستِ ادامه پس از بدنه باید به پایان برسد. تحویلِ دوم، چه در مسیرِ عادی و چه پس از گرفتنِ خطای بدنه، در شاهدهای ما خطای پروتکل است؛ برای جریانِ چند نتیجه باید رابطِ دیگری تعریف کرد.
تمرین چهارم: اگر خودِ پاکسازی خطا بدهد، آیا همان خطای بدنه حتماً بیرون میآید؟ پاسخ: خیر؛ شاهدِ مستقلِ پاکسازیِ خراب، CleanupFailure را بیرون میدهد و خطای بدنه را در زمینهٔ آن نگه میدارد. مدیرِ اصلیِ تنظیمات چنین خطای عمدیای ندارد؛ موفقیتِ آن، تضمینِ پاکسازیِ دستگاه یا بازیابیِ پس از خرابی نیست.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۸ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

