از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
چگونه نتایج یک تابع پایتون را به برنامه خود برگردانید وقتی کتابخانه راه را مسدود می کند
سرفصلهای مطلب
معمولاً یک تابع پایتون نتایج خود را با استفاده از a ارسال می کند return
بیانیه.
مشکل این است که گاهی اوقات نمی تواند، بنابراین شما باید راه دیگری را برای برگرداندن آن نتایج بیابید.
این اغلب زمانی اتفاق می افتد که رابط کتابخانه شخص دیگری در راه است و شما نمی توانید آن را تغییر دهید.
آنچه شما باید انجام دهید ممکن است عبور نتایج خارج از باند، در یک جریان جانبی، دور زدن، دور زدن، یا انجام یک اجرای نهایی نامیده شود.
این آموزش این مشکل را در چندین سطح دشواری توضیح میدهد، سپس راهحلهای متعددی را برای مدیریت آن سطوح، از جمله عدم استفاده و چرایی، ارائه میدهد.
توصیه هایی در مورد چگونگی استفاده از حداقل راه حل های مختلف برای پوشش مشکل در پروژه شما وجود دارد.
این مقاله طولانی است، زیرا شما را از طریق پیشرفت شدت مشکل و راه حل های آن راهنمایی می کند.
با این حال، ممکن است فقط به شروع، یک یا دو راه حل توضیح داده شده و راهنمایی نزدیک به پایان نیاز داشته باشید.
محتویات این آموزش:
- چه زمانی این مشکل رخ می دهد؟
- نحوه یافتن راه حل، بسته به شدت مشکل
- چند راه حل ممکن
- نمونه ای از کتابخانه پایتون که در مسیر عبور نتایج مفید قرار می گیرد
- راه حل با مثال و بحث
- راه حل شماره 1: ویژگی تابع
- راه حل شماره 2: متغیر جهانی (ضد الگوی منسوخ شده)
- راه حل شماره 3: صف
- راه حل شماره 4: صف ها
- تنوع: صف Last In First Out (LIFO).
- تنوع: هم صفت و هم صف
- راه حل شماره 5: بسته بندی ساده پایتون
- راه حل شماره 6: لفاف دکوراتور پایتون
- مشاوره انتخاب راه حل برای استفاده
- خلاصه
چه زمانی این مشکل رخ می دهد؟
اگر کتابخانه ای در راه باشد، مشکل دارید، اما باید از آن استفاده کنید.
برنامه عملکرد سطح بالا خود را تصور کنید، پ، یک تابع کتابخانه شخص ثالث را فراخوانی می کند، L، که سپس تابع هدف سطح پایین شما را فراخوانی می کند، f.
یک مثال خوب از چنین عملکرد کتابخانه ای است timeit
.
برنامه سطح بالا باید تابع کتابخانه را فراخوانی کند و نتایج برگردانده شده را مطابق با مشخصات مستند Application Programming Interface (API) آن کتابخانه دریافت کند.
به طور مشابه، عملکرد f پارامترها را می پذیرد و باید نتایجی را برگرداند که دوباره با مشخصات API آن تابع کتابخانه مطابقت داشته باشد.
عملکرد کتابخانه L یک واسطه میانی است.
مشکل زمانی اتفاق میافتد که کتابخانه واسطه و API آن تحت کنترل شما نیستند و نیاز شما را برای به دست آوردن نتایج تابع هدف برآورده نمیکنند.
اغلب API برای برگرداندن نتایج از یک تابع سطح پایین به کتابخانه واسطه و API از کتابخانه به عملکرد سطح بالاتر شما، در ترکیب، نتایج کامل تابع سطح پایین را ارسال نمی کند.
به طور معمول، کتابخانه از پیپ، گیت، آناکوندا یا خود پایتون می آید.
حتی اگر این کتابخانهها منبع باز باشند، باز هم شما، همتایانتان، کارفرمایان و مشتریانتان نمیخواهید کسی با داخلی و خارجی کتابخانه دستکاری کند.
همچنین اگر کتابخانه مانند timeit.repeat
یا scipy.optimize.minimize
، سپس تابع هدف سطح پایین معمولاً بارها فراخوانی می شود.
نحوه یافتن راه حل بسته به شدت مشکل
چگونه یک برنامه پایتون می تواند یک محاصره کتابخانه واسطه را دور بزند؟
مفاهیمی که باید یاد بگیرید و برای رسیدگی به مشکل استفاده کنید به میزان نیازها بستگی دارد:
- آخرین نتیجه: تابع سطح بالای شما فقط به آخرین نتیجه عملکرد سطح پایین نیاز دارد، حتی اگر بارها فراخوانی شده باشد. این راه حل از ایده یادداشت با ذخیره آخرین نتیجه در یک ویژگی تابع سطح پایین استفاده می کند. با استفاده از یک قرارداد نامگذاری، نام ویژگی همیشه یکسان است. این ایمن است، مهم نیست که چه تعداد توابع مختلف، حتی نام مستعار، از همان نام ویژگی استفاده می کنند. به جای استفاده از یک ویژگی، یک راه حل جایگزین از مفهوم یک متغیر جهانی استفاده می کند. استفاده از متغیر سراسری کار خوبی نیست. مثال ارائه شده نشان می دهد که چگونه یک عمل بد ناامن است.
- نیاز به هر نتیجه: تابع سطح بالای شما به نتیجه هر یک از تماس های متعددی که کتابخانه با عملکرد سطح پایین انجام می دهد نیاز دارد. این راه حل از مفهوم صف استفاده می کند که توسط کتابخانه صف استاندارد پایتون پیاده سازی شده است.
- به هر نتیجه ای نیاز دارید، اما رفتن به مکان های مختلف: مکان های متعدد در توابع سطح بالا تابع سطح پایین را از طریق یک یا چند واسطه فراخوانی می کنند. هر کدام به مجموعه نتایج خاص خود نیاز دارند. راه حل از صف های متعددی استفاده می کند که صف برای استفاده به عنوان یک پارامتر به تابع سطح پایین ارسال می شود.
- نمی توان عملکرد سطح پایین را افزایش داد: تابع سطح پایین از یک کتابخانه یا جای دیگر می آید، بنابراین شما نمی توانید یا نباید آن را تغییر دهید. راه حل از مفهوم لفاف استفاده می کند. wrapper صف و صفت ها را تنظیم می کند، تابع سطح پایین را فراخوانی می کند و کاری که لازم است را با نتیجه انجام می دهد. این راه حل از مفهوم عمومی یک لفاف استفاده می کند، اما از مفهوم پیشرفته پایتون «پوشش های تزئینی» استفاده نمی کند.
- نمی توان عملکرد سطح پایین را افزایش داد. به هر نتیجه ای نیاز دارید، اما رفتن به مکان های مختلف: این پیشرفته ترین نیازها را ترکیب می کند. این راه حل از مفهوم پیشرفته Python از پوشش های دکوراتور بهره برداری می کند. این امر نیاز به ارسال اشیاء صف را به عنوان پارامتر حذف می کند.
کد مثال برای هر یک از اینها ارائه شده است.
مورد من که در ابتدا منجر به این تلاش شد در سطح “نیاز به هر نتیجه” بود.
واسطه یک حلقه تودرتو شامل دو تابع کتابخانه بود.
برنامه سطح بالای من این را فراخوانی کرد timeit.repeat
تابعی که سپس فراخوانی شد scipy.optimize.minimize
چندین بار.
هر فراخوانی از scipy.optimize.minimize
بارها تابع سطح پایین من را فراخوانی کرد.
برای هر اجرای تابع سطح پایین من، میخواستم نتایج را به برنامه اصلی، تا بالا و اطراف هر دو کتابخانه ارسال کند.
تلاش طراحی و توسعه برخی از شیوه های خوب را در نظر داشت:
- از تداخل یا تداخل نامگذاری خودداری کنید: این امر به ویژه زمانی مهم است که چندین تابع هدف وجود دارد که باید نتایج جریان جانبی خود را برگردانند. همچنین مهم است که تابع هدف ممکن است از چندین مکان در کد سطح بالا یا از طریق چندین واسطه فراخوانی شود.
- اجتناب از جفت شدن: اتصال صریح بین عملکردهای سطح بالا و سطح پایین را به حداقل برسانید.
- اختیاری را ارائه دهید اگر نیازی به نتایج اضافی ندارد، از تحمیل الزامات کدگذاری اضافی به تماس گیرنده سطح بالا خودداری کنید.
چند راه حل ممکن
دو راه حل اول جایگزین در این آموزش زمانی قابل اجرا هستند که تنها به یک نتیجه نیاز باشد و آن نتیجه از اجرای نهایی تابع هدف حاصل می شود.
راه حل سوم جایگزین زمانی قابل اجرا است که نتیجه ای از هر اجرای تابع هدف یا هر اجرای کامل یک حلقه داخلی از فراخوانی تابع هدف مورد نیاز باشد.
- ویژگی تابع – برای یک نتیجه خوب است. هر تنظیم از یک نتیجه، قبلی را بازنویسی می کند.
- متغیر جهانی – برای یک نتیجه خوب است. هر تنظیم همچنین تنظیمات قبلی را بازنویسی می کند. این جایگزین به دلایل فلسفی و فنی پایتونیک یک ضدالگوی منسوخ است که در ادامه توضیح داده خواهد شد.
- صف – برای یک یا چند نتیجه خوب است. انتخاب هایی در مورد ترتیب نتایج وجود دارد.
- صف – برای چندین نتیجه خوب است، اما جایی که همه آنها به یک مکان نمی روند.
- Simple Wrapper – زمانی خوب است که نمی توانید عملکرد سطح پایین را تغییر دهید. این راه حل واقعاً تفاوت اساسی با قرار دادن نتایج در یک صفت یا صف ندارد. با این حال، کدی را که در داخل تابع هدف قرار می گیرد، خارجی می کند. نتیجه تابعی است که تابع هدف را که ممکن است غیرقابل تغییر در برخی از کتابخانه ها باشد، به روشی که به تخصص پیشرفته پایتون نیاز ندارد، می پیچد.
- Python Decorator Wrapper – از مکانیزم پیچیده کردن تابع Python پیشرفته “دکوراتور” استفاده می کند. این اجازه می دهد تا صف ها و ویژگی های متعدد بدون تغییر عملکرد سطح پایین زیرین. ممکن است درک آن چالش برانگیز باشد، اما استفاده از آن در کد شما آسان است.
همه جایگزین ها با چند رشته کار می کنند.
جایگزین های صف و بسته بندی نیز با چند پردازش کار می کنند.
نمونه ای از کتابخانه پایتون که در مسیر عبور نتایج مفید قرار می گیرد
بیایید ابتدا مشکل را ایجاد کنیم و سپس هر راه حل ممکن را بررسی خواهیم کرد.
# LibraryL.py
# usage: from LibraryL import L
# The purpose of this library and its sole function is to demonstrate an intermediary that prevents an invoked function from returning useful results to the high level caller.
# Imagine it is in an external library not under the application programmer's control.
#
def L(f,fparms=[],Lparms=[]): # intermediary function
# f: target function;
# fparms: parameter object to be passed to f. Defaults to []
# Lparms: object to be used by L itself (unused in this example). Defaults to [].
rf=f(fparms) # pass fparms to f, call f, collect its return value
return rf.__class__.__name__
# L does not return to its caller any of what function f returned to L, only the returned object's class's name, such as 'list'. This is a good example of a bad example.
این تابع، L، به عنوان تابع واسطه در مثال های حل زیر استفاده خواهد شد.
راه حل شماره 1: استفاده از ویژگی تابع
تابع سطح پایین نتیجه مفید خود را مطابق با یک قرارداد نامگذاری در یک ویژگی پایتون ذخیره می کند.
def f(fparms): # Low level function to be called through an intermediate
# Attribute naming convention: <function_name>.mrrv
# where mrrv stands for Most Recent Result Value
# Create the attribute, so it exists even in case an exception is thrown before the result is set.
if not hasattr(f, "mrrv"): f.mrrv=None
# f.mrrv=None # uncomment to clear it out on each call.
# do work …
ultimate_result=["this", "that", 42, "and the other thing"]
f.mrrv=ultimate_result
result_to_return=42
return result_to_return
def p(): # High level function calling f through the intermediary L
from LibraryL import L
fparms=[]
Lparms=[]
r=L(f, fparms, Lparms) # invoke f through L
resultfromf=f.mrrv
print(resultfromf)
# do work …
p()
quit()
خروجی:
['this', 'that', 42, 'and the other thing']
در این راه حل، تابع فراخوانی شده، نتیجه جریان جانبی خود را مطابق با یک قرارداد نامگذاری ساده در یک ویژگی در شی تابع قرار می دهد.
هر تابع ویژگی محلی خود را دارد که در آن نتایج را واریز می کند.
نام صفت، mrrv
، برای اکثر نتایج اخیر، مقدار نتیجه در همه توابعی که از این تکنیک استفاده می کنند یکسان است، بنابراین حتی زمانی که تابع فراخوانی شده از طریق نام مستعار فراخوانی شود، کار می کند، مانند مثال زیر:
# Demonstration of how the attribute solution works correctly,
# even if the target function has been aliased.
def get_target_function():
return f
def p():
g=get_target_function() # g.__name__ would provide the string name of the underlying aliased function. So what? We don't need it.
# Do other things.
from LibraryL import L
gparms=[]
Lparms=[]
r=L(g, gparms, Lparms) # invoke g through L
# L invokes g, which is really f
# f sets f.mrrv
# Don't care that g is not the function's real name.
resultfromg=g.mrrv # No problem.
print(resultfromg)
p()
quit()
خروجی:
['this', 'that', 42, 'and the other thing']
این ویژگی دارای یک مقدار واحد در هر تابع است، اگرچه آن مقدار واحد می تواند یک لیست یا شی پیچیده دیگری باشد.
حتی اگر تابع از مکانهای زیادی در کد فراخوانی شود، حتی اگر از رشتههای زیادی باشد، ویژگی همچنان فقط مقداری را خواهد داشت که اخیراً توسط هر فراخوانی در فرآیند تنظیم شده است.
در خانه داری اولیه در بالای تابع سطح پایین، اگر مشخصه از قبل وجود نداشته باشد، مقدار مشخصه بر روی None
.
به این ترتیب، حتی اگر تابع بدون تنظیم ویژگی روی یک مقدار مفید برگردد، ارجاعات به ویژگی استثنایی ایجاد نخواهد کرد.
به صورت اختیاری، ویژگی را می توان به تنظیم کرد None
در ابتدای هر تماس، به منظور پاک کردن هر گونه نتیجه قبلی.
انجام این کار ممکن است در یک محیط چند رشته ای نامطلوب باشد.
راه حل شماره 2: استفاده از یک متغیر سراسری
هشدار: این راه حل جایگزین یک ضدالگوی منسوخ به دلایل فلسفی و فنی پایتونیک است که در زیر توضیح خواهم داد.
من آن را فقط به عنوان یک مرد کاهی برای اهداف آموزشی برای نشان دادن شیوه های برنامه نویسی ضعیف ارائه کرده ام:
def f(fparms): # DEPRECATED Low level function to be called through an intermediate
# Global object naming convention: <invoked_function_name>_mrrv
# where mrrv represents Most Recent Result Value
global f_mrrv
f_mrrv=None # clears the global, in case an exception is thrown before the result is set.
# do work, then
ultimate_result=["this", "that", 42, "and the other thing"]
f_mrrv=ultimate_result
result_to_return=42
return result_to_return
def p(): # top level calling program
from LibraryL import L
# Global object naming convention: <invoked_function_name>_mrrv
global f_mrrv # unnecessary to use this global statement if L is called from __main__ , but doesn't hurt
fparms=[]
Lparms=[]
r=L(f, fparms, Lparms) # invoke f through L
resultfromf=f_mrrv
print(resultfromf)
# do work.
p()
quit()
خروجی:
['this', 'that', 42, 'and the other thing']
این راه حل به هر تابع مکان جهانی خود را برای سپرده گذاری نتایج می دهد.
نام متغیر بر اساس نام تابع فراخوانی شده است.
تابع فراخوانی شده نتیجه جریان جانبی خود را در متغیر سراسری خود قرار می دهد.
تابع framus
نتیجه خود را در متغیر جهانی قرار می دهد framus_mrrv
.
در خانه داری اولیه در بالای تابع فراخوانی شده، کد مقدار متغیر سراسری را بر روی None
.
به این ترتیب، حتی اگر تابع بدون تنظیم متغیر روی یک مقدار مفید برگردد، ارجاع به متغیر استثنایی ایجاد نخواهد کرد.
همچنین، هر گونه نتیجه از تماس قبلی پاک می شود.
متغیر سراسری یک مقدار واحد است، اگرچه آن مقدار واحد می تواند یک لیست یا شی پیچیده دیگری باشد.
حتی اگر تابع از مکان های زیادی در کد فراخوانی شود، متغیر سراسری تنها آخرین مقدار تعیین شده توسط هر فراخوانی به آن تابع در فرآیند را خواهد داشت.
چرا این راه حل منسوخ شده است؟
این جایگزین متغیر جهانی به دلایل فلسفی و فنی پایتونیک منسوخ شده است.
فلسفی: این راه حل خود را به کد تابع سطح بالاتر وارد می کند و یک شی را به فضای نام جهانی تزریق می کند. راه حل ویژگی این کار را انجام نمی دهد. تابع سطح پایین ممکن است در بسیاری از برنامه ها استفاده شود. برخی ممکن است به نتیجه طولانی نیاز نداشته باشند. با حل ویژگی، اگر فراخوان سطح بالا به نتیجه توسعه یافته نیاز نداشته باشد، نیازی به نگرانی در مورد آن یا انجام کار اضافی ندارد.
فنی: از نظر فنی، راه حل متغیر جهانی قوی نیست. پایتون با توابع به عنوان اشیاء درجه یک رفتار می کند. یک شی تابع یکسان را می توان به چندین نام متغیر اختصاص داد. سپس هر نام می تواند به عنوان نام مستعار برای تابع استفاده شود. کد نمایشی شکسته آینده این کار را انجام می دهد.
در مثال، تابع target یک متغیر سراسری با یک نام ایجاد می کند.
پس از آن، تابع سطح بالاتر نتیجه را در جای دیگری جستجو می کند، با این فرض که نام دیگری دارد.
راه حل ویژگی از این مشکل جلوگیری می کند.
با حل ویژگی، تابع هدف یک ویژگی را تنظیم می کند، mrrv
، در داخل خود تابع، نه در شیء دیگر.
تابع سطح بالا به همان نام ویژگی در همان شی ارجاع می دهد، حتی اگر به شی از طریق نام مستعار ارجاع داده شود.
می تواند این کار را انجام دهد زیرا نام ویژگی به شیئی که بخشی از آن است گره خورده است، نه نامی که برای دسترسی به شی مورد استفاده قرار می گیرد.
بیایید ببینیم چگونه راه حل متغیر جهانی شکسته می شود:
# Demonstration of how the global variable solution is broken.
def get_target_function():
return f
def p(): # BROKEN! Demonstration why NOT to use the global variable solution.
# Forget or ignore that g is not the function's real name.
g=get_target_function() # p() does not and cannot know the name of g's targetfunction
# Do other things.
from LibraryL import L
# Global object naming convention: <invoked_function_name>_mrrv
# Create and then use g_mrrv, which the function will not set.
global g_mrrv # NOT GOOD: This is not the name of the global that will be set by the code inside the function.
gparms=[]
Lparms=[]
r=L(g, gparms, Lparms) # invoke g through L
# L invokes g, which is f
# f sets f_mrrv not g_mrrv
resultfromg=g_mrrv # Has not been set. g (alias for f) did set f_mrrv
print(resultfromg)
# Fail to do work.
راه حل شماره 3: استفاده از صف
در این راه حل، تابع سطح پایین نتایج توسعه یافته خود را در یک صف پایتون قرار می دهد. یک تابع سطح بالا نتایج را بازیابی می کند.
معمولاً در پایتون به استفاده از صف ها در برنامه های چند رشته ای یا چند پردازشی فکر می کنیم.
در اینجا از صف برای انتقال اطلاعات بین توابع حتی در همان رشته استفاده می شود.
انواع مختلفی از راه حل صف وجود دارد.
آنها در اینکه کدام تابع صف را ایجاد می کند و نحوه دسترسی تابع دیگر به شی صف متفاوت است.
در این راه حل، شی صف در یک ویژگی تابع در تابع سطح پایین ذخیره می شود. این ویژگی مطابق با یک قرارداد نامگذاری نامگذاری شده است.
این از نظر مفهومی مشابه راه حل ویژگی است، با این تفاوت که این ویژگی به جای خود نتایج، یک شی صف را در خود نگه می دارد. نتایج در صف قرار می گیرند.
اگر برنامه فراخوانی به نتایج توسعه یافته علاقه مند باشد، پس از تکمیل تابع فراخوانی شده می تواند از صف بخواند.
هشدار: اگر تابع سطح پایین هزاران یا میلیونها بار اجرا شود بدون اینکه عملکرد سطح بالا صف را تخلیه کند، ممکن است فرآیند خارج از حافظه از کار بیفتد.
# Solution #3: Queue object in a function attribute
# low level function
def f(fparms): # ultimate result is put to a queueID which is stored in a function attribute.
ultimate_result=None
# Queue object naming convention: <invoked_function_name>.sidestreamoutq
if not hasattr(f, "sidestreamoutq"): f.sidestreamoutq=queue.Queue() # only create the queue once.
# do useful things
ultimate_result=["what comes in is what is returned",fparms]
f.sidestreamoutq.put(ultimate_result)
result_to_return=42
return result_to_return
def get_target_function():
return f
# high level function
def p():
fparms4=[4, "queueID in a function attribute", 84]
fparms5=[5, "queueID in an aliased function's attribute", 84]
Lparms=[]
r4=L(f, fparms4, Lparms)
while not f.sidestreamoutq.empty():
f_result=f.sidestreamoutq.get()
print(f_result)
# now do the same with an alias to the low level function
g=get_target_function() # p() does not and cannot know the name of g's targetfunction
r5=L(g, fparms5, Lparms)
while not g.sidestreamoutq.empty(): # Note that g.sidestreamoutq === f.sidestreamoutq. Reading from one is the same as reading from the other.
g_result=g.sidestreamoutq.get()
print(g_result)
# __main__
import queue
from LibraryL import L
p()
quit()
در این تغییر راه حل، تابع سطح پایین یک شی صف ایجاد می کند و سپس یک ویژگی تابع را در خود تنظیم می کند تا به آن ارجاع دهد.
بر اساس یک قرارداد نامگذاری، نام ویژگی است sidestreamoutq
.
اما از آنجایی که در یک شی تابع وجود دارد، به آن اشاره می شود whateverfunctionname.sidestreamoutq
.
تابع فراخوانی شده نتیجه جریان جانبی خود را در آن صف قرار می دهد.
همانطور که در کد مثال نشان داده شده است، این راه حل حتی اگر برنامه فراخوانی نام تابع سطح پایین را مستعار کند، مانند با someotherfunctionname=lowlevelfunctionname
، و سپس فراخوانی می کند L(
. someotherfunctionname
، fparms، Lparms)
دلیل آن این است که تمام مراجع در
ارجاعاتی به همان نمونه های شی هستند که در someotherfunctionname
lowlevelfunctionname
.
در خانه داری اولیه در بالای تابع فراخوانی شده، تابع (a) مقدار نتیجه توسعه یافته را بر روی None
و (ب) صف نتیجه را ایجاد می کند، اما فقط در صورتی که قبلا وجود نداشته باشد.
انجام این کار به طور خودکار نتایج را مجبور به قرار دادن نتایج در صف در صورت عدم موفقیت یا خاتمه عملکرد بدون قرار دادن صریح نمی کند.
اما اگر تابع مقدار را به چیز دیگری تغییر ندهد، اگر تابع قرار را انجام دهد، آیتمی که در صف قرار میگیرد مقدار تعریف شده را خواهد داشت. None
.
راه حل شماره 4: صف های متعدد
در این راه حل توابع سطح بالایی صف هایی را مشخص می کنند که نتایج را دریافت می کنند.
هیچ قرارداد نامگذاری برای شی صف مورد نیاز نیست.
تابع سطح بالا یک شی صف ایجاد می کند و سپس آن را در بین پارامترهای ارسال شده از طریق واسطه به هدف شامل می شود.
تابع سطح پایین مقدار شی صف را استخراج کرده و از آن استفاده می کند.
این به طور قابل توجهی با راه حل های قبلی متفاوت است.
راهحلهای قبلی همگی بر یک قرارداد نامگذاری برای شیء به اشتراک گذاشته شده بین تابع فراخوانی سطح بالا و تابع هدف تکیه کردهاند.
این یک جفت نام صریح بین توابع است.
در علوم کامپیوتر، این جفت معمولاً نامطلوب در نظر گرفته می شود.
حتی در مورد یک واسطه، این جفت شدن معمولاً به قیمت افزایش پیچیدگی کد قابل اجتناب است.
“معمولا قابل اجتناب” زمانی است که فراخوان واسطه بتواند یک مقدار پارامتر اضافی را مشخص کند که به هدف ارسال می شود.
در این مورد، مقدار اضافی شی صف است.
اگر واسطه به فراخوان اجازه ندهد شی صف را به تابع هدف ارسال کند، این راه حل یک گزینه قابل اجرا نیست.
در اینجا امکان پذیر است و کد آن را انجام می دهد:
# Solution 4. Allow for multiple queues.
def fqp(fparms): # fparms is a list containing values to be passed to f, including a queue object to be sent the ultimate result
ultimate_result=None
# Extra complexity: Extract the result queue object from the incoming parameter list
sidestreamoutq=None
for p in fparms: # find and use the first element of parameter list which is a Queue or LifoQueue
if p.__class__.__name__ in ["Queue", "LifoQueue"] :
sidestreamoutq=p
break
# do useful things
ultimate_result=["this", "that", 42, "and the other thing", fparms]
# put the result object to the result queue, if there is one
if not (sidestreamoutq is None): sidestreamoutq.put(ultimate_result)
result_to_return=42
return result_to_return
# usage:
def p():
potusqueue=queue.Queue() # could use queue.LifoQueue(), and a single get, if you only want the last item
flotusqueue=queue.LifoQueue()
fparms1a=[11, "George", potusqueue, 1788] # bparms is a list containing values to be passed to b
fparms1b=[12, "George", potusqueue, 1792] # bparms is a list containing values to be passed to b
fparms2=[3, "no queueID in the parameters", 1789] # by not including the queue object among the parameters, the function will not put results to the queue.
fparms3=[4, flotusqueue, "Martha", 1788, 1792]
Lparms=[]
r1a=L(fqp, fparms1a, Lparms) # invoke fqp through L
r1b=L(fqp, fparms1b, Lparms) # invoke fqp through L
r2=L(fqp, fparms2, Lparms) # fparms2 is missing a queueID.
r3=L(fqp, fparms3, Lparms)
#print("drain potusqueue and flotusqueue")
while not potusqueue.empty():
f_result=potusqueue.get()
print(f_result)
# use the f_result
if not flotusqueue.empty(): # only interested in last result on the LIFO queue
f_result=flotusqueue.get()
print(f_result)
# use the f_result
# __main__
import queue
from LibraryL import L
p()
quit()
در این راه حل، تابع فراخوانی یک یا چند شیء صف LIFO یا FIFO ایجاد می کند.
تابع فراخوانی شامل آن شی صف در میان پارامترهای ارسال شده به کتابخانه برای ارسال کتابخانه به تابع هدف می شود.
آن تابع فراخوانی شده به دنبال و در صورت وجود، شی صف را از پارامترهای ورودی آن استخراج می کند.
در خانه داری اولیه در بالای تابع فراخوانی شده، مقدار نتیجه را بر روی تنظیم می کند None
.
انجام آن باعث نمی شود None
در صورت عدم موفقیت یا خاتمه عملکرد بدون قرار دادن صریح، در صف قرار می گیرد.
اما اگر تابع مقدار را به چیز دیگری تغییر ندهد، وقتی تابع put را انجام می دهد، آیتم در صف مقدار تعریف شده را خواهد داشت. None
.
تابع فقط در صورتی نتایج را در صف قرار می دهد که یک شی صف در بین پارامترهای خود دریافت کند.
اگر برنامه سطح بالای شما فقط به آخرین نتایج از همه نتایج توسعه یافته نیاز داشته باشد چه می شود؟
گاهی اوقات فقط آخرین نتیجه برای برنامه سطح بالا جالب است.
اگر این درست باشد و از یک صف استفاده شود، جایگزینی برای تخلیه کل صف و حفظ آخرین نتیجه وجود دارد.
در عوض، صف را به عنوان یک صف Last-In-First-out ایجاد کنید anyqueue=queue.LifoQueue()
بجای anyqueue=queue.Queue()
.
سپس اولین (و احتمالاً تنها) anyqueue.get()
ورودی نهایی را در صف قرار می دهد.
کد راه حل مثال بالا دو صف ایجاد می کند، یکی FIFO و دیگری LIFO.
در دو تماس صف FIFO را به عنوان پارامتر و در یک تماس صف LIFO را ارسال می کند.
هر چند نتایج زیادی در صف FIFO وجود داشته باشد تخلیه می شود، اما فقط یک ورودی از صف LIFO دریافت می کند.
این به این دلیل است که هدف استفاده از صف LIFO زمانی است که آخرین مورد مورد علاقه اولیه یا تنها مورد مورد علاقه باشد.
امکان طراحی – هر دو راه حل قابل اجرا را ترکیب کنید
این دو گزینه، (الف) ویژگی تابع هدف و (ب) پارامتر شی صف تابع فراخوانی، می توانند با هم ترکیب شوند.
این ترکیب به تابع سطح بالا اجازه می دهد تا به صورت اختیاری یک صف برای دریافت نتایج توسعه یافته مشخص کند.
اگر توسط تابع سطح بالا مشخص نشده باشد، تابع سطح پایین آنها را در شیء صفی که ایجاد کرده و در یک ویژگی تابع ذخیره می کند، قرار می دهد.
کد زیر این کار را انجام می دهد.
توجه داشته باشید که وقتی این تابع با یک صف مشخص شده توسط تماس گیرنده استفاده می شود، ویژگی حاوی صف پیش فرض را تغییر نمی دهد.
این به تماسگیرندگان در چندین مکان در کد تماس اجازه میدهد از صفهای مختلف استفاده کنند یا از صف پیشفرض استفاده کنند، همانطور که در ویژگی تابع مشخص شده است:
def fqap(fparms): # ultimate result is put to a queue object whose value is in a function parameter or, if not specified, in an attribute.
ultimate_result=None
# Queue object attribute naming convention: <invoked_function_name>.sidestreamoutq
if not hasattr(fqap, "sidestreamoutq"): fqap.sidestreamoutq = queue.Queue() # only create the default queue attribute once. It might never be used.
q2use = fqap.sidestreamoutq
for p in fparms: # find and use the first parameter that is a Queue or LifoQueue, if there is one.
if p.__class__.__name__ in ["Queue", "LifoQueue"] :
q2use=p
break
# do useful things
ultimate_result=["this", "that", 42, "and the other thing", fparms]
fqap.mrrv=ultimate_result
q2use.put(ultimate_result)
result_to_return=42
return result_to_return
هنگام استفاده از راه حل های مبتنی بر صف باید به چند نکته توجه داشته باشید:
حافظه: ورودی های صف در حافظه مجازی وجود دارد. اگر نتایج توسعهیافته در یک صف قرار گیرند، اما هرگز بازیابی نشدند، پس از هزاران یا میلیونها تماس، در نهایت حافظه پردازش یا سیستم تمام میشود و از کار میافتد (مگر اینکه ابتدا فرآیند به پایان برسد). چه با خاتمه منظم و چه به دلیل خرابی، در آن نقطه هر عنصر صفی که بازیابی نشده باشد کنار گذاشته می شود.
چند نام مستعار برای یک صف: تغییر مبتنی بر ویژگی یک شی صف دارد بدون توجه به اینکه تابع چند نام مستعار دارد. هنگامی که یک تابع سطح بالا یک صف مبتنی بر ویژگی را تخلیه می کند، حلقه تمام نتایج را در آن صف دریافت می کند، حتی اگر آنها با فراخوانی تابع تحت نام مستعار دیگری در آنجا قرار گرفته باشند.
در مثال راه حل شماره 3 بالا، f.sidestreamoutq.get()
در حال خواندن از همان صف است g.sidestreamoutq.get()
.
مفهوم این است که اگر تماس هایی وجود داشته باشد L(f,...)
و به دنبال آن تماس هایی به L(g,...)
، به دنبال آن یک حلقه تا خالی از f.sidestreamoutq.get()
، سپس آن get
حلقه تمام نتایج قرار داده شده در آنجا توسط همه فراخوانی ها را دریافت می کند f
و یا به g
.
راه حل شماره 5: روکش ساده پایتون
اگر تابع سطح پایین از یک کتابخانه یا جای دیگری آمده باشد، بنابراین نمی توانید یا نباید آن را تغییر دهید، چه کاری انجام می دهید؟
این راه حل از مفهوم لفاف استفاده می کند.
wrapper صف و صفت ها را تنظیم می کند، تابع سطح پایین را فراخوانی می کند، آنچه را که لازم است با نتیجه انجام می دهد و بازگشتی را که کتابخانه نیاز دارد را فراهم می کند.
این راه حل از مفهوم عمومی یک wrapper استفاده می کند، اما از مفهوم پیشرفته Python از “wrappers تزئینی” استفاده نمی کند.
این راه حل واقعاً تفاوت اساسی با قرار دادن نتایج در یک صفت یا صف ندارد.
با این حال، کدی را که اگر بتوانید آن را تغییر دهید، داخل تابع هدف قرار میگیرد، خارجی میکند.
نتیجه یک تابع جدید است که به سادگی تابع هدف را میپیچد:
#Solution #5: Simple python wrapper
import queue
from LibraryL import L
# The function we cannot change
def f(fparms=[]):
return ["f returns",fparms]
# This wrapper is specific to function f.
def wrapped_f(fparms): # wraps f. Useful when f's source code cannot be edited.
# This function invokes function f and then
# deposits the returned results into both an attribute and a queue.
# Queue object attribute naming convention:
# <wrappingfunction_name>.sidestreamoutq
# Result object attribute naming convention:
# <wrappingfunction_name>.mrrv
#
# For higher performance if fwrap will be called millions of times,
# move the following statementinto a higher level function,
# so that the tests are only done once each.
if not hasattr(wrapped_f, "sidestreamoutq"): wrapped_f.sidestreamoutq=queue.Queue()
#
# Just call f
ultimate_result=f(fparms)
# Both leave the result in the attributes of this function and put it to the queue
wrapped_f.mrrv=ultimate_result
wrapped_f.sidestreamoutq.put(ultimate_result)
if False: # The following statements would create and set attributes in the target function.
# This injection is not considered a good programming practice if not necessary, and here it is not.
if not hasattr(f, "sidestreamoutq"): f.sidestreamoutq=wrapped_f.sidestreamoutq # Same Queue. Only set it if it does not exist
f.mrrv=ultimate_result
# endif if False
result_to_return=42
return result_to_return
def test_wrapped_f():
print(["object f: ", f ])
print(["use f directly:", f("use f directly:")])
# sidestream f
print(["wrapped f object: ", wrapped_f])
print(["use wrapped_f directly:", wrapped_f("use sidestreamed_f directly:")])
print(["wrapped f Attribute:", wrapped_f.mrrv])
print(["wrapped f Q entry:", wrapped_f.sidestreamoutq.get()])
# print(["direct f Attribute:", f.mrrv])
# print(["direct f Q entry:", f.sidestreamoutq.get()])
print(["wrapped_f through L returns:", lwfr:=L(wrapped_f,["use L(sidestreamed_f):"])])
print(["wrapped f Attribute:", wrapped_f.mrrv])
print(["wrapped f Q entry:", wrapped_f.sidestreamoutq.get()])
return
test_wrapped_f()
quit()
""" Expected Output, but with different addresses:
['object f: ', <function f at 0x7fbf5f57fd90>]
['use f directly:', ['f returns', 'use f directly:']]
['wrapped f object: ', <function wrapped_f at 0x7fbf5ea36cb0>]
['use wrapped_f directly:', 42]
['wrapped f Attribute:', ['f returns', 'use sidestreamed_f directly:']]
['wrapped f Q entry:', ['f returns', 'use sidestreamed_f directly:']]
['wrapped_f through L returns:', 'int']
['wrapped f Attribute:', ['f returns', ['use L(sidestreamed_f):']]]
['wrapped f Q entry:', ['f returns', ['use L(sidestreamed_f):']]]
"""
این راه حل مشابه Solution #3 Queue است که دقیقاً یک صف برای wrapper وجود دارد.
برای اینکه استفادههایی برای یک تابع هدف داشته باشید و در عین حال نتایجی داشته باشید که به صفها و ویژگیهای مختلف میروند، میتوانید توابع wrapper اضافی را تعریف کنید، مانند wrapped_f2()
، wrapped_f3()
، و غیره.
هر کدام تقریباً یکسان خواهند بود wrapped_f()
، به جز تغییر همه نمونه های “wrapped_f” به “wrapped_f2″، “wrapped_f3″، و غیره.
راه حل شماره 6: لفاف دکوراتور پایتون
اگر عملکردهای سطح پایین بسیار زیادی وجود داشته باشد، چه کار می کنید، بنابراین تغییر همه آنها یک کابوس تعمیر و نگهداری خسته کننده خواهد بود؟
اگر بسیاری از توابع سطح پایین از کتابخانه ها یا جاهای دیگر می آیند، بنابراین شما نمی توانید یا نباید آنها را تغییر دهید، چه کار می کنید؟
اگر لازم باشد چندین صفت نتیجه برای برخی از توابع وجود داشته باشد، چه کاری انجام می دهید؟ به عنوان مثال، یک تابع از چندین مکان در کد یا از چندین رشته فراخوانی می شود.
این راه حل نیز از مفهوم لفاف استفاده می کند، اما متفاوت از بالا.
راه حل شماره 5 مشکل تکثیر کد را دارد. این نیاز به نوشتن یک تابع اضافی برای هر استفاده مستقل از هر تابع سطح پایین دارد.
در مقیاس، این نیز به یک کابوس تعمیر و نگهداری مستعد خطا تبدیل می شود. می توان آن را به عنوان “آسان برای یادگیری، اما آسان برای استفاده در مقیاس” طبقه بندی کرد.
درعوض، این راهحل بستهبندیها را در زمان اجرا از یک مجموعه کد ژنریک wrapper-generic تولید میکند. این اجازه می دهد تا صف ها و ویژگی های متعدد بدون تغییر عملکرد سطح پایین زیرین.
هر استفاده از ژنراتور برای ایجاد یک پوشش دیگر تنها به یک خط کد ساده نیاز دارد، مانند sidestreamed_f2=create_sidestream_wrapper_func(f)
.
تکنیکی که در اینجا به کار می رود «دکوراتور» پایتون نامیده می شود. من دکوراتورها را به عنوان پایتون پیشرفته دسته بندی می کنم.
دکوراتورها در دو مقاله freeCodeCamp توضیح داده شده اند، یکی توسط Roy Chng و دیگری توسط Brandon Wallace. لطفا موضوع را از طریق آن مقالات مطالعه کنید.
در اینجا ما فقط از تکنیک استفاده خواهیم کرد. می توان آن را به عنوان “استفاده آسان در مقیاس، اما آسان برای یادگیری” طبقه بندی کرد.
در اینجا، مولد wrapper، از جمله کد تعریف کننده wrapper خاص و ایجاد صف آن، تنها 8 خط کد اجرایی در create_sidestream_wrapper_func
، بدون احتساب print
عباراتی که در حین آزمایش حذف خواهید کرد:
#!/bin/python3
#Solution #6: Python decorator wrapper generator
import queue
from LibraryL import L
# function we cannot change
def f(fparms=[]):
return ["f returns",fparms]
def create_sidestream_wrapper_func(myfunc):
# Usage
# Create the function: sidestreamed_f=create_sidestream_wrapper_func(f)
# fparm=["This", "that", "and the other thing"]
# Use the created function:
# direct: x=sidestreamed_f(fparm)
# indirect: x=L(sidestreamed_f,fparm,lparms)
# Every time this is called, it creates a new instance of the wrapped function
def sidestream_wrapped_func(parms):
print("I am going to execute: ", myfunc.__name__)
ultimate_result=myfunc(parms)
# The queue was created when the wrapper function is created
sidestream_wrapped_func.sidestreamoutq.put(ultimate_result)
sidestream_wrapped_func.mrrv=ultimate_result
return 42
sidestream_wrapped_func.sidestreamoutq=queue.Queue()
print(["sidestream_wrapper_func = ",sidestream_wrapped_func, " ; with queue at = ",id(sidestream_wrapped_func.sidestreamoutq)])
return sidestream_wrapped_func
def test_create_and_use_sidestream_wrapper_func():
print(["use f directly:", f("use f directly:")])
# sidestream f
sidestreamed_f=create_sidestream_wrapper_func(f) #suitable for use whether f is in some library or inline here, either way.
sidestreamed_f2=create_sidestream_wrapper_func(f) # creates another sidestreamed function with its own result attribute and queue
print(["defined the sidestreamed f", sidestreamed_f])
print(["defined the sidestreamed f2", sidestreamed_f2])
print(["use sidestreamed_f directly:", sidestreamed_f("use sidestreamed_f directly:")])
print(["use sidestreamed_f2 directly:", sidestreamed_f2("use sidestreamed_f2 directly:")])
print(["wrapped f Attribute:", sidestreamed_f.mrrv])
print(["wrapped f2 Attribute:", sidestreamed_f2.mrrv])
print(["wrapped f Q entry:", sidestreamed_f.sidestreamoutq.get()])
print(["wrapped f2 Q entry:", sidestreamed_f2.sidestreamoutq.get()])
test_create_and_use_sidestream_wrapper_func()
quit()
""" output should be something this, except that addresses will be different.
['use f directly:', ['f returns', 'use f directly:']]
['sidestream_wrapper_func=", <function create_sidestream_wrapper_func.<locals>.sidestream_wrapped_func at 0x7efffca3edd0>, " ; with queue at=", 139637920186032]
["sidestream_wrapper_func=", <function create_sidestream_wrapper_func.<locals>.sidestream_wrapped_func at 0x7efffca3ee60>, " ; with queue at=", 139637920175280]
["defined the sidestreamed f', <function create_sidestream_wrapper_func.<locals>.sidestream_wrapped_func at 0x7efffca3edd0>]
['defined the sidestreamed f2', <function create_sidestream_wrapper_func.<locals>.sidestream_wrapped_func at 0x7efffca3ee60>]
I am going to execute: f
['use sidestreamed_f directly:', 42]
I am going to execute: f
['use sidestreamed_f2 directly:', 42]
['wrapped f Attribute:', ['f returns', 'use sidestreamed_f directly:']]
['wrapped f2 Attribute:', ['f returns', 'use sidestreamed_f2 directly:']]
['wrapped f Q entry:', ['f returns', 'use sidestreamed_f directly:']]
['wrapped f2 Q entry:', ['f returns', 'use sidestreamed_f2 directly:']]
"""
تابع مولد و توابع تولید شده دارای چند عبارت چاپی هستند که می خواهید پس از استفاده و آزمایش کد، نظر بدهید.
مهم است که در خروجی نمونه توجه کنید که آدرس توابع sidestreamed_f
و sidestreamed_f2
و اشیاء صف آنها متفاوت است.
به طور مشابه، mrrv
ویژگی ها نیز در آدرس های مختلف هستند:
sidestreamed_f ... at 0x7efffca3edd0>, ' ; with queue at=", 139637920186032]
sidestreamed_f2 ... at 0x7efffca3ee60>, " ; with queue at=", 139637920175280]
این تقویت می کند که چگونه این دو لفاف اشیاء کاملاً مجزا هستند.
مشاوره در مورد انتخاب راه حل برای استفاده
این آموزش چندین راه حل و تنوع را به شما نشان داده است.
کدام یک یا یکی را باید استفاده کنید؟
ملاحظات، معیارهای انتخاب و مفاهیم چیست؟
این بخش ترکیبی از اصول کلی مهندسی نرم افزار و مشخصات موضوع این مقاله را ارائه می دهد.
ابتدا محلول هایی را که اصلا استفاده نکنید حذف کنید.
از راه حل متغیر سراسری استفاده نکنید. منسوخ شده است.
تا زمانی که راه حل شماره 6: لفاف دکوراتور پایتون را متوجه نشدید، از آن استفاده نکنید.
اگر نمیتوانید کد را در تابع سطح پایین تقویت کنید، از یک wrapper استفاده کنید.
بسته بندی می تواند بر اساس راه حل شماره 5: بسته بندی ساده پایتون برای هر تابع خاص، یا راه حل شماره 6: لفاف تزئین کننده پایتون برای تولید لفاف باشد.
هر جا ممکن است از راه حل ویژگی استفاده کنید. راه حل مشخصه یک روش کدگذاری عمومی بسیار خوب را ارائه می دهد که به آن حافظه می گویند.
من پیشنهاد میکنم برای همه توابع در همه جا، حافظهسازی نتیجه عملکرد را به استانداردهای کدگذاری پایتون گروه خود اضافه کنید.
شاید از یک قرارداد نامگذاری ویژگی متفاوت استفاده کنید. من استفاده می کنم .mrrv
، که مخفف “مقدار آخرین نتیجه” است.
به عنوان مثال، یک تابع m
چیزی شبیه به:
def m():
result=[1,2,3]
m.mrrv=result
return m.mrrv.copy()
سپس در کد خود در جاهای دیگر از نتیجه حفظ شده استفاده کنید و دوباره استفاده کنید، مانند با print([sum(m())," = sum(",m.mrrv,")"])
کدام خروجی ها [6, ' = sum(', [1, 2, 3], ')']
.
با این حال، از ترتیب ارزیابی پایتون و اتصال کوتاه عبارات منطقی آگاه باشید تا مطمئن شوید که تابع قبل از استفاده از آن فراخوانی شده است. mrrv
.
به عبارت دیگر، همیشه یک ویژگی تابع را برای مقداری که برگردانده می شود تنظیم کنید، و سپس یک کپی از آن را برگردانید، حتی اگر مشکل دور زدن کتابخانه این آموزش را نداشته باشید.
آیا یک فراخوانی به تابع کتابخانه چندین بار تابع سطح پایین را فراخوانی می کند و آیا برنامه سطح بالا به نتیجه هر کدام نیاز دارد؟
اگر چنین است، از یک راه حل مبتنی بر صف به جای یا علاوه بر راه حل ویژگی استفاده کنید.
آیا مشکل دور زدن کتابخانه شما برای بسیاری از توابع سطح پایینتر که باید چندین نتیجه را از طریق یک یا چند صف برای هر تابع ارسال کنند، اعمال میشود؟
اگر راه حل شماره 6: پوشش دکوراتور پایتون را می دانید، از آن استفاده کنید. در غیر این صورت، از Solution #4: Queues استفاده کنید.
آیا بهتر است برای هر نیاز در پروژه از ساده ترین راه حل استفاده کنیم یا تا حد امکان در کل پروژه از این راه حل های مختلف استفاده کنیم؟
من راه کمی که ممکن است را توصیه می کنم. برای من، این به معنای استفاده از راه حل شماره 1: ویژگی تابع و راه حل شماره 6: پوشش دکوراتور پایتون است.
این راه حل ها چقدر خوب هستند؟
بنابراین ممکن است تعجب کنید – آیا این راه حل های توصیه شده با اصول راهنمای بالای این آموزش مطابقت دارند؟
در اینجا مواردی وجود دارد که باید در نظر بگیرید:
- تضادها: با استفاده از قرارداد نامگذاری از تضادها و تداخل نامگذاری اجتناب کردیم.
- اختیاری: دو راه حل پیشنهادی جایگزین، ویژگی و صف، در صورت عدم نیاز به نتایج توسعه یافته از تابع سطح پایین، به کد اضافی در تابع سطح بالا نیاز ندارند. اگر نتایج توسعه یافته برای همیشه بدون تخلیه شدن جمع شوند، راه حل های صف ممکن است خراب شوند.
- جفت: چندین راه حل، جفت کد منبع صریح با نام های رمزگذاری شده سخت و یک قرارداد نام گذاری را حذف کردند. در یک راه حل، تماس گیرنده سطح بالا ابتدا یک یا چند صف مقصد نتیجه را تعریف کرد. سپس تماس گیرنده یک مرجع شی به یک صف در پارامترهای ارسال شده از طریق واسطه به تابع سطح پایین تر ارسال می کند.
خلاصه
همانطور که در این آموزش آموختید، می توان نتایج را از یک تابع سطح پایین منتقل کرد تا توسط یک تابع سطح بالاتر یا برنامه اصلی انتخاب شود.
این به شما امکان می دهد با استفاده از یک جریان جانبی خارج از باند، هرگونه واسطه را دور بزنید.
چندین راه حل در اینجا ارائه شده است. در پروژه یا پروژه های خود تا حد امکان از تعداد کمتری استفاده کنید.
همه این راه حل ها از پایتون رویه ای استفاده می کردند. آنها همچنین برای پایتون شی گرا با کلاس ها قابل استفاده هستند.
از یک ویژگی در تابع سطح پایین استفاده کنید یا از یک صف استفاده کنید.
از متغیر سراسری استفاده نکنید.
تیتراژ تصویر: مار بزرگ اسکات که کنفدراسیون را مسدود می کند. (ج) 1861
منتشر شده در 1402-12-26 14:44:04