2010-12-20 75 views
6

我有一個模塊,它有一個函數,其原型與線程類相似。帶有選項的Python裝飾器

def do(fn, argtuple=(), kwargdict={}, priority=0, 
      block=False, timeout=0, callback=None, daemon=False) 

    # do stuff 

FN是一個調用,和argtuple和kwargdict都將被傳遞給Fn調用時,它被調用位置和字典參數。

我現在正在爲此編寫一個裝飾器,但我很困惑。我從來沒有對裝飾者有過很好的把握。有沒有辦法讓一個裝飾器,我可以設置在上面的選項,如超時,但傳遞函數時調用argtuple和kwargdict。

因此,例如:

@do(priority=2) 
def decoratedTask(arg, dic=3): 
    #do stuff 

decoratedTask(72) 

我很困惑我怎麼會傳遞運行參數72到裝飾功能。我在想裝飾器需要是一個類,__call__方法返回函數調用,但我不確定如何傳遞這樣的參數的語法。

這是否有意義?

回答

14

裝飾器的功能是將函數作爲參數並返回一個函數,通常是一個在裝飾器中創建的新函數。

該新函數需要採用與您裝飾的函數相同的參數,並且還需要調用原始函數。

現在,當你有一個裝飾器與參數,有一個額外的水平在進行。該裝飾者應該採取參數,並且返回裝飾者。你使用的功能其實不是裝飾者,而是裝飾者!

下面是一個例子:

>>> def mydeco(count): 
...  def multipass(fn): 
...   def caller(*args, **kw): 
...    return [fn(*args, **kw) for x in range(count)] 
...   return caller 
...  return multipass 
... 
>>> @mydeco(5) 
... def printer(text): 
...  print(text) 
... 
>>> printer("Yabbadabbadoo!") 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
Yabbadabbadoo! 
[None, None, None, None, None] 

你會看到這些decoratormakers作爲類實現的實例。我也更喜歡它(儘管我通常最終沒有裝飾器),但函數中的函數可以工作。 :)

+0

感謝這使得它更加清晰。我只是混淆了像這樣的深層嵌套函數定義。我很確定我明白。在我接受之前,讓我先玩一下。 – Falmarri 2010-12-20 17:30:26

4

這並不完全如何裝飾者語法工作。當你編寫@do(priority=2)時,Python將評估do(priority=2)並使用該調用的結果作爲裝飾器。這是速記

decorator=do(priority=2) 
@decorator 

所以你真正想要做do一個裝飾工廠:你希望它把所有的位置參數,並返回一個裝飾。

def do(args=(), kwargs={}, ...): 
    def _decorator(fn): 
     def newfn(*args, **kwargs): 
      return fn(*args, **kwargs) 
     return newfn 
    return _decorator 

注意,這裏居然在這裏功能!

  • do是我們打電話給我們讓我們的裝飾功能(所以如do(priority=2)就是一個例子裝飾)
  • _decorator是實際的裝飾返回,它取決於參數do
  • 由於裝飾需要一個函數作爲輸入並返回一個函數作爲輸出,我們需要定義裝飾器返回的函數。該功能是newfn

實施例:

>>> def rename(name): 
...  def _decorator(fn): 
...    def renamed(*args, **kwargs): 
...      return fn(*args, **kwargs) 
...    renamed.__name__ = name 
...    return renamed 
...  return _decorator 
... 
>>> @rename('I like omelettes in the morning.') 
... def foo(): 
...  return 'bar' 
... 
>>> foo() 
'bar' 
>>> foo.__name__ 
'I like omelettes in the morning.' 

或等效

>>> omeletter = rename('I like omelettes in the morning.') 
>>> @omeletter 
... def foo(): 
...  return 'bar' 
... 
>>> foo() 
'bar' 
>>> foo.__name__ 
'I like omelettes in the morning.' 

順便提一下,照顧與可變默認參數(){};我相信你知道危險!

+0

katrielalex:'()'是不是可變的,僅僅是罰款作爲默認值。你是對的'{}'。 – Duncan 2010-12-20 10:24:00

+0

@鄧肯 - 的確如此!謝謝=) – katrielalex 2010-12-20 15:56:44

+0

你能指出文章或文件解釋這種行爲。我真的不明白函數參數如何在Python中傳遞,這在我看來有點不合邏輯。 – moz 2017-06-13 21:17:57

3

至於其他的答案已經解釋,裝飾通常當只使用他們的名字像這樣調用通過一個單一的隱函數的參數:

@deco 
    def somefunc(...): pass 

哪個做同樣的事情:

def somefunc(...): pass 
    somefunc = deco(somefunc) 

在這種情況下的參數是緊接着的函數定義的編譯版本。在這種情況下,裝飾器返回一個可分配給函數名稱而不是編譯函數體的可調用對象,正如通常情況那樣。

但是,如果一個裝飾功能明確給出一個或多個參數時,它被調用,就像這樣:

@deco(args) 
    def somefunc(...): pass 

它變成相當於:

def somefunc(...): pass 
    somefunc = deco(args)(somefunc) 

正如你可以看到,在這個事情案件的工作有些不同。裝飾器函數仍然返回一個可調用的函數,這次只有需要一個「普通」的單一隱式參數裝飾器函數,然後像以前一樣用以下函數定義中的函數對象調用它。

再次–正如其他人指出–這使得裝飾明確地傳遞參數,裝飾工廠在這個意義上,他們建立並返回「常規」裝飾功能。

在大多數情況下(如果不是全部),裝飾器可以作爲函數或類來實現,因爲兩者都可以在Python中調用。就我個人而言,我發現函數更容易理解,因此將在下面使用該方法。另一方面,函數方法可能會變得棘手,因爲它通常涉及一個或多個嵌套函數定義。

下面介紹如何在模塊中爲do()函數編寫裝飾器。在下面的代碼中,我定義了它在調用它之前打印出函數的參數。

def do(fn, args=tuple(), kwargs={}, priority=0, 
     block=False, timeout=0, callback=None, daemon=False): 
    # show arguments 
    print ('in do(): fn={!r}, args={}, kwargs={}, priority={},\n' 
      '   block={}, timeout={}, callback={}, daemon={}' 
      .format(fn.__name__, args, kwargs, priority, 
        block, timeout, callback, daemon)) 
    # and call function 'fn' with its arguments 
    print (' calling {}({}, {})'.format(
       fn.__name__, 
       ', '.join(map(str, args)) if args else '', 
       ', '.join('{}={}'.format(k, v) for k,v in kwargs.items()) 
        if kwargs else '') 
     ) 
    fn(*args, **kwargs) 

def do_decorator(**do_kwargs): 
    def decorator(fn): 
     def decorated(*args, **kwargs): 
      do(fn, args, kwargs, **do_kwargs) 
     return decorated 
    return decorator 

@do_decorator(priority=2) 
def decoratedTask(arg, dic=42): 
    print 'in decoratedTask(): arg={}, dic={}'.format(arg, dic) 

decoratedTask(72, dic=3) 

輸出:

in do(): fn='decoratedTask', args=(72,), kwargs={'dic': 42}, priority=2, 
     block=False, timeout=0, callback=None, daemon=False 
    calling decoratedTask(72, dic=3) 
in decoratedTask(): arg=72, dic=3 

這裏的竄缸打擊帳戶的這個複雜的尋找的東西是如何工作的:

外裝飾功能do_decorator(),定義它返回另一個內部裝飾功能,這裏創造性地命名爲decorator

做什麼decorator是確定會發生什麼了在一個簡單的裝飾「不爭論」的情況—,此處可確定和返回另一個–但最終–嵌套函數調用decorated,這只是調用該模塊的do()功能和功能如果從調用的角度來看,則將它傳遞給參數,以及用於do()函數的參數。

這個用例有點複雜,因爲外部裝飾器和正在裝飾的函數都有關鍵字參數。需要特別注意確保每個關鍵字的名稱都是唯一的,這樣它們就不會發生衝突(並且可變kwargs參數默認值不會因do()函數中的某些內容而無意中更改)。

0

我喜歡@Lennart Regebro,上述@katrielalex和@martineau答案,但聽起來很老土的風險,我要大膽寫故事基於例子。

在這個兩部分的故事,我們可以有教師學生從經驗。

def self_taught_teacher(fn): 
    ''' I teach students, because I had no teacher to guide me.''' 
    def a_student(*real_life_issues, **details_about_my_world): 
     ''' I'm a student who has been trained by a teacher 
     who has taught me about the problems I may encounter in real life. 

     Thanks to my teacher for giving me extra knowledge.  
     ''' 
     print 'I have to remember what my teacher taught me.' 
     my_answer = fn(*real_life_issues, **details_about_my_world) 
     print 'Ah yes, I made the right decision.' 
     # 
     return my_answer 
    # 
    return a_student 

@self_taught_teacher 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

讓我們來看看有什麼學生都知道...

>>> answer = student_named_Lisa_practicing_maths(10, 20, height=3) 
I have to remember what my teacher taught me. 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> answer 
600 

非常好。在故事的第二部分,我們介紹教授誰教別人成爲教師。那些教師然後分享他們與他們學到的學生

def professor_who_trains_teachers(*subjects, **to_train_teachers): 
    '''I am a profeseur. I help train teachers. ''' 
    # 
    def a_teacher_who_gets_trained(fn): 
     ''' I learn subjects I should teach to my students.''' 
     knowledge = [s for s in subjects] 
     # 
     def a_student(*real_life_issues, **details_about_my_world): 
      ''' I'm a student who has been trained by a teacher 
      who has taught me about the problems I may encounter in real life. 

      Thanks to my teacher for giving me extra knowledge.  
      ''' 
      print '(I know %s that i learned from my teacher,...)' % \ 
        [information for information in knowledge] 
      my_answer = fn(*real_life_issues, **details_about_my_world) 
      print 'Ah yes, I made the right decision.' 
      # 
      return my_answer 
     # 
     return a_student 
     # 
     # 
    return a_teacher_who_gets_trained 

因此,我們可以培養教師,讓他們教給學生......

>>> teacher1 = professor_who_trains_teachers('math','science') 
>>> teacher1 
<function a_teacher_who_gets_trained at 0x104a7f500> 
>>> teacher1.__name__ 
'a_teacher_who_gets_trained' 
>>> 

@teacher1 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

那個學生知道他們的數學..

>>> answer = student_named_Lisa_practicing_maths(20, 10, 2) 
(I know ['math', 'science'] that i learned from my teacher,...) 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> 
>>> answer 
400 

我們可以創造出老師也是。

@professor_who_trains_teachers('math', 'science', remember='patience') 
def student_named_Lisa_practicing_maths(length, width, height): 
    print 'Im consulting my powers of maths...' 
    return length * width * height 

同樣新來的同學可以做他們的數學......

>>> answer = student_named_Lisa_practicing_maths(10, 20, height=3) 
(I know ['math', 'science'] that i learned from my teacher,...) 
Im consulting my powers of maths... 
Ah yes, I made the right decision. 
>>> 
>>> answer 
600