Wednesday, July 15, 2015

თავი 8 (8,13 - 8,14)

8.13 List arguments


როცა ფუნქციაში სიას შეიტან, ფუნქცია იღებს მითითებას სიაზე. ფუნქცია თუ შეცვლის სიის პარამეტრს, გამომძახებელი დაინახავს ცვლილებას. მაგალითად, delete_head შლის სიის პირველ ელემენტს:

def delete_head(t):
            del t[0]

აქაა გამოყენება:

>>> letters = ['a', 'b', 'c']
>>> delete_head(letters)
>>> print letters
['b', 'c']


პარამეტრი t და ცვლადი letters არიან  ერთი და იგივე ობიექტის aliased.
მნიშვნელოვანია განსხვავება ოპერაციებს შორის, რომელიც ცვლის სიას და რომელიც აკეთებს ახალ სიას, მაგალითად მეთოდი append ცვლის სიას, მაგრამ
+ ოპერატორი აკეთებს ახალ სიას.

>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> print t1
[1, 2, 3]
>>> print t2
None

>>> t3 = t1 + [3]
>>> print t3
[1, 2, 3]
>>> t2 is t3
False

ეს განსხვავება არის მნიშვნელოვანი, მაშინ როცა დაწერ ფუნქციებს, რომელმაც სავარაუდოდ უნდა შეცვალოს სია. მაგალითად ეს ფუნქცია არ შლის სიის თავს:

def bad_delete_head(t):
            t = t[1:]                                         # WRONG!

დაჭრის ოპერატორი აკეთებს ახალ სიას და ტოლობით t მას წარმოადგენს, მაგრამ აქედან არცერთს არ აქვს ეფექტი სიაზე, რომელიც იყო შეტანილი როგორც არგუმენტი. სხვანაირად შეგვიძლია დავწეროთ ფუნქცია რომელიც აკეთებს და აბრუნებს ახალ სიას. მაგალითად tail აბრუნებს ყველაფერს სიის პირველი ელემენტის გარდა:

def tail(t):
            return t[1:]

ეს ფუნქცია ორიგინალ სიას ტოვებს უცვლელს. აქაა თუ როგორ გამოვიყენეთ:

>>> letters = ['a', 'b', 'c']
>>> rest = tail(letters)
>>> print rest
['b', 'c']

სავარჯიშო 8.1 დაწერე ფუნქცია chop რომელიც იღებს სიას და ცვლის, შლის პირველ და ბოლო ელემენტს და აბრუნებს None - ს.
შემდეგ დაწერე ფუნქცია middle რომელიც იღებს სიას და აბრუნებს ახალ სიას რომელიც შეიცავს ყველაფერს პირველი და ბოლო ელემენტების გარდა.

8.14 გამართვა


სიის (და სხვა შეცვლადი ობიექტების) დაუდევრად გამოყენებამ გამართვისას შეიძლება ბევრი დრო დაგვაკარგვინოს. აქაა რამდენიმე ხერხი ამის თავიდან ასარიდებლად:

1.
 არ დაგავიწყდეს, რომ სიის მეთოდების უმეტესობა ცვლის არგუმენტს და აბრუნებს None - ს. ესაა სტრინგის მეთოდების საწინააღმდეგო, რომელიც აბრუნებს ახალ სტრინგს და ორიგინალს ტოვებს უცვლელს. თუ უნდა გამოიყენო სტრინგის მსგავსი კოდი: 

word = word.strip()
           
არის ცდუნება რო დამწერო ასე:

t = t.sort() # WRONG!

იმის გამო რომ sort აბრუნებს None - ს, შემდეგ ოპერაციას t - სთან სავარაუდოდ ვერ შეასრულებ.
სანამ სიის მეთოდებს და ოპერატორებს გამოიყენებ, დოკუმენტაცია ყურადღებით უნდა წაიკითხო და შემდეგ გამოცადო ინტერაქტიულ რეჟიმში. მეთოდები და ოპერატორები რომელსაც სია აზიარებს სხვა თანრიგებთან(მაგალითად სტრინგებთან) დოკუმენტირებულია აქ: docs.python.org/lib/typesseq.html. მეთოდები და ოპერატორები რომლებიც ვრცელდება მხოლოდ შეცვლად თანრიგთან დოკუმენტირებულია აქ:
docs.python.org/lib/typesseq-mutable.html.

2.
Pick an idiom and stick with it.

სიებში პრობლემა ის არის, რომ  არსებობს ბევრი გზა რამის გასაკეთებლად. მაგალითად, სიიდან ელემენტის წასაშლელად შეგიძლია გამოიყენო pop, remove,
del ან დაჭრის მეთოდი.
ელემენტის დასამატებლად შეგიძლია გამოიყენო append მეთოდი ან + ოპერატორი. მაგრამ არ დაგავიწყდეს რომ ეს სწორია:

t.append(x)
t = t + [x]

ეს კი არასწორი:

t.append([x]) # WRONG!
t = t.append(x) # WRONG!
t + [x] # WRONG!
t = t + x # WRONG!

ცადე ეს მაგალითები ინტერაქტიულ რეჟიმში და დარწმუნდი, რომ გაიგე რას აკეთებენ. შენიშვნა: მარტო ბოლო მაგალითში ხდება შეცდომა შესრულებისას. სხვა მაგალითები არის სამართლიანი, მაგრამ აკეთებენ არასწორ რამეს.

3.
aliasing - ის თავიდან ასარიდებლად გააკეთე ასლები.

ისეთი მეთოდის გამოიყენება თუ გინდა, როგორიცაა sort, რომელიც ცვლის არგუმენტს, თუმცა ორიგინალის შეინახვაც გინდა, შეგიძლია გააკეთო ასლი.

orig = t[:]
t.sort()

ამ მაგალითში ასევე შეგეძლო გამოგეყენებინა ჩაშენებული ფუნქცია sorted,  რომელიც აბრუნებს ახალ დალაგებულ სიას და ორიგინალს ტოვებს უცვლელს. ამ შემთხვევაში ცვლადს არ უნდა დაარქვა sorted.

4.
სიები, split და ფაილები
როცა ვკითხულობთ და ვარჩევთ ფაილებს არის ბევრი შესაძლებლობა, რომ გადავაწყდეთ ისეთ შენატანს(input), რომელსაც პროგრამაში შეცდომის გამოწვევა შეუძლია, ასე რომ კარგი იქნება გადავხედოთ "მეურვის ნიმუშს"(guardian pattern).
გადავხედოთ პროგრამას, რომელიც ფაილის ხაზებში ეძებდა კვირის დღეს:

From stephen.marquard@uct.ac.za Sat Jan 5 09:14:16 2008

ვინაიდან ამ ხაზს სიტყვებად ვყოფთ, შეგვეძლო გამოგვეყენებინა startswith და მარტივად გვენახა ხაზის პირველი სიტყვა, რომ განგვესაზღვრა რომელი ხაზი გვაინტერესებს. შეგვიძლია გამოვიყენოთ continue იმ ხაზების გამოსატოვებლად, რომელიც არ იწყება ”From - ით”:

fhand = open('mbox-short.txt')
for line in fhand:
            words = line.split()
            if words[0] != 'From' : continue
            print words[2]

ეს გამოიყურება უფრო მარტივად. rstrip - ის გამოყენებაც არ გვჭირდება ფაილის ბოლოდან ახალი ხაზების წასაშლელად. მაგრამ არის ეს უკეთესი?

python search8.py
Sat
Traceback (most recent call last):
    File "search8.py", line 5, in <module>
        if words[0] != 'From' : continue
IndexError: list index out of range

თითქოს მუშაობს, პირველი ხაზიდან დღეც გამოიტანა (Sat), მაგრამ შემდეგ პროგრამა "ფუჭდება". რა მოხდა? რა მონაცემმა გააფუჭა ჩვენი ელეგანტური, ჭკვიანი და ძალიან პითონიკური პროგრამა?
დიდი ხნის განმავლობაში ალბათ გაოცებული უყურებდი, დაიბენი ან ვინმეს დახმარება სთხოვე, მაგრამ ყველაზე სწრაფი და ჭკვიანური გამოსავალია print ბრძანების დამატება. საუკეთესო ადგილი print ბრძანების დასამატებლად არის იმ ხაზამდე, სადაც პროგრამა ფუჭდება და ბეჭდავს მონაცემს, რომელმაც სავარაუდოდ გამოიწვია მარცხი.
ასეთმა გამოსავალმა შეიძლება დააგენერიროს ბევრი ხაზი, მაგრამ წარმოდგენა მაინც შეგექმნება თუ რამ გამოიწვია პრობლემა. მეხუთე ხაზამდე დავამატეთ print ცვლადზე words, ასევე დავამატეთ პრეფიქსი "Debug:" , ასე რომ შეგვეძლება გამართვის ამონაბეჭდი რეგულარულ ამონაბეჭდისგან გავარჩიოთ.

for line in fhand:
            words = line.split()
            print 'Debug:', words
            if words[0] != 'From' : continue
            print words[2]

პროგრამას როცა გავუშვებთ ეკრანზე ბევრი ამონაბეჭდი გამოჩნდება, მაგრამ ბოლოში ვნახავთ ჩვენს გამართვის ამონაბეჭდს და შეცდომას. ასე რომ გვეცოდინება რა მოხდა შეცდომამდე.

Debug: ['X-DSPAM-Confidence:', '0.8475']
Debug: ['X-DSPAM-Probability:', '0.0000']
Debug: []
Traceback (most recent call last):
    File "search9.py", line 6, in <module>
        if words[0] != 'From' : continue
IndexError: list index out of range

გამართვის ყოველი ხაზი ბეჭდავს სიტყვების სიას, რომელსაც ვღებულობთ ხაზის სიტყვებად დაყოფით ( split ).  პროგრამა როცა ფუჭდება, სიტყვების სია არის ცარიელი []. ფაილს თუ გავხსნით ტექსტურ რედაქტორში და დავათვალიერებთ, ის გამოიყურება როგორც ეს:

X-DSPAM-Result: Innocent
X-DSPAM-Processed: Sat Jan  5 09:14:16 2008
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000


შეცდომა ხდება მაშინ როცა პროგრამა აწყდება ცარიელ ხაზს! რა თქმა უნდა ცარიელ ხაზში "ნული სიტყვაა". რატო არ ვიფიქრეთ ამაზე კოდის წერისას? როცა კოდი ამოწმებს პირველი სიტყვა (word[0]) ემთხვევა თუ არა "From” - , ვიღებთ შეცდომას: “index out of range”.
ეს კარგი ადგილია "მეურვე" კოდის დასამატებლად, რათა თავიდან ავირიდოთ პირველი სიტყვის შემოწმება იმ შემთხვევაში თუ პირველი სიტყვა არა არის. არსებობს ბევრი გზა კოდის დასაცავად. ჩვენ გადავწყვიტეთ ჯერ შეგვემოწმებინა სიტყვების რაოდენობა და მერე - პირველი სიტყვა:

fhand = open('mbox-short.txt')
count = 0
for line in fhand:
            words = line.split()
            # print 'Debug:', words
            if len(words) == 0 : continue
            if words[0] != 'From' : continue
            print words[2]

ჩამატებული გამართვის ბრძანება წაშლის ნაცვლად გადავაკეთეთ კომენტარად(იმ შემთხვევისთვის თუ ჩასწორებული კოდი მაინც გაფუჭდება და გამართვა კიდევ დაგვჭიდება). შემდეგ დავამატეთ "მეურვე" კოდი, რომელიც ამოწმებს არის თუ არა ნული სიტყვა და თუ ასეა ვიყენებთ  continue - ს ამ ხაზის გამოსატოვებლად.
ორ continue ბრძანებაზე შეიძლება ვიფიქროთ, როგორც დახმარებაზე ხაზების წყების დალაგებისას. ხაზს, რომელსაც არ აქვს სიტყვა ჩვენთვის უინტერესოა, ამიტომ ვტოვებთ. ასეთივეა ხაზი, რომელიც არ იწყება "From - ით.
შეცვლილი პროგრამა მუშაობს წარმატებით და სავარაუდოდ უშეცდომოა. "მეურვის" მეშვეობით words[0] არასდროს გააფუჭებს პროგრამას, მაგრამ ალბათ ეს საკმარისი არაა. როცა ვაპროგრამებთ ყოველთვის უნდა ვიფიქროთ "რა შეიძლება იყოს შეცდომით?"
სავარჯიშო 8.2 გაარკვიე ზემო პროგრამაში რომელი ხაზი არაა  დაცული სათანადოდ. ნახე თუ შეძლებ ტექსტური ფაილის ისე შეცვლას, რომ პროგრამაში გამოიწვიო შეცდომა და მერე პროგრამა გაასწორე და შეამოწმე ყველაფერი სწორად მუშაობს თუ არა.
სავარჯიშო 8.3  ზემო მაგალითიდან დაცვის პროგრამა გადაწერე ორი if ბრძანებების გარეშე. მათ სანაცვლოდ გამოიყენე ლოგიკური გამოსახულება and ლოგიკური ოპერატორით და ერთი if ბრძანებით.