Thursday, June 16, 2016

Decorators | Meta Python


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

სანამ დავიწყებდე დეკორატორებზე წერას, აღვწერ პითონის ფუნქციების შესაძლებლობებს, რომლებიც საკმაოდ მნიშვნელოვან როლს თამაშობენ პროგრამირებაში. ასევე დასაწყისისთვის განვსაზღვრავ ერთ მარტივ ფუნქციას, რომლისთვისაც არ გამოვიყენებ სინტაქურ მომნიშვნელს (მეზარება ბევრი gist-ის გამოყენება და საშველი ვერ დავადგი სხვა ფლაგინის მოძებნას).

def print_hello (name = "გიორგი"):
    print("Hello {0}!".format(name))

def caller (func=None):
    if func is not None:
        return func


  • ფუნქცია შეიძლება მივანიჭოთ ცვლადს, მაგალითად თუ გვაქვს ცვლადი ( f = None ), შეგვიძლია ეს ცვლადი ავიღოთ და გავუტოლოთ ზემოთ მოცემულ ფუნქციას, როგორც f = print_hello. და ბოლოს, თუ მოგვინდა ფუნქციის გამოძახება print_hello-ს ბეჭდვის ნაცვლად მარტივად შეგვიძლია გამოვიყენოთ - f ("python")
  • ასევე შესაძლებელია ფუნქციაში განვსაზღვროთ სხვა ფუნქცია, რაც ჩვეულებრივი რამ არის პითონში და ისე კეთდება, როგორც თქვენ წარმოგიდგენიათ (სინამდვილეში, მეზარება წერა :(  ).
  • შესაძლებელია, რომ პითონის ერთ ფუნქციას არგუმენტად გადავცეთ სხვა, მეორე ფუნქცია - caller ( print_hello ) (out === Hello გიორგი). 
  • ასევე შესაძლებელია, რომ ერთმა ფუნქციამ დააბრუნოს მეორე ფუნქცია და შემდეგ გამოვიძახოთ ეს ფუნქცია. 
  • ეს სია შეიძლება კიდევ გაგრძელდეს, მაგრამ ამ ეტაპზე ვფიქრობ, რომ საკმარისია

ზემოთ მოცემული სიის გამოყენებით შეგვიძლია მარტივად შევქმნათ პირველი დეკორატორი, რომელიც "გაალამაზებს" (ხშირ/ჭკვიანურ შემთხვევებში) უკვე არსებულ ფუნქციებს. ამ ეტაპზე ისევ გვაქვს print_hello ფუნქცია და დავუშვათ გვსურს, რომ ეს ყველაფერი გვინდა ისე გავაკეთოთ, რომ გამოსული ტექსტს დავუმატოთ გამოძახების დრო (ანუ რა დროს იქნა გამოძახებული ფუნქცია). 

from datetime import datetime

def target_function(arg=None):
  return "Target function was caller with {0}".format(arg)

def call_time(func):
  def func_wrapper(arg=None):
    print("--call time - {0}".format(datetime.now()))
    print("--call result - {0}".format(func(arg)))
  return func_wrapper

ინტუიტიურად შესაძლებელია, რომ დეკორაციის გარეშე (რასაც მოგვიანებით ზუსტად დავწერ) შესაძლებელია, რომ call_time-ს არგუმენტად გადავცეთ target_function და მივიღოთ შესაბამისად დეტალური ინფორმაცია ფუნქციის გამოძახების დროზე, შემდეგნაირად:

time_info = call_time(target_function)
time_info("Custom argument")

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

@call_time
def target_function(arg=None):
  return "Target function was caller with {0}".format(arg)

რაც იმას "გამოიწვევს", რომ როდესაც გამოვიძახებთ ფუნქციას target_function მივიღებთ ზუსტად იმავე შედეგს, რაც მივიღე ზემოთ მოცემული ორი ხაზის გამოყენებით.

ახლა მოდი ვისაუბრებ რაში გამომიყენებია ეს ყველაფერი - ბევრ რამეში, მაგრამ კოდის დამწერისთვის, რომელიც რაიმე ახალზე მუშაობს და მითუმეტეს თუ ეს ახალი არ იცის რა შედეგს მოუტანს და როგორ გაეშვება, მისთვის ყველაზე კრიტიკული სეგმენტი არის Exception handling-ი კოდში და ზოგადად კოდის დებაგი. თითოეულმა ნამდვილმა პროგრამისტმა იცის, რომ ვერანაირი დებაგი ვერ შეედრება print-ს, ვერც testcase-ები, და საერთოდ ვერაფერი, როდესაც გვაქვს ეს მძლავრი დებაგ იარაღი ტერმინალში - print ().


მოკლედ ერთ მშვენიერ დღეს გავაკეთე შემდეგი რამ, რისი სურვილიც C#-ში მქონდა, მაგრამ ვერ ვპოულობდი შესაბამისად მარტივ იარაღს ამ იდეის განხორციელებისთვის. წარმოიდგინეთ სიტუაცია, როდესაც მქონია ფაილი, სადაც დაახლოებით ერთი თემის ირგვლივ გაერთიანებული ფუნქციები მაქვს დაწერილი და თითოეულს უნდა ჰქონდეს exception handling ლოგიკა (ამ ეტაპზე შემოვიფარგლები მხოლოდ try except-ით). ამ დროს ფუნქციების რიცხვი n = 10-ს. 10 ფუნქცია, სადაც ყოველთვის მეორდება როგორც მინიმუმ 3 ხაზი.

def function(*args, **kwargs):
  try:
    #function body
  except Exception as ge:
    handle_exception(ge)

ხშირად ამ handle_exception მეთოდი ჩანაცვლებულია print (ge)-ით, სანამ არ მაფიქრდება ხოლმე როგორ გავაკეთო exception-ის მოგვარების ლოგიკა (საერთოდ, ლოგების გარდა რამისთვის თუ არის საჭირო ხოლმე). 10 ფუნქციაში ეს განმეორებადი კოდი გამოდის 30 ხაზი, რომლის ბეჭდვა ძალიან მოსაბეზრებელი იყო ჩემთვის, ამიტომ მივაგენი გზას - დეკორატორებს


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

def great_deco(func):
    def func_wrapper(*args, **kwargs):
        print("wrapping some function @{0}".format(datetime.now()))
        func(*args, **kwargs)
    return func_wrapper

@great_deco
def target_function1(name, sname):
    print(name, sname)

@great_deco
def target_function2(age):
    print(age)

target_function1(name="Giorgi", sname="Jambazishvili")
target_function2(age=21)

ასე მარტივად გადაიჭრა არგუმენტების პრობლემა. *args და **kwargs არგუმენტები შეითავსებენ ნებისმიერ გადაცემულ keyword თუ არა keyword არგუემნტს, სადაც args-ის ტიპი ჩვეულებრივი list-ია, ხოლო kwargs-ის ტიპი ლექსიკონი. ახლა დარჩა კიდევ ერთი რამ, რისი გამოყენებაც მინდა. კერძოდ, მინდა, რომ გავიგო, რომელი ფუნქცია არის გამოძახებული (სახელი და გვარი) რომ შემდეგ შეცდომებზე შევამოწმო. ამას კი მივიღებთ func.__qualname__ ის დახმარებით. 

def debug(func):
    def func_wrapper(*args, **kwargs):
        try:
            loc = func.__qualname__
            t = datetime.now()
            print("calling function {0} @{1}".format(loc, t))
            func(*args, **kwargs)
            print("call successful")
        except Exception as ge:
            print("--exception in {0}\n--{1}".format(loc, ge))
    return func_wrapper

@debug
def target_function3(isException=False):
    if isException:
        raise Exception("Exception was raised due to isExcception is True")
    else:
        print("target_function3 call")

target_function3(True)
target_function3(False)

<<< OUT >>>
calling function target_function3 @2016-06-16 08:01:57.295949
--exception in target_function3
--Exception was raised due to isExcception is True
calling function target_function3 @2016-06-16 08:01:57.319953
target_function3 call
call successful

მოკლედ ერთ ხელის მოსმით თავიდან ავირიდეთ 30 ხაზი 11 ხაზის გამოყენებით და ამ დროს მივიღეთ უფრო ზუსტი და უკეთესი ინფორმაცია დებაგისთვის ან სულაც ლოგისთვის. ასე რომ დეკორატორები მართლა გამოსადეგია, თუნდაც ამ ერთი მარტივი მაგალითისთვის. მაგრამ არის კიდევ ერთი რამ. ყველას გვქონია ისეთი მომენტი, როდესაც გვაქვს ამდენი ფუნქცია და უბრალოდ გვეზარება 10 @debug-ის დაწერა.


არაუშავს, აქაც არის მარტივი გამოსავალი. აქ გვჭირდება კლასის დეკორატორები და ცოტა oop. ჯერ განვსაზღვროთ კლასის დეკორატორი, რაც იმას ნიშნავს, რომ ფუნქციის დეკორატორის ნაცვლად კლასის დეკორატორი აიღებს კლასს არგუმენტად და შემდმეგ უკვე გააკეთებს კოდის გენერაციას, რომ კლასის თითოეული "გამოძახებადი" მეთოდს გაუკეთოს დეკორაცია debug დეკორატორით, რომელიც ზემოთ განვსაზღვრეთ. დეკორატორის კოდი იქნება შემდეგნაირი:

def class_debugger(cls):
    for name, val in vars(cls).items():
        if callable(val):
            setattr(cls, name, debug(val))
    return cls

და აი ეს კოდი ძალიან მაგარი ნაწილია, რომელიც დამეხმარა ნებისმიერი სტანდარტული კლასი მქონოდა და აღარ დამეწერა თითოეული მეთოდისთვის try except else finally ლოგიკა. ვნახოთ როგორ - ( @class_debugger ის გამოყენებით)

@class_debugger
class TargetClass:
    def method1(self):
        print("Method1 was called")

    def method2(self):
        print("Method2 was called")

    def method3(self):
        print("Method3 was called")
        raise Exception("Exception raised due to bad code")

    def method4(self):
        print("Method4 was called")

    def method5(self):
        print("Method5 was called")

tc = TargetClass()
tc.method1()
tc.method2()
tc.method3()
tc.method4()
tc.method5()

აქ გასაგებია, რომ მესამე მეთოდი გამოიწვევს exception-ს და ამის ლამაზად მოგვარება უნდა შეძლოს დეკორატორმა.

<<<OUT>>>
calling function TargetClass.method1 @2016-06-16 08:35:37.881743
Method1 was called
call successful
calling function TargetClass.method2 @2016-06-16 08:35:37.928557
Method2 was called
call successful
calling function TargetClass.method3 @2016-06-16 08:35:37.944209
Method3 was called
--exception in TargetClass.method3
--Exception raised due to bad code
calling function TargetClass.method4 @2016-06-16 08:35:37.975460
Method4 was called
call successful
calling function TargetClass.method5 @2016-06-16 08:35:38.006689
Method5 was called
call successful

ძირითადად ეს იყო რაც მინდოდა მეთქვა დეკორატორებზე. ეს არის მეტა პროგრამირების ერთ-ერთი შესავალი და საწყისი კონცეფცია, როდესაც კოდი აგენერირებს კოდს (ამ შემთხვევაში შეიძლება პრიმიტიული გენერირება გეგონოთ, მაგრამ შემდეგ პოსტებში არც ისე პრიმიტიული მოგეჩვენებათ ეს ყველაფერი).

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















No comments:

Post a Comment