فصل چهار

ماژول ها

اگر از مفسر پایتون خارج شده اید و دوباره وارد آن شده اید تعریف های شما (مثل توابع و متغییر ها ) از دست می روند بنا براین اگر خواستبد برنامرو قدری طولانی تر بنویسید بهتر است که از ادیتور متنی  استفاده کنید که برای ورود و ترجمه و اجرا ی کدتون آماده شده این شناخته ترین راه خلق اسکریپته همچنان که برنامتون بزرگ تر می شه شاید  بخواهید اون را در چندین فایل برای حفظ و پشتیبانی جدا از هم نگهدارید و ممکنه که بخواهید از توابعی که به صورت دستی در چندین برنامه دیگه نوشته اید بدون کپی تعاریف آن دوبار توی تمام برنامه ها استفاده کنید

برای پشتیبانی از این هدف یعنی استفاده از توابع و متغییرهای نوشته شده در فایل های دیگر پایتون ، درون اسکریپت جاری پایتون ، پایتون  راه حل هایی رو پیش بینی کرده. که به شما امکان استفاده از تعریف شده های یک ماژول در ماژول ها و حتی برنامه اصلی رو می دهد.

ماژول یک فایل شامل تعاریف پایتون و جملات است . اسم فایل همان اسم ماژول است  که پسوند  py.  اضافه می شود داخل یک ماژول ,اسم ماژول  (همچون یک رشته ) مثل مقدار متغییر سراسری  __name__   در دسترس است برای مثال  از ادیتور متنی مورد علاقتون ( مثلا notepad ) استفاده کنید و یک فایل با نام fibo.py  داخل دایرکتوری جاری ( درون فایل برنامه پایتون -فایل lib ) با محتویات زیر ایجاد کنید :

def fib(n):    # jomalate fibo nachi ra chap mikonad
    a, b = 0, 1
    while b < n:
        print(b, end=' ')
        a, b = b, a+b
    print()

def fib2(n): # return Fibonacci series up to n
    result = []
    a, b = 0, 1
    while b < n:
        result.append(b)
        a, b = b, a+b
    return result

حالا وارد مفسر پایتون بشین و ماژول فیبوناچی را وارد کنید مثل زیر :

>>> import fibo

اسامی توابع تعریف شده در ماژول دیگر وارد صفحه ی مفسر نمی شود تنها اسم ماژول وارد می شود .با استفاده از اسم ماژول میشه به توابع دسترسی پیدا کرد :

>>> fibo.fib(1000)
۱ ۱ ۲ ۳ ۵ ۸ ۱۳ ۲۱ ۳۴ ۵۵ ۸۹ ۱۴۴ ۲۳۳ ۳۷۷ ۶۱۰ ۹۸۷
>>> fibo.fib2(100)
[۱, ۱, ۲, ۳, ۵, ۸, ۱۳, ۲۱, ۳۴, ۵۵, ۸۹]
>>> fibo.__name__
'fibo'

اگر مایل به استفاده از یک تابع بودید می تونید اون رو به یک متغییر اختصاص دهید و استفاده  کنید :

>>> fib = fibo.fib
>>> fib(500)
۱ ۱ ۲ ۳ ۵ ۸ ۱۳ ۲۱ ۳۴ ۵۵ ۸۹ ۱۴۴ ۲۳۳ ۳۷۷

ماژول می تونه جملات را نیز به خوبی تعریف توابع در بر بگیره این جملات فقط در نخستین بار که ماژول بعضی جاها وارد می شه  اجرا می شه [۱]

همه ماژول ها جدول نمادی ( صفحه ای شامل کدهای مربوط به ان ماژول) مخصوص خود را دارد که مثل جدول نمادی  سراسری ، تمام توابع  مربوط به خودشون داخل این جدول تعریف می کنند .بنابراین نویسنده ی ماژول می تواند متغییر های سراسری  را داخل ماژول به کار بگیرد بدون اینکه نگران برخورد تصادفی متغییر ها با یک دیگر باشد.

ماژول ها می توانند ماژول های دیگر را وارد کنند .این کار خیلی رایج است اما، لازم نیست که در ایتدای آغاز هر ماژول عبارت    import   را قرار بدهیم.اسم های ماژول های وارداتی داخل جدول نمادی ماژول سراسری قرار داده شده اند

اینجا یک فراخوانی متفاوت از  جمله  داریم که اسم ها را از یک ماژول مستقیمن وارد جدول نمادی ماژول های ورودی می کند برای مثال :

>>> from fibo import fib, fib2
>>> fib(500)
۱ ۱ ۲ ۳ ۵ ۸ ۱۳ ۲۱ ۳۴ ۵۵ ۸۹ ۱۴۴ ۲۳۳ ۳۷۷

این مرسوم نیست که اسم ماژول از ورودی های شرکت کننده در جدول نمادی محلی باشد ( بنابراین در این مثال  fibo تعریف نشده است )

اینجا یک روش دیگه برای وارد کردن تمام اسامی به کار رفته در ماژول  ارائه شده است :

>>> from fibo import *
>>> fib(500)
۱ ۱ ۲ ۳ ۵ ۸ ۱۳ ۲۱ ۳۴ ۵۵ ۸۹ ۱۴۴ ۲۳۳ ۳۷۷

این تمام اسم ها را به جز آنهایی که با علامت ( _ ) شروع می شوند را دریافت می کند در اغلب موارد برنامه نویسان پایتون از این امکان استفاده نمی کنند چون که  مفسر را با یک مجموعه  ی ناشناخته از اسم ها آشنا می کنه احتمالن بعضی چیز ها رو که تعریف کردید را مخفی می کند

 

توجه :

برای دلایل اثر بخشی هر ماژول فقط یک بار وارد مترجم می شود بنابراین اگر شما ماژولتون رو تغییر بدهید  باید مفسر ( مترجم ) را ریست کنید یا اگر فقط  آن  یک ماژول است و شما می خواهید آن را تست کنید از   imp.reload()    استفاده کنید برای مثال :

import imp; imp.reload(modulename).

اجرا کردن ماژول ها همچون اسکریپت ها

وقتی که یک ماژول پایتون را اجرا می کنید

python fibo.py <arguments>

کد این ماژول اجرا خواهد شد درست مثل این که شما آن را وارد کرده اید اما با  وصل کردن  __name__   به  "__main__". که معنیش اینه که با اضافه کردن این کد در آخر ماژول خود :

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

شما می تونید  به خوبی ماژول های ورودی در مفسر،  فایل کاربردی همچون یک اسکریپت  بسازید ، زیرا کدی که خط فرمان را تکه تکه بررسی می کند در صورتی اجرا می شود که ماژول به عنوان یک فایل اصلی به اجرا درآمده باشد :

$ python fibo.py 50
۱ ۱ ۲ ۳ ۵ ۸ ۱۳ ۲۱ ۳۴

اگر ماژول وارد بشه کد آن اجرا ( دیده ) نمی شه :

>>> import fibo
>>>

جستوجوی مسیر ماژول

وقتی ماژولی که اسپم (  spam )  نامیده شده وارد می شود، مفسر داخل فایل های جاری ( جایی که برنامه نصب شده است ) دنبال فایلی که اسپم ( spam.py)  نامیده شده جستوجو می کند و سپس لیست فایل ها به وسیله متغیر محیظی پایتون پچ (   the environment variable PYTHONPATH)  مشخص و تعییین می شود

 نحو متغیر محیطی  PYTHONPATH   شبیه متغیر شل PATH  است.   PATH یک لیست از اسامی فایل ها است . وقتی که  PYTHONPATH  آماده نیست یا وقتی که فایل، آنجا پیدا نمی شه جستوجو داخل مسیر پیشفرض ادامه پیدا می کنه داخل یونیکس معمولا این مسیر  اینه  :/usr/local/lib/python

نکته :     path یعنی  مسیر  – خاصیتی که آدرس یک دایرکتوری را مشخص می کند

در واقع ماژول ها به وسیله ی لیستی ازدایرکتوری ها داده شده بوسیله متغیر sys جستجو می شود. مسیر از دایرکتوری شامل اسکریپت ورودی (یا دایرکتوری جاری) آغاز می شود.این به برنامه های پایتون اجازه می دهد که بدانند چه چیزی را در مسیر جستجوی ماژول تغییر یا قرار داده اند. توجه کنید که دایرکتوری جاری اسکریپت به عنوان مسیر جستجو اجرا شده است این مهم هستش که اسکریپت نامی مشابه نام یک ماژول استاندارد نداشته باشد یا اینکه تلاش نکند که اسکریپت را بعنوان ماژولی که وارد شده است بارگذاری کند. بطور کلی این ها خطایی می دهند. برای اطلاعات بیشتر بخش Standard Modules در راهنمای همراه زبان پایتون جستجحو کنید.

 

کامپایل فایل های پایتون

افزایش سرعت آغاز به کار برنامه های کوچیک که از ماژول های استاندارد زیادی استفاده می کنند اهمیت زیادی دارد ، اگر یک فایل که  spam.pyc نامیده میشه در دایرکتوری که  spam.py  هستش وجود داشته باشه، این فایل یک نسخه از محتویات کمپایل شده ی  spam.py است. زمان تغییر و اصلاح یک نسخه از spam.py به کار برده شده در خلق spam.pyc، در فایل spam.pyc ثبت می شه __ بنابراین اگر این زمان ثبت شده با آخرین تغییر مازول مطابقت نکند ، فایل pyc. ایگنور ( نادیده گرفته ) می شه

معمولا برای درست کردن فایل spam.pyc احتیاج نیست کار خاصی انجام بدهید هروقت که فایل spam.py با موفقیت کمپایل شد یک کوششی به منظور درست کردن یک نسخه کمپایل شده با عنوان spam.pyc صورت می گیرد اگر این تلاش نافرجام بماند  خطایی رخ نمی دهد اگه بدون هیچ دلیلی فایل به طور کامل نوشته نشه ، فایل spam.pyc نا معتبر شناخته میشود و بعدن ایگنور می شود محتویات فایل spam.pyc یک پلت فرم مستقل هستند ، بنابراین یک دایرکتوری ماژول پایتون می تونه روی سیستم های سخت افزاری متفاوت اجرا بشه

ماژول های استاندارد

پایتون با یک کتابخانه از ماژول های استاندارد نصب میشه که به آن کتابخانه مرجع می گویند بعضی ماژول ها در مفسر ساخته می شوند   این نوع ماژول ها امکان دسترسی به عملکرد هایی را فراهم می کند که جزو هسته ی زبان نیستند اما به هر حال جزو ساختمان زبان هستند  که برای افزایش کارایی در سیستم عامل های قدیمی ساخته شده اند مجموعه ماژول هایی از این قبیل یک گزینه پیکربندی دارند که بستگی به پلت فرم پایه دارد برای مثال ، ماژول  winreg   فقط برای سیستم های ویندوز تهیه شده است

یک ماژول منحصر به فرد که به مقداری توجه نیاز دارد :  sys  ،  که داخل تمام مفسر های پایتون ساخته می شود. متغییر های sys.ps1   و   sys.ps2  که رشته هایی را به منظور اعلان اولیه و ثانویه  تعریف می کنند :

sys.ps2  روی پایتون ۲.۷ مشاهده نکردم شحصا ولی توی راهنما نوشته شده . شاید دای

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

این دو متغییر فقط وقتی تعریف شده اند که مفسر داخل حالت فعل و انفعالی باشه.

متغییر sys.path   یک لیست از رشته ها (  مسیرهای جستوجوی مفسر برای پیدا کردن ماژول  ) را بر می گرداند

 

 

sys بطور پیشفرض با متغیر محیطی PYTHONPATH ارزش دهی می شود اگر PYTHONPATH تنظیم نشده باشد شما می توانید با عملگر استاندارد لیست تنظیمش کنید.

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

 dir() تابع

تابع توکار dir()  به منظور فهمیدن اسامی تعریف شده در ماژول  استفاده می شود و بک لیست مرتب شده از رشته ها رو بر می گرداند :

>>> import fibo, sys
>>> dir(fibo)
['__name__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
 '__stdin__', '__stdout__', '_getframe', 'api_version', 'argv',
 'builtin_module_names', 'byteorder', 'callstats', 'copyright',
 'displayhook', 'exc_info', 'excepthook',
 'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
 'getrecursionlimit', 'getrefcount', 'hexversion', 'maxint', 'maxunicode',
 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache',
 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
 'version', 'version_info', 'warnoptions']

 dir() بدون آرگومان ، اسامی { لیست ها ، متغییر ها ، ماژول ها ،…} که تا به حال تعریف کرده اید را بر می گرداند :

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir()
['__builtins__', '__doc__', '__file__', '__name__', 'a', 'fib', 'fibo', 'sys']

نکته این که این لیست شامل تمام انواع اسم میباشد : متغییرها،توابع،ماژول ها،لیست ها،مجموعه ها،دیکمشنری ها،…

 dir()  اسامی داخل توابع و متغییر ها را لیست نمی کند . اگر می خواهید این اسامی را لیست کنید ،آنها در ماژول استاندارد  builtins  تعریف شده اند :

توجه ماژول builtins رو در پایتون ۲.۷ مشاهده نکردم .  مستندات آموزشی سایت من بر پایه پایتون ۳ هستند 

>>> import builtins
>>> dir(builtins)

['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'Buffer Error', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'Environme ntError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'Generato rExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexErr or', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'P endingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', ' StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'Ta bError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'Unicod eEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserW arning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__deb ug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'chr', 'classmethod', 'compile', ' complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate ', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memory view', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property' , 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sort ed', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

پکیج ها ( بسته ها )

پکیج ها یک راه ساختن فضای نام ماژول پایتون ، با استفاده از ” اسم های ماژول نقطه دار ” هستند . برای مثال ، اسم ماژول  A.B به یک زیرماژول (  SUBmodule )  که B نامیده می شود, از یک بسته که A نامیده می شود اختصاص دارد.استفاده از این نوع ماژول های نقطه دار، نویسنده ی پکیج ها را  از سر در گمی و  نگرانی راجع به اسم های متغییر سراسری نجات می دهد.

فرض کنید می خواهید  کلیکسیونی از ماژول ها ( یک پکیج ) برای یک شکل کردن دسترسی به فایل ها و اطلاعات صوتی ، طراحی کنید . تعداد زیادی فرمت فایل صوتی وجود دارد ( فرمت فایل های صوتی معمولا با اضافه ای که همراه نام انهاست قابل تشخیص است مثلا :  .wav, .aiff, .au ) ، و ممکن است که  احتیاج _  به درست کردن و نگهداری کلیکسیون ماژول ها  برای تبدیل فرمت های گوناگون فایل ها_ پیدا کنید. همچنین عملکرد های متفاوت زیادی وجود دارد که شما مایل باشید آنها را سر اطلاعات صوتی اجرا کنید مثلا : میکس ، حالت اکو ، تابع تنظیمات اکولایزر ، حالت استریو

اینجا یک ساختار ممکن ، برای پکیج تون طراحی شده :

sound/                          Top-level package
      __init__.py              تنظیمات پکیج صوتی
      formats/                  یک زیر پکیج ، برای تبدیل فرمت های صوتی
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                    زیر پکیجی ، برای حالت های صوتی
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                 زیر پکیجی برای فیلترها
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

وقتی که پکیج را وارد مفسر می کنید ، پایتون  میان دایرکتوری های  sys.path  برای پیدا کردن زیر دایرکتوری پکیج وارد شده جستوجو می کند.

فایل های  __init__    نیاز دارند که مستقیما توسط پایتون ، همچون محتویات پکیج ها ، وارد بشوند ، این کار به وسیله ممانعت از دایرکتوری ها ، با یک اسم معمولی _  مثل یک رشته ساده _،انجام می گیرد و از اجرای غیر عمدی ماژول های معتبر پنهان در مسیر جستوجوی ماژول ها جلوگیری می کند ، در ساده ترین مورد ،   __init__   می تواند یک فایل خالی باشد

 

کاربران پکیج می تونن ماژول خاصی را از درون پکیج وارد کنند ، برای مثال :

import sound.effects.echo

. زیر ماژول    sound.effects.echo فراخوانی می کند . ممکن است که با اسم کامل به زیر ماژول رجوع شود :

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

یک راه چاره دیگه  برای ورود زیر ماژول،  استفاده از :

from sound.effects import echo

این یکی نیز زیر ماژول echo  را فراخوانی می کنه و بدون استفاده از پیشوند پکیج قابل دسترسی می باشد و به صورت زیر به کار می ره :

echo.echofilter(input, output, delay=0.7, atten=4)

هنوز راه های متفاوت دیگری هست که تابع مطلوب را وارد کنیم :

from sound.effects.echo import echofilter

دوباره ، زیر ماژول  echo در دسترس خواهد بود ، با فراخوانی تابع  echofilter()    به صورت مستقیم :

echofilter(input, output, delay=0.7, atten=4)

توجه کنید که موقعیکه از from package import item,   استفاده می کنید ، آیتم می تونه هر زیر ماژول یا زیر بسته ای از پکیج باشد یا می تونه اسم های دیگر تعریف شده د ر پکیج باشد ،مثل یک تابع ،کلاس یا متغییر . جمله  import  تست می کند که آیا آیتم داخل پکیج تعریف شده است یا نه، اگر نه که فرض می کنهآن یک ماژول است و تلاش می کنه آن را فراخوانی کنه، اگر در پیدا کردن ماژول شکست خورد ، یک  خطای  ImportError  می دهد .

برعکس، وقتی که دستوری مثل     import item.subitem.subsubitem  را استفاده می کنید ، آخرین آیتم می تواند یک ماژول یا یک پکیج باشد اما نمی تواند یک کلاس یا تابع یا متغییر تعریف شده در آیتم قبلی باشد.

 

import  * از یک پکیج ( بسته )

حالا  وقتی کاربر   from sound.effects import *   را  می نویسه، چه اتفاقی می افتد؟ این کد  زیر ماژول هایی را که در پکیج حاضر هستند پیدا می کند، و همه ی آن ها را وارد می کند. متاسفانه این عملیات به درستی روی پلاتفورم ویندوز عمل نمی کنه، جایی که فایل سیستمی همیشه اطلاعات دقیقی درباره وضعییت اسم فایل ندارد! در این پلاتفرم ها، راه تضمین شده ای برای شناخت  اینکه آیا یک فایل ECHO.PY باید به عنوان یک ماژول echo, Echo یا ECHO وارد شود وجود ندارد.(برای مثال، ویندوز ۹۵ شیوه ی ناراحت کننده ای از نمایش اسم فایل ها با یک حرف بزرگ اول دارد.)اسم فایل های مخصوص داس ۸+۳ مسئله جالب دیگری برای اسم های طولانی ماژول ایجاد می کند.

  برای  مولف پکیج، تهیه ی یک راهنمای صریح و روشن از بسته تنها راه حل است. عبارت import ، از قرارداد زیر استفاده می کند:

اگر، __ init__.py  یک پکیج کد لیستی را که__all__نامیده می شود، تعریف کند، این لیست (__all__) به یک لیست از اسامی ماژول هایی می پردازد که _وقتیfrom package import * با  مواجه شد_ وارد شده باشند . این بستگی به مولف پکیجی داره که این لیست رو وقتی که نسخه جدید منتشر شد به روز نگه می داره. مولف پکیج هم ممکنه تصمیم به پشتیبانی اون نگیرند، و استفاده از * را در پکیجشون  لازم نبینند . برای مثال، فایل sounds/effects/__init__.py  می تونه کد زیر رو شامل بشه:

__all__ = ["echo", "surround", "reverse"]

این می تونه این جوری معنی بده که from sound.effects import *زیر ماژولهای ۳ اسمی را که درون لیست است ,از پکیج SOUND وارد کن.

 

اگر   __all__ تعریف نشده باشد ، عبارت     from sound.effects import *  تمام زیر ماژول ها را از پکیج  sound.effects   داخل فضای نام جاری وارد نمی کند ،  فقط وارد شدن پکیج    sound.effects  را تضمین میکند در نتیجه تمام اسم های  ( زیر ماژول های صاف و ساده ) تعریف شده توسط ، __init__.py  در پکیج را نیز شامل می شود. همچنین     from sound.effects import *  زیر ماژول هایی را از پکیج که ساده و واضح به وسیله عبارت ورودی پیشین فراخوانی شده اند در بر می گیرد .  این کد را ملاحظه کنید :

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

داخل این مثال ،  echo و ماژول    surround داخل فضای نام جاری  وارد شده اند، زیرا  آنها داخل بسته ی sound.effects    وقتی که عبارت from...import   اجرا می شود، تعریف شده اند .(This also works when __all__ is defined.)

 

توجه کنید که در عمل  ورود *  از یک ماژول یا پکیج اغلب باعث کاهش خوانایی کد می شود هرچند که اگر انجام بگیرد می تواند در تایپ کردن ما در محیط  interactive یاری رسان باشد.

 

به یاد داشته باشید که، خطایی در استفاده ازfrom Package import specific_submodule! وجود ندارد! در حقیقت این نشانه گذاری توصیه و پیشنهاد شده است مگر آن که ماژول ورودی احتیاج داشته باشد از زیر ماژولی با اسم مشابه از پکیج های دیگری  استفاده کند.

 

2 دیدگاه برای «فصل چهار»

    1. آرگومان هایی که از توی خط فرمان به پایتون داده می شه رو در یک لیست بر می گردونه

      برای بهتر متوجه شدن پیشنهاد می کنم برنامه کوچیک زیر رو بنویسین و در فایل test.py ذخیره کنین:

      #test.py
      
      import sys
      print sys.argv
      

      حالا خط فرمان سیستمتون رو باز کنین و به مسیر فایل ذخیره شده برین و برنامتون رو با دادن آرگومان هایی از طریق خط فرمان اجرا کنین:

      python test.py "Hello world!"
      

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *