۱۲/۲۱/۱۳۸۸

معرفی یک ماژول > doctest


doctest یکی از ماژولهای استاندارد پایتون است که به منظور سهولت در تست کردن کد پایتون (کپی شده از python shell) طراحی شده است. به طور کلی این ماژول برای سه منظور ممکن است استفاده شود:

  1.  بررسی به روز بودن docstring های یک ماژول با استفاده از مقایسه ی نتایج تست های ثبت شده در docstring ها با مقدار بدست آمده هنگام تست 
  2.  بررسی صحت درست کارکردن مثالهای موجود در یک فایل تست
  3. نوشتن اسناد آموزشی برای یک  package
 در این پست به شکلی ساده برخی از قابلیتهای این ماژول توضیح داده میشود. در واقع تنها به رابط اصلی و پایه ی آن (Basic API) میپردازیم.

تابع فاکتوریل را در نظر بگیرید:

def factorial(n):
    if n<0: return "Only a natural number!"
    return (1 if n==0 else n*factorial(n-1))

حالا امتحانش میکنیم:

>>> factorial(5)
120
>>> factorial(3)
6
>>> factorial(-2)
'Only a natural number!'
>>>  
میتوانیم این نتایج را به عنوان توضیحی درمورد خروجی این متود در docstring آن قرار دهیم:

def factorial(n):
    '''
    Returns factorial of a natural number
    for example:
    >>> factorial(5)
    120
    >>> factorial(3)
    6
    >>> factorial(-2)
    'Only a natural number!'
    >>>

    '''    
    if n<0: return "Only a natural number!"    
    return (1 if n==0 else n*factorial(n-1))

این نتایج به درستی محاسبه شده اند. به عبارت دیگر هرگاه این تابع به ازای مقادیری که در تست های موجود در docstring آن آمده اجرا شود همان خروجی هایی که در docstring آمده نمایان خواهند شد.
این تابع را در ماژول test.py ذخیره میکنیم.
برای اطمینان حاصل کردن از این درستی کافی است متود testmod را در انتهای ماژول test.py صدا بزنیم:

import doctest
doctest.testmod()
دوباره ماژول را اجرا میکنیم...
.
.
.
هیچ اتفاقی نمیافتد! خوب این یعنی این که نتایج موجود در تمام docstring های موجود در test.py به درستی محاسبه شده اند ☺
میتوان جزییات مربوط به این تست را این گونه دید:



حال کمی متود factorial را تغییر میدهیم تا در صورت بروز خطاهای
احتمالی در ورودی خروجی مناسبی بازگرداند:

def factorial(n):
    '''
    Returns factorial of a natural number
    for example:
    >>> factorial(5)
    120
    >>> factorial(3)
    6
    >>> factorial(-2)
    'Only a natural number!'
    >>>
    '''
    if n<0: return "Not negative integers!"
    if int(n) is not n:
        return "Must be an integer!"
    return (1 if n==0 else n*factorial(n-1))

دقت کنید doctest یک docstring را اینگونه برداشت میکند:

>>> import test #test.py contains the factorial method
>>> print doctest.testsource(test,'test.factorial')
# Returns factorial of a natural number
# for example:
factorial(5)
# Expected:
## 120
factorial(3)
# Expected:
## 6
factorial(-2)
# Expected:
## 'Only a natural number!'

>>>

دوباره ماژول را اجرا میکنیم...

>>>
**********************************************************************
File "C:\Users\SEVEN\Documents\test.py", line 9, in __main__.factorial
Failed example:
    factorial(-2)
Expected:
    'Only a natural number!'
Got:
    'Not negative integers!'
**********************************************************************
1 items had failures:
   1 of   3 in __main__.factorial
***Test Failed*** 1 failures.
>>>

نتیجه ی اخلاقی اینجا این است که هرگاه هرچیزی (مثلا یک متود) را تغییر میدهید، docstring مربوطه را نیز به روز کنید!☻
با استفاده از testmod تمام docstring های موجود در یک ماژول جستجو میشوند.(شیءهایی که import شده اند جستجو نمیشوند)

حالا میخواهیم یک فایل آموزشی درباره ی این ماژول درست کنیم. یک فایل txt با نام how_to یعنی how_to.txt میسازیم با این محتوا:

This is a tutorial documentation for test.py module

this module contains the following objects:
    factorial method

That's it for now :D

how to use factorial method:

first you have to import it like this
>>> from test import factorial

then you can use it this way:
>>> factorial(5)
120

if you enter a negative number, it can handle it:
>>> factorial(-3)
'Not negative integers!'

also if you enter a non-integer, you'll receive this message:
>>> factorial(5.5)
'Must be an integer!'

حالا میتوانیم این فایل را با استفاده از متود testfile تست کنیم:

>>> doctest.testfile("C:\Users\SEVEN\Documents\\how_to.txt")
TestResults(failed=0, attempted=4)

همانطور که دیدید testfile تمام خطوط قابل اجرا در فایل how_to را بررسی کرد و در انتها نشان داد که همگی به درستی نوشته شده بودند.

دو متود testmod و testfile ساده ترین راههای استفاده از doctest هستند. از آنجایی که این پست هم تنها قصد معرفی این ماژول را داشت بیش از این پیش نمیرویم :دی

اطلاعات بیشتر درباره ی این دو متود را اینجا خواهید یافت.
مزیت ها و اشکالات doctest نیز به طور خلاصه اینجا گردآوری شده اند.

موفق باشید ☺

۱۲/۱۳/۱۳۸۸

برنامه نویسی شیء گرا - قسمت ششم > متودهای خاص


تا اینجا با متودهایی چون __init__ و __str__ آشنا شدید. این متودها معمولا مستقیم صدا زده نمیشوند و در مواقع خاصی عمل میکنند. مثلا __str__ هنگام پرینت کردن یک شیء صدا زده میشود. انواع دیگری از این متودهای خاص وجود دارد که در این پست یکی دیگر از آنها را معرفی میکنم.


> object.__repr__() or repr(object) or `object`

repr مخفف representation یا همان "نمایش" یک شیء است. برای این که بفهمیم هر شیء چه چیزی را نمایش میدهد کافیست نام آن را در Python Shell صدا بزنیم:

>>> a='I am string of a'
>>> a
'I am string of a'
>>> b=12.3
>>> b
12.300000000000001
>>> c=range(10)
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

هنگامی که یک شیء جدید تعریف میکنیم به حالت پیش فرض، هر نمونه از آن، نوع و مکان خود در حافظه را نمایش میدهد:
>>> class Point(object): pass

>>> p=Point()
>>> p                                               <__main__.Point object at 0x02012290>
>>> 

خروجی متود repr همواره رشته ایست از آنچه شیء نمایش میدهد:

>>> repr(p)
'<__main__.Point object at 0x02012290>'
>>> p.__repr__()
'<__main__.Point object at 0x02012290>'
>>>
>>> b=12.3
>>> repr(b)
'12.300000000000001'
>>> c=range(10)
>>> c.__repr__()
'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'
>>> d=set([1,2,3,3])
>>> `d`
'set([1, 2, 3])'
>>>


خب، پس همانند متود __str__ خروجی این متود یک رشته است. در صورتی که متود __str__ برای یک شیء تعریف نشده باشد ولی __repr__ تعریف شده باشد، __repr__ به جای __str__ نیز صدا زده میشود (عکس این برقرار نیست!)

>>> class Point(object):
        def __init__(self,x,y):
       
    self.x=x
       
    self.y=y
   
    def __repr__(self):
       
    return str(self.x)+','+str(self.y)

   
>>> p=Point(2,3)
>>> print p
2,3
>>> str(p)
'2,3'
>>> repr(p)
'2,3'
>>> p
2,3
>>> 

همانطور که دیدید حالا که ما متود __repr__ برای Point را override کردیم (یعنی تعریف آنرا تغییر دادیم)، دیگر p ، نوع و مکان خود در حافظه را نمایش نمیدهد. ما تعیین کردیم که هر Point مختصات خود را نمایش دهد.
حالا متود __str__ را هم برای Point تعریف میکنیم و __repr__ را هم کمی تغییر میدهیم:

>>> class Point(object):
        def __init__(self,x,y):
       
    self.x=x
       
    self.y=y
       
   
    def __str__(self):
       
    return str(self.x)+','+str(self.y)
   
    def __repr__(self):
       
    return 'Point(x=%d,y=%d)'\
                   %(self.x,self.y)

   
>>> p=Point(2,3)
>>> print p
2,3
>>> str(p)
'2,3'
>>> p
Point(x=2,y=3)
>>> repr(p)
'Point(x=2,y=3)'
>>>

حالا که ما __str__ را هم override کرده ایم، دیگر متود repr به جای آن صدا زده نمیشود، در ضمن ما تعیین کردیم که هر Point ، نوع و مختصات خود را نمایش دهد. به این ترتیب هر Point نمایشی ساده و قابل فهم دارد.
علاوه بر قابل فهم تر کردن نمایش یک شیء، یکی دیگر از فواید متود repr، قابلیت شناخته شدن یک شیء برای استفاده در متود eval است.

با استفاده از متود eval میتوان یک دستور قابل اجرا که در قالب یک رشته نوشته شده را اجرا کرد:

>>> eval('abs(-1)')
1
>>> a=eval('range(10)')
>>> a==range(10)
True
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> eval('1+1')
2

ورودی متود eval همانند خروجی متود repr یک رشته است.
اشیاء از پیش تعریف شده در پایتون مثل اعداد، رشته ها، لیست ها و ... طوری نمایش داده میشوند که با استفاده از متود eval میتوان آنها را دوباره تولید کرد (کپی کرد):

>>> a=2
>>> b=eval(repr(a))
>>> b
2
>>> a==b
True
>>> repr(a)
'2'
>>> lst=range(10)
>>> repr(lst)
'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'
>>> lst_copy=eval(repr(lst))
>>> lst_copy
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> p=Point(2,3)
>>> p
Point(x=2,y=3)
>>> p2=eval(repr(p))
>>> p2
Point(x=2,y=3)
>>> p2.x
2
>>> p2.y
3
>>> 

همانطور که دیدید من نیز متود __repr__ را به گونه ای override کرده ام که در صورت evaluate کردن نمایش یک Point، یک نمونه با همان مشخصات تولید شود.

با استفاده از متود repr میتوان هر شیء را به یک رشته تبدیل کرد. میتوان از این رشته ها در انواع تست ها، کپی کردن، ذخیره سازی در پایگاههای داده و ... بهره جست.
برای انواع خاصی از اشیاء همچون اعداد صحیح، لیست ها و ...، خروجی این متود همان خروجی متود str است. 

>>> str(1.0)==repr(1.0)
True
>>> str(3.2)==repr(3.2)
False
>>> print str(3.2),repr(3.2)
3.2 3.2000000000000002
>>> str(range(10))==repr(range(10))
True
>>> 
>>> print str('Good \n Luck')
Good
 Luck
>>> print repr('Good \n Luck')
'Good \n Luck'


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

موفق باشید ☺☻