2014-11-04 27 views
7

Öncelikle yapmak istediğim şeyin aptalca kötülükten bir şey olarak kabul edilebileceğini kabul etmeme izin verin, ancak yine de Python'da yapıp yapamayacağımı öğrenmek istiyorum.Bir dekoratör, imzasını değiştirmeden değişkenleri bir işleve nasıl iletebilir?

Diyelim ki, değişkenleri tanımlayan anahtar kelime argümanlarını alan bir işlev dekoratörüm var ve sarılmış işlevdeki bu değişkenlere erişmek istiyorum. Böyle bir şey yapabilir:

def more_vars(**extras): 
    def wrapper(f): 
     @wraps(f) 
     def wrapped(*args, **kwargs): 
      return f(extras, *args, **kwargs) 
     return wrapped 
    return wrapper 

Şimdi şöyle bir şey yapabilirsiniz:

@more_vars(a='hello', b='world') 
def test(deco_vars, x, y): 
    print(deco_vars['a'], deco_vars['b']) 
    print(x, y) 

test(1, 2) 
# Output: 
# hello world 
# 1 2 

Bu konuda sevmiyorum şey bu dekoratör kullandığınızda, zorunda olmasıdır dekoratöre tokat ek olarak ek değişken ekleyerek, işlevin çağrı imzasını değiştirmek. Eğer fonksiyon için yardım bakarsak Ayrıca, size işlevini çağırarak kullanılacak beklenen değiliz fazladan değişken bakın:

Bu kullanıcı işlevi çağırmak için beklenen gibi görünmesini sağlar
help(test) 
# Output: 
# Help on function test in module __main__: 
# 
# test(deco_vars, x, y) 

3 parametresi ile, ama açıkçası bu işe yaramaz. Bu nedenle, ilk parametrenin arayüzün bir parçası olmadığını belirten bir mesaj eklemeniz gerekecek, sadece bir uygulama detayı ve göz ardı edilmemelidir. Gerçi bu berbat bir şey. Bu değişkenleri küresel ölçekte bir şeye asmadan bunu yapmanın bir yolu var mı? İdeal olarak, ben aşağıdaki gibi bakmak istiyorum:

@more_vars(a='hello', b='world') 
def test(x, y): 
    print(a, b) 
    print(x, y) 

test(1, 2) 
# Output: 
# hello world 
# 1 2 
help(test) 
# Output: 
# Help on function test in module __main__: 
# 
# test(x, y) 

Bir Python 3 Varsa tek çözüm ile memnunum.

+1

Belki de ihtiyacınızı daha çok tanımlamalısınız. İşlevi süslemek için sadece bir şans elde edersiniz, bu yüzden sadece a = 'merhaba' 'dekoratöre koymanın avantajı, sadece işlevin ilk satırı olarak değil, yerine koymak? –

+0

"Test" kelimesinin herhangi bir şekilde tanımlanmasına gerek kalmadan, daha önce derlendikten sonra "testin" kapanmasına "extras" ın nasıl enjekte edileceğini soruyorsunuz gibi geliyor. Eğer öyleyse, bazı korkunç hackler olmadan mümkün olacağını düşünmüyorum. – abarnert

+0

Ama bir dekoratör yerine bir [MacroPy] (https://github.com/lihaoyi/macropy) makrosu kullanarak, bunun kolay olacağını tahmin ediyorum… Bu kabul edilebilir bir cevap olur mu? – abarnert

cevap

0

Yerlileri ad alanına enjekte etme fikrini sevmiyorum. Muhtemelen mümkün olsa da, enjekte edilen yeni yerliler ile zaten fonksiyonda bulunan isimler arasındaki çarpışmaların üstesinden gelmek kötü olacaktır.

İşlev nesnesinde attrs olarak saklamak kabul edilebilir mi? Bu şekilde isimleri verildi.

from functools import wraps 

def more_vars(**extras): 
    def wrapper(f): 
     @wraps(f) 
     def wrapped(*args, **kwargs): 
      return f(*args, **kwargs) 
     for k,v in extras.items(): 
      setattr(wrapped, k, v) 
     return wrapped 
    return wrapper 

@more_vars(a='hello', b='world') 
def test(x, y): 
    print(test.a, test.b) 
    print(x, y) 
+0

İlk önce onları fonksiyon olarak attrs diye düşünmekteyim, ama benim problemim fonksiyon tanımlarını artık "bağımsız" bir şekilde yapmıyor olmasıdır. Test işlevi tanımını kopyalayıp "test2" olarak değiştirirseniz, tanımdaki tüm "test" örneklerini yeniden adlandırdığınızdan emin olmanız gerekir. Bu, kopyalayıp yapıştırma hatalarına neden olabilir. – user108471

+0

Kolayca çalışılır .. işlev adı dinamik olarak "inspect.currentframe(). F_code.co_name" veya "inspect.stack" ile kullanılabilir, daha sonra işlev ismini kodlamak gerekmeden işlev nesnesinde bir tanıtıcı alabilirsiniz . – wim

+0

Bu kötü bir fikir değil, ancak yine de, decolator'ı kullanmak isteyen her işleve boilerplate kodunu ekler. Tüm ilgili shenaniganların dekoratör tarafından halledilebilmesi hoş olurdu, böylece dekoratörün kullanıcısı dekoratörün eklenmesinin ötesinde fonksiyonu değiştirmeye gerek duymaz. – user108471

2

Sen işlevin yerel değişkenlere dekoratör geçirilen değişkenleri ekler nasm'ın yapabilirsiniz:

import sys 
from functools import wraps 
from types import FunctionType 


def is_python3(): 
    return sys.version_info >= (3, 0) 


def more_vars(**extras): 
    def wrapper(f): 
     @wraps(f) 
     def wrapped(*args, **kwargs): 
      fn_globals = {} 
      fn_globals.update(globals()) 
      fn_globals.update(extras) 
      if is_python3(): 
       func_code = '__code__' 
      else: 
       func_code = 'func_code' 
      call_fn = FunctionType(getattr(f, func_code), fn_globals) 
      return call_fn(*args, **kwargs) 
     return wrapped 
    return wrapper 


@more_vars(a="hello", b="world") 
def test(x, y): 
    print("locals: {}".format(locals())) 
    print("x: {}".format(x)) 
    print("y: {}".format(y)) 
    print("a: {}".format(a)) 
    print("b: {}".format(b)) 


if __name__ == "__main__": 
    test(1, 2) 

Bunu Can

? Emin! yapmalısınız? Muhtemelen değil!

(Kod mevcut here.)

+0

Bu iyi bir çözüm gibi görünüyor, ama bir sorun görüyorum. Eğer more_vars ile enjekte edilen değişkenlerden herhangi birini tahsis etmeye çalışırsanız, fonksiyona global bildirimler eklemediğiniz sürece, global olarak enjekte edilen "sahte" gölgeleri yansıtan aynı adın yerel bir kapsam değişkeni oluşturacaksınız. Bunun etrafında bir yolu var mı? – user108471

+0

Bu yüzden tasarımınız ilk etapta neden kötü bir fikirdir. Neden isim alanını kırmak istiyorsun? Mümkün kullanım durumu için bu iyi bir fikir olabilir mi? Kalıcı bir sınıf kullanmayı ve yeni objeleri dekoratörler ile uğraşmaktan daha kolay ve açık bir şekilde enjekte etmeyi düşündünüz mü? – wim

+0

Bir şey uygulamak zorsa, her zaman arkasındaki motivasyonları anlamaya gerek kalmadan "bunu ilk etapta yapmamanız gerekir" diziyle sarsılma eğiliminde bir eğilim vardır. Anlıyorum, zorlu problemler onları farklı, daha kolay olanlara değiştirdiğinizde çözülmesi kesinlikle daha kolay. Ama peşinde olduğum işlevselliği elde etmek için başka stratejiler istemiyorum, bu stratejinin uygulanıp uygulanmayacağını görmek istiyorum. Bunu dekoratörlerle yapmanın herhangi bir yolunu bilmiyorsanız, sorun değil, ama lütfen bu noktaya giden tüm motivasyonları açıklayan bir makale istemeyin. – user108471

0

Bu tek sorun var gibi görünüyor help sarılmış fonksiyonun imza olarak ham test imzasını gösteren olmasıdır ve bunu istemiyoruz. oluyor

tek nedeni olduğunu wraps (daha doğrusu update_wrapper, hangi wraps aramaları) sargıya wrappee dan açıkça kopyalar bu.

Tam olarak ne yaptığınıza ve kopyalamak istemediğinize karar verebilirsiniz. Farklı yapmak istediğiniz şey basitse, yalnızca varsayılan WRAPPER_ASSIGNMENTS ve WRAPPER_UPDATES'dan gelen şeyleri filtrelemek meselesidir.Diğer şeyleri değiştirmek isterseniz, update_wrapper'u çatallamanız ve kendi sürümünüzü kullanmanız gerekebilir - ancak functools, okunabilir örnek olarak kullanılması gerektiğinden, dokümanların en üstünde the source bağlantısına sahip olan modüllerden biridir. kodu. Senin durumunda

, sadece wraps(f, updated=[]) meselesi olabilir veya f imzasını almak için kullanım inspect.signature gibi fantezi bir şey yapmak istiyorum ve ilk parametreyi kaldırmak için değiştirmek ve açıkça bir sarmalayıcı inşa edebilir etrafında inspect modülünü bile kandırmak için.

+0

Tek sorun bu değil. Sorunun ilk açıklamasında da yazdım: "Bu konuda sevmediğim şey, bu dekoratör kullandığınızda, fonksiyonun çağrı imzasını değiştirmelisiniz. dekoratör." "Yardım" ile ilgili sorun, bu cümleyi doğrudan takip eder, ancak bu iki ayrı meseledir. Son kod snippet'inde, nasıl görünmesini istediğimi gösteriyorum. – user108471

İlgili konular