Soru Python'da işlev imzasını ayarla


Farz edelim ki genel bir fonksiyon var f. istiyorum programlı f ile aynı davranan ancak özel bir imzası olan bir f2 işlevi oluşturun.

Daha fazla detay

Bir liste l ve sözlük d göz önüne alındığında şunları yapmak istiyorum:

  • F2 anahtar kelimesi olmayan argümanlarını l'deki dizelere ayarla
  • F2'nin anahtar kelime argümanlarını d içindeki tuşlara ve d değerlerini varsayılan değerlere ayarlayın.

yani. Varsayalım

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code

Sonra imzalı bir işlev istiyorum:

def f2(x, y, opt=None):
    #My code

Belirli bir kullanım durumu

Bu benim özel kullanım durumumun basitleştirilmiş bir versiyonu. Bunu sadece bir örnek olarak veriyorum.

Gerçek kullanım durumum (basitleştirilmiş) aşağıdaki gibidir. Genel bir başlatma işlevimiz var:

def generic_init(self,*args,**kwargs):
    """Function to initiate a generic object"""
    for name, arg in zip(self.__init_args__,args):
        setattr(self, name, arg)
    for name, default in self.__init_kw_args__.items():
        if name in kwargs:
            setattr(self, name, kwargs[name])
        else:
            setattr(self, name, default)

Bu işlevi birkaç sınıfta kullanmak istiyoruz. Özellikle bir fonksiyon yaratmak istiyoruz içinde generic_init gibi davranır, ancak bazı sınıf değişkenleri tarafından tanımlanan imzası vardır. oluşturma zamanı:

class my_class:
    __init_args__=["x", "y"]
    __kw_init_args__={"my_opt": None}

__init__=create_initiation_function(my_class, generic_init)
setattr(myclass, "__init__", __init__)

Kullanarak tanımlanan imza ile yeni bir işlev oluşturmak için create_initiation_function istiyoruz init_args ve kw_init_args. Create_initiation_function yazmak mümkün mü?

Lütfen aklınızda bulundurun:

  • Ben sadece yardımı iyileştirmek isteseydim, doktor.
  • Yaratılışta fonksiyon imzasını ayarlamak istiyoruz. Bundan sonra, değiştirilmesi gerekmiyor.
  • Generic_init gibi bir işlev oluşturmak yerine, ancak farklı bir imza ile, yalnızca generic_init çağıran istenen imzaya sahip yeni bir işlev oluşturabiliriz.
  • Create_initiation_function tanımlamak istiyoruz. Yeni işlevi manuel olarak belirtmek istemiyoruz!

İlgili


21
2017-09-11 07:35


Menşei


Açıkça sorduğun şeyden tamamen farklı bir şey bilmek istiyorsun. Belki de yaşadığınız sorunun ne olduğunu açıklamalısınız? Bu gerçekten ne sormaya çalıştığını söyleyebilirdi. - Lennart Regebro
Programlamanın böyle bir işlevi nasıl yarattığını mı soruyorsunuz? Her neyse, bence bize biraz bağlam vermelisin. Nasıl kullanmak istersin? İşlevin gövdesini nasıl belirtmek istersiniz? Nasıl kullanmak istediğinizi bize gösterir misiniz? - Tomek Szpakowicz
Şimdi yeniden düzenlediğim daha net mi? - Casebash
@tomekszpakowicz Bu tam olarak öyle. Programatik olarak bedeni yaratabilmek istiyorum. - Casebash
@Casebash: Bence bu çok temiz bir özellik. Genel bir işleviniz var ve belirli bir imza ile birkaç genel olmayan işlev oluşturmak istiyorsunuz. Bunu yapmanın en temiz yolu sadece onları yaratmaktır. - Lennart Regebro


Cevaplar:


Usecase'iniz için, sınıfta / işlevde bir doktora sahip olmanız işe yaramaz - bu da help () tamamında görünecek ve programatik olarak ayarlanabilir (func .__ doc__ = "stuff").

Gerçek imzayı ayarlama yolunu göremiyorum. Düşünürdüm functools modülü eğer yapılabilirse bunu yapardı, ama en azından py2.5 ve py2.6'da değil.

Kötü giriş alırsanız, bir TypeError istisnasını da yükseltebilirsiniz.

Hmm, gerçekten aşağılık bir sakıncası yoksa, derlemek için derlemek () / eval () kullanabilirsiniz. İstediğiniz imza arglist = ["foo", "bar", "baz"] tarafından belirtilmişse ve gerçek fonksiyonunuz f (* args, ** kwargs) ise, şunları yönetebilirsiniz:

argstr = ", ".join(arglist)
fakefunc = "def func(%s):\n    return real_func(%s)\n" % (argstr, argstr)
fakefunc_code = compile(fakefunc, "fakesource", "exec")
fakeglobals = {}
eval(fakefunc_code, {"real_func": f}, fakeglobals)
f_with_good_sig = fakeglobals["func"]

help(f)               # f(*args, **kwargs)
help(f_with_good_sig) # func(foo, bar, baz)

Docstring ve func_name’ın değiştirilmesi size tam bir çözüm getirmelidir. Ama, eww ...


15
2017-09-11 09:16



Değerlendirmeyi düşünmeliydim. Her şey onunla mümkündür. - Casebash
Bu, dekoratör paketinin kullandığı modeldir - pypi.python.org/pypi/decorator - Alice Purcell
Tamamen aşağılık? bu tam olarak ne namedtuple yapar. - Rick Teachey
Kullanmamalısın eval yardım edemezsen. Kullanmayı düşünün python-forge cevabımda önerdiğim paket: stackoverflow.com/a/50533832/465164 - Devin


itibaren PEP-0362aslında, imzayı py3.3 + 'da ayarlamak için bir yol gibi görünüyor. fn.__signature__ özellik:

def shared_vars(*shared_args):
    """Decorator factory that defines shared variables that are
       passed to every invocation of the function"""

    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            full_args = shared_args + args
            return f(*full_args, **kwargs)

        # Override signature
        sig = signature(f)
        sig = sig.replace(parameters=tuple(sig.parameters.values())[1:])
        wrapper.__signature__ = sig

        return wrapper
    return decorator

Sonra:

>>> @shared_vars({"myvar": "myval"})
>>> def example(_state, a, b, c):
>>>     return _state, a, b, c
>>> example(1,2,3)
({'myvar': 'myval'}, 1, 2, 3)
>>> str(signature(example))
'(a, b, c)'

Not: PEP tam olarak doğru değildir; Signature.replace paramleri konumsal argümandan kw-only argümanına taşıdı.


16
2017-09-11 06:42



Kullanmayı düşünün python-forge cevabımda önerdiğim paket: stackoverflow.com/a/50533832/465164 - Bu sorunu çözmek için tasarladım. - Devin


Yazdım adında paket forgeBu Python 3.5+ için bu kesin sorunu çözüyor:

Şu anki kodunuz şunun gibi:

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code

Ve istediğiniz kodunuz şöyle:

def f2(x, y, opt=None):
    #My code

Bunu kullanarak nasıl çözersiniz forge:

f2 = forge.sign(
    forge.arg('x'),
    forge.arg('y'),
    forge.arg('opt', default=None),
)(f)

Gibi forge.sign Bir sarıcı, aynı zamanda doğrudan kullanabilirsiniz:

@forge.sign(
    forge.arg('x'),
    forge.arg('y'),
    forge.arg('opt', default=None),
)
def func(*args, **kwargs):
    # signature becomes: func(x, y, opt=None)
    return (args, kwargs)

assert func(1, 2) == ((), {'x': 1, 'y': 2, 'opt': None})

2





Bunu canlı kodla yapamazsın.

Yani, şu gibi görünen gerçek, canlı bir işlev almak istersiniz:

def f(*args, **kwargs):
    print args[0]

ve buna benzer şekilde değiştir:

def f(a):
    print a

Bunun gerçekleştirilememesinin nedeni - en azından gerçek Python bayt kodunu değiştirmeden - bunların farklı şekilde derlenmesidir.

Birincisi, iki parametre alan bir işlevle sonuçlanır: bir liste ve bir dikte ve yazdığınız kod bu listede ve dikte çalışır. İkincisi, bir parametre alan ve doğrudan bir yerel değişken olarak erişilen bir işlevle sonuçlanır. "İmza" işlevini değiştirdiyseniz, konuşacak olursak, bunun gibi bir işlevle sonuçlanır:

def f(a):
    print a[0]

Açıkçası işe yaramayacaktı.

Daha fazla ayrıntı istiyorsanız (gerçekten size yardımcı olmamakla birlikte), * args veya * kwargs alan bir fonksiyonun bir veya iki biti vardır. f.func_code.co_flags; bunu kendiniz inceleyebilirsiniz. Normal bir parametre alan işlev f.func_code.co_argcount 1'e ayarla; * args sürüm 0'dır. Bu, Python'un çağrıldığında fonksiyonun yığın çerçevesini nasıl ayarlayacağını, parametrelerini kontrol etmeyi vb.

Fonksiyonu doğrudan değiştirerek oynamak istiyorsanız - sadece işe yaramayacağına kendinizi ikna etmek için - bkz. bu cevap kod nesnesinin nasıl oluşturulacağı ve bitlerin değiştirilmesi için varolan bir işlevden canlı işlevlerin nasıl oluşturulacağı. (Bu şeyler bir yerlerde belgelendi, ancak bulamıyorum;

Dedi ki kutu Bir fonksiyonun dokusunu dinamik olarak değiştirir. Sadece atayın func.__doc__. Bunu yalnızca yükleme zamanında yaptığınızdan emin olun (global bağlamdan veya büyük olasılıkla bir dekoratörden); Daha sonra yaparsanız, modül dizelerini incelemek için modülü yükleyen araçlar asla görmez.


1



Bir fonksiyonun imzasını dinamik olarak değiştirmeye ihtiyacım yok, sadece başka bir işlevle aynı işlevi gören ve farklı bir imzası olan bir fonksiyon yaratabilmek için - Casebash
Bu aynı şey. - Glenn Maynard
Tam olarak değil. Kasabalar tarafından belirtildiği gibi, genel işlevi çağırabilen eval kullanarak yeni bir işlev oluşturabiliriz. - Casebash


Belki de problemi iyi anlamadım, ancak fonksiyon imzasını değiştirirken aynı davranışı sürdürmekse, o zaman şöyle bir şey yapabilirsiniz:

# define a function
def my_func(name, age) :
    print "I am %s and I am %s" % (name, age)

# label the function with a backup name
save_func = my_func

# rewrite the function with a different signature
def my_func(age, name) :
    # use the backup name to use the old function and keep the old behavior
    save_func(name, age)

# you can use the new signature
my_func(35, "Bob")

Bu çıktılar:

I am Bob and I am 35

0



Programlı olması gerekiyor. Açıklamak zor ama sanırım son bir düzenleme beni öldürmez - Casebash


İstiyoruz create_initiation_function imzayı değiştirmek

Lütfen bunu yapma.

Bu işlevi birkaç derste kullanmak istiyoruz

Lütfen sıradan miras kullanın.

Çalışma zamanında "değiştirildi" imzasına sahip hiçbir değer yoktur.

Bakım kabusu yaratıyorsun. Hiç kimse, ne yaptığınızı anlamaya hiç kimse zahmet etmeyecek. Basitçe koparırlar ve mirasla değiştirirler.

Bunun yerine yap. Bu basit ve açıktır ve genel altyazınızı tüm alt sınıflarda açık, basit, Pythonic bir şekilde kullanılabilir hale getirir.

class Super( object ):
    def __init__( self, *args, **kwargs ):
        # the generic __init__ that we want every subclass to use

class SomeSubClass( Super ):
    def __init__( self, this, that, **kwdefaults ):
        super( SomeSubClass, self ).__init__( this, that, **kwdefaults )

class AnotherSubClass( Super ):
    def __init__( self, x, y, **kwdefaults ):
        super( AnotherSubClass, self ).__init__( x, y, **kwdefaults )

-1





Düzenleme 1: Yeni soru cevaplanıyor:

Bu imza ile nasıl bir işlev oluşturabileceğinizi soruyorsunuz:

def fun(a, b, opt=None):
    pass

Python'da bunu yapmanın doğru yolu şu şekildedir:

def fun(a, b, opt=None):
    pass

Düzenleme 2: Açıklama yanıtlama:

"Genel bir fonksiyonum var f. Programlı olarak f ile aynı davranan ama özel bir imzaya sahip bir f2 fonksiyonu yaratmak istiyorum."

def f(*args, **kw):
    pass

Tamam, o zaman f2 şöyle görünüyor:

def f2(a, b, opt=None):
    f(a, b, opt=opt)

Yine, sorunuza verilen cevap o kadar önemsiz ki, açıkçası sizden ne istediğini farklı bir şekilde bilmek istiyorsunuz. Soyut sorular sormayı ve somut probleminizi açıklamanız gerekiyor.


-4



Soruyu cevaplamıyor. Belirsiz olduğum için üzgünüm, şimdi açıklığa kavuştum. - Casebash
Soruyu cevapladı. Ayrıca, cevabınızı değişikliklerinizi yansıtacak şekilde güncelledim. Yine soruya cevap veriyor. Muhtemelen ne bilmek istediğinizi söylemez, ama önce ne bilmek istediğinizi bize söylemeniz gerekir. - Lennart Regebro
Düzenledikten sonra anlamın net olduğu konusunda eminim, ancak tekrar düzenledim. - Casebash
Hayır, hala belli değil. "Bir işlev yaratmak" ile ne demek istiyorsun? Bu fonksiyon ne yapmalı? - Miles
Tamam, kafam karıştıran herkes için üzgünüm. Umarım bu sefer kendimi netleştirmeyi başardım. - Casebash