Python'da varsayılan argümanlarda kod tekrarından kaçınmak

Varsayılan argümanlarla tipik bir fonksiyon düşünün:

def f(accuracy=1e-3, nstep=10):
    ...

Bu kompakt ve anlaşılması kolaydır. Ancak, f diyecek başka bir g fonksiyonuna sahipsek ve g 'nin bazı argümanlarını f <' a aktarmak istersek/code>? Bunu yapmanın doğal bir yolu:

def g(accuracy=1e-3, nstep=10):
    f(accuracy, nstep)
    ...

Bu işleri yapmanın yolunda sorun, isteğe bağlı değişkenlerin varsayılan değerlerinin tekrarlanmasıdır. Genellikle, bunun gibi varsayılan argümanları ilerletirken, biri üst fonksiyonda ( g ) alt fonksiyonda ( f ) olduğu gibi aynı varsayılanı ister ve böylece varsayılan olarak her değişiklik olduğunda f 'da, onu çağıran tüm işlevlerin üzerinden geçilmesi ve argümanlarının herhangi birinin varsayılanlarını f ' ya yayacakları güncellemesi gerekir.

Bunu yapmanın başka bir yolu, bir yer tutucu argümanı kullanmak ve işlev içindeki değerini doldurmaktır:

def f(accuracy=None, nstep=None):
    if accuracy is None: accuracy = 1e-3
    if nstep is None: nstep=10
    ...
def g(accuracy=None, nstep=None):
    f(accuracy, nstep)
    ...

Şimdi, çağıran fonksiyonun f 'un varsayılan değerlerinin ne olduğunu bilmesi gerekmez. Ancak f arayüzü şimdi biraz daha hantal ve daha az net. Bu, fortran veya JavaScript gibi açık varsayılan argüman desteği olmayan dillerdeki tipik yaklaşımdır. Fakat eğer biri python'da her şeyi bu şekilde yaparsa, dilin varsayılan argüman desteğinin çoğunu atıyor demektir.

Bu ikisinden daha iyi bir yaklaşım var mı? Bunu yapmanın standart, pitonik yolu nedir?

15
Bu soruyu kendime defalarca sordum. Ancak [programcılar [( programmers.stackexchange.com ) için bir soru olabilir mi? Compci bir borsa değişimi olduğunu sanıyordum ama şimdi bulamıyorum. Ne tür cevaplar aldığınızı görmek için sabırsızlanıyorum.
katma yazar Mark Mikofski, kaynak
teşekkürler @MichaelT diğerleri için bkz. Yığın Taşması ve Programcılar Yığın Değişimi arasında seçim yapma , özellikle> "başparmak kuralını seviyorum" : IDE'nizin önünde oturuyorsanız, Stack Overflow'a sorun. Bir beyaz tahtanın önünde duruyorsanız, bunu Programlayıcılara sorun "
katma yazar Mark Mikofski, kaynak
Öte yandan, @MarkMikofski, eğer soru esas olarak Stack Overflow hakkında görüş veya çok geniş bir soruysa, büyük olasılıkla Programcılara eşit derecede fayda sağlayacaktır. Bu amaçla, Programcılara neler oluyor? Yığın Taşması kullanıcılarının Programcılar kapsamını anlamalarına ve düşük geçiş önerilerini önlemelerine yardımcı olmaya çalışan Yığın Taşması için bir kılavuz .
katma yazar user289086, kaynak
@MarkMikofski, cs.stackexchange.com 'u düşündüğünüzü düşününce, bu site korkakça yunan harfleri gerektiren ve Büyük hakkında konuşmak gibi sorularla daha fazla ilgileniyor O. Bu soru yine de, Yığın Taşması veya Programcılardan en iyi şekilde sorulmasını isterse kenarda olurdum. Burada olduğu ve konu oylarını bir araya getirmediği için, muhtemelen burada tamam. P.SE’ye geçirilecekse, cevapların o site için istenen cevap stiliyle eşleşmeyeceğini unutmayın.
katma yazar user289086, kaynak

6 cevap

Global sabitleri tanımlayın:

ACCURACY = 1e-3
NSTEP = 10

def f(accuracy=ACCURACY, nstep=NSTEP):
    ...

def g(accuracy=ACCURACY, nstep=NSTEP):
    f(accuracy, nstep)

f ve g farklı modüllerde tanımlanmışsa, o zaman da bir constants.py modülü yapabilirsiniz:

ACCURACY = 1e-3
NSTEP = 10

ve sonra f 'ı şöyle tanımlayın:

from constants import ACCURACY, NSTEP
def f(accuracy=ACCURACY, nstep=NSTEP):
    ...

ve benzer şekilde g .

9
katma
@MarkMikofski: Cevabınızın ayrı bir cevap olarak durmasına izin vermek için iyi ve yeterince farklı olduğunu düşünüyorum; ve insanların bir sorunu çözmenin farklı yollarını görmelerine izin vermek iyidir.
katma yazar unutbu, kaynak
f ve g farklı modüllerde tanımlanmışsa (ve başka bir işleve sahipseniz h g öğesini çağırıyorsanız), bu, def g (kesinlik = fmod.ACCURACY, nstep = fmod.NSTEP ve h (accyracy = gmod.fmod.ACCURACY, nstep = gmod.fmod.NSTEP) , öyle değil mi? Bu varsayılanları çeşitli modüller etrafında yaymanın uygun bir yolu var mı?
katma yazar amaurea, kaynak
@ unutbu, cevabımın sizinkinden çok farklı olup olmadığından emin değilim, sadece onları birleştirebilir ve benimkini kaldırabilirim.
katma yazar Mark Mikofski, kaynak
Genellikle, bu kadar çok sayıda katılımcı varsa ve paketteki her modül için geçerliyse, bunları __init__.py veya constants.py adlı bir modülde gizlice gizlerim. Daha sonra __all__ = ['ACCURACY', 'NSTEP'] gibi bir şey de belirleyebilirsiniz ve ardından her modülde, benim sabitlerinizin tümünü almak için

> mypackage import * 'ı kullanın. Her bir alt modül.

katma yazar Mark Mikofski, kaynak
@amaurea, fmod.py sabitlerini fmod import ACCURACY, NSTEP 'den kullanarak ' i alın, sonra işlev protokollerinde sabitleri kullanmak zorunda değilsiniz. tam ad boşluğunu kullanabilirsiniz, sadece @ unutbu cevabı, def g (doğruluk = ACCURACY, nstep = NSTEP) ve h (doğruluk = ACCURACY, nstep = NSTEP) komutlarını kullanabilirsiniz.
katma yazar Mark Mikofski, kaynak
Bunu bir yorum olarak eklemek üzereydim. Evet, bu sık kullandığım tekniktir, yalnızca bir kez varsayılanlarınızı güncellemeniz gerekmesi gibi birçok avantaja sahiptir, fakat aynı zamanda (iyi ya da kötü olabilir) bir bakışı vardır ve kesin olarak daha fazla tuş vuruşu anlamına gelir. CONSTANTS ’unuzdan yalnızca 4 harf uzunluğunda veya daha az, OP’nin önerdiği şekilde Yok seçeneğinden fazla değil.
katma yazar Mark Mikofski, kaynak

Bence prosedürel paradigma, bu soruna bakışınızı daraltıyor. Diğer Python özelliklerini kullanırken bulduğum bazı çözümler.

Nesne yönelimli programlama

Aynı parametre alt grubuyla f() ve g() 'i çağırıyorsunuz - bu, bu parametrelerin aynı varlığı temsil ettiği için iyi bir ipucu. Neden bir nesne yapmıyorsun?

class FG:
    def __init__(self, accuracy=1e-3, nstep=10):
        self.accuracy = accuracy
        self.nstep = nstep

    def f(self):
        print ('f', self.accuracy, self.nstep)

    def g(self):
        self.f()
        print ('g', self.accuracy, self.nstep)

FG().f()
FG(1e-5).g()
FG(nstep=20).g()

İşlevsel programlama

f() 'ı daha üst düzey bir işleve dönüştürebilirsiniz - başka bir deyişle:

from functools import partial

def g(accuracy, nstep):
    print ('g', accuracy, nstep)

def f(accuracy=1e-3, nstep=10):
    g(accuracy, nstep)
    print ('f', accuracy, nstep)

def fg(func, accuracy=1e-3, nstep=10):
    return partial(func, accuracy=accuracy, nstep=nstep)

fg(g)()
fg(f, 2e-5)()
fg(f, nstep=32)()

Ancak bu aynı zamanda zor bir yaklaşım - burada f() ve g() çağrıları değiştirildi. Muhtemelen bunu yapmak için daha iyi yaklaşımlar var - yani, geri aramaları olan boru hatları, FP :( ile iyi değilim

Dynamicness & introspection

Bu çok daha karmaşık bir yaklaşımdır ve CPython'un iç dünyalarına kazmayı gerektirir, ancak CPython buna izin verdiğinden, neden kullanmıyorsunuz?

__defaults__ üyesi aracılığıyla varsayılan değerleri güncellemek için bir dekoratör:

class use_defaults:
    def __init__(self, deflt_func):
        self.deflt_func = deflt_func

    def __call__(self, func):
        defltargs = dict(zip(getargspec(self.deflt_func).args, 
                            getargspec(self.deflt_func).defaults))

        defaults = (list(func.__defaults__) 
                    if func.__defaults__ is not None 
                    else [])

        func_args = reversed(getargspec(func).args[:-len(defaults)])

        for func_arg in func_args:
            if func_arg not in defltargs:
                # Default arguments doesn't allow gaps, ignore rest
                break
            defaults.insert(0, defltargs[func_arg])

        # Update list of default arguments
        func.__defaults__ = tuple(defaults)

        return func

def f(accuracy=1e-3, nstep=10, b = 'bbb'):
    print ('f', accuracy, nstep, b)

@use_defaults(f)
def g(first, accuracy, nstep, a = 'aaa'):
    f(accuracy, nstep)
    print ('g', first, accuracy, nstep, a)

g(True)
g(False, 2e-5)
g(True, nstep=32)

Bununla birlikte, __kwdefaults__ 'dan ayrı, yalnızca anahtar kelime bağımsız değişkenlerini dışlar ve muhtemelen use_defaults dekoratörünün arkasındaki mantığı patlatır.

Ayrıca sarıcı kullanarak çalışma zamanında değişkenler ekleyebilirsiniz, ancak bu muhtemelen performansı düşürür.

4
katma
@amaurea, Cevabımı kabul ettiğin için teşekkürler! İşlevsel programlama örneğini değiştirdim, şimdi f() ve g() arasındaki sıkı bağımlılık bozuldu. Ayrıca, cevabım kavramsaldır ve seçilmesi gereken yol f ve g türüne bağlıdır.
katma yazar myaut, kaynak
Üçüncü önerin çok ilginçti. Böyle bir şeyin mümkün olabileceğini düşünmemiştim. Dekoratörün bir işleve uygulanması basit ve açıklayıcıdır. Güzel!
katma yazar amaurea, kaynak
Sanırım ilk çözümün işe yarayacak, ama buna pek düşkün değilim. Bence f ve g değerlerini çok sıkı bir şekilde birleştiriyor. g kullanıcısı temel olarak bir "f-parametreler" nesnesinden geçiyordu; bu, uygulama kodunu f g kullanıcıya
katma yazar amaurea, kaynak
İkinci önerinin ne eklediğini anlamıyorum. Basitçe def f (a, b) 'den farkı nedir: pass; def g (a = 1, b = 2): f (a, b) . Varsayılanları yalnızca bir kez belirtmek kolaydır, yalnızca en üstte yaparsanız. Sorun şu ki, f 'nin tek başına mantıklı varsayılanlara sahip bağımsız bir işlev olarak çalışmasını istiyorum. f 'un tipik kullanım durumu, g tarafından çağrılmamalıdır. Bu sadece bir olası kullanımı.
katma yazar amaurea, kaynak

Bence prosedürel paradigma, bu soruna bakışınızı daraltıyor. Diğer Python özelliklerini kullanırken bulduğum bazı çözümler.

Nesne yönelimli programlama

Aynı parametre alt grubuyla f() ve g() 'i çağırıyorsunuz - bu, bu parametrelerin aynı varlığı temsil ettiği için iyi bir ipucu. Neden bir nesne yapmıyorsun?

class FG:
    def __init__(self, accuracy=1e-3, nstep=10):
        self.accuracy = accuracy
        self.nstep = nstep

    def f(self):
        print ('f', self.accuracy, self.nstep)

    def g(self):
        self.f()
        print ('g', self.accuracy, self.nstep)

FG().f()
FG(1e-5).g()
FG(nstep=20).g()

İşlevsel programlama

f() 'ı daha üst düzey bir işleve dönüştürebilirsiniz - başka bir deyişle:

from functools import partial

def g(accuracy, nstep):
    print ('g', accuracy, nstep)

def f(accuracy=1e-3, nstep=10):
    g(accuracy, nstep)
    print ('f', accuracy, nstep)

def fg(func, accuracy=1e-3, nstep=10):
    return partial(func, accuracy=accuracy, nstep=nstep)

fg(g)()
fg(f, 2e-5)()
fg(f, nstep=32)()

Ancak bu aynı zamanda zor bir yaklaşım - burada f() ve g() çağrıları değiştirildi. Muhtemelen bunu yapmak için daha iyi yaklaşımlar var - yani, geri aramaları olan boru hatları, FP :( ile iyi değilim

Dynamicness & introspection

Bu çok daha karmaşık bir yaklaşımdır ve CPython'un iç dünyalarına kazmayı gerektirir, ancak CPython buna izin verdiğinden, neden kullanmıyorsunuz?

__defaults__ üyesi aracılığıyla varsayılan değerleri güncellemek için bir dekoratör:

class use_defaults:
    def __init__(self, deflt_func):
        self.deflt_func = deflt_func

    def __call__(self, func):
        defltargs = dict(zip(getargspec(self.deflt_func).args, 
                            getargspec(self.deflt_func).defaults))

        defaults = (list(func.__defaults__) 
                    if func.__defaults__ is not None 
                    else [])

        func_args = reversed(getargspec(func).args[:-len(defaults)])

        for func_arg in func_args:
            if func_arg not in defltargs:
                # Default arguments doesn't allow gaps, ignore rest
                break
            defaults.insert(0, defltargs[func_arg])

        # Update list of default arguments
        func.__defaults__ = tuple(defaults)

        return func

def f(accuracy=1e-3, nstep=10, b = 'bbb'):
    print ('f', accuracy, nstep, b)

@use_defaults(f)
def g(first, accuracy, nstep, a = 'aaa'):
    f(accuracy, nstep)
    print ('g', first, accuracy, nstep, a)

g(True)
g(False, 2e-5)
g(True, nstep=32)

Bununla birlikte, __kwdefaults__ 'dan ayrı, yalnızca anahtar kelime bağımsız değişkenlerini dışlar ve muhtemelen use_defaults dekoratörünün arkasındaki mantığı patlatır.

Ayrıca sarıcı kullanarak çalışma zamanında değişkenler ekleyebilirsiniz, ancak bu muhtemelen performansı düşürür.

4
katma
@amaurea, Cevabımı kabul ettiğin için teşekkürler! İşlevsel programlama örneğini değiştirdim, şimdi f() ve g() arasındaki sıkı bağımlılık bozuldu. Ayrıca, cevabım kavramsaldır ve seçilmesi gereken yol f ve g türüne bağlıdır.
katma yazar myaut, kaynak
Sanırım ilk çözümün işe yarayacak, ama buna pek düşkün değilim. Bence f ve g değerlerini çok sıkı bir şekilde birleştiriyor. g kullanıcısı temel olarak bir "f-parametreler" nesnesinden geçiyordu; bu, uygulama kodunu f g kullanıcıya
katma yazar amaurea, kaynak
İkinci önerinin ne eklediğini anlamıyorum. Basitçe def f (a, b) 'den farkı nedir: pass; def g (a = 1, b = 2): f (a, b) . Varsayılanları yalnızca bir kez belirtmek kolaydır, yalnızca en üstte yaparsanız. Sorun şu ki, f 'nin tek başına mantıklı varsayılanlara sahip bağımsız bir işlev olarak çalışmasını istiyorum. f 'un tipik kullanım durumu, g tarafından çağrılmamalıdır. Bu sadece bir olası kullanımı.
katma yazar amaurea, kaynak
Üçüncü önerin çok ilginçti. Böyle bir şeyin mümkün olabileceğini düşünmemiştim. Dekoratörün bir işleve uygulanması basit ve açıklayıcıdır. Güzel!
katma yazar amaurea, kaynak

@Unutbu ile kırlangıç:

Paket yapı kullanıyorsanız:

mypackage
|
+- __init__.py
|
+- fmod.py
|
+- gmod.py
|
...

sonra __init__.py 'de sabitlerinizi @unutbu'nun önerdiği gibi koyun:

ACCURACY = 1e-3
NSTEP = 10
__all__ = ['ACCURACY', 'NSTEP']

then in fmod.py

from mypackage import *
def f(accuracy=ACCURACY, nstep=NSTEP):
    ...

ve gmod.py ve diğer tüm modüller sabitlerinizi alır.

from mypackage import *
def g(accuracy=ACCURACY, nstep=NSTEP):
    f(accuracy, nstep)
    ...

Veya paketleri kullanmıyorsanız, yalnızca myconstants.py adlı bir modül oluşturun ve mypackage öğesinden içe aktarmak yerine, __init__.py ile aynı işlemi yapın. , konsoloslarım 'dan içe aktarırsınız.

Bu stilin bir avantajı, sabitleri bir dosyadan (ya da bir işlevin argümanları olarak) var olduğunu varsayarak okumak istiyorsanız, kodu __init__.py veya myconstant'lara koyabileceğinizdir. .py bunu yapmak için.

3
katma

@Unutbu ile kırlangıç:

Paket yapı kullanıyorsanız:

mypackage
|
+- __init__.py
|
+- fmod.py
|
+- gmod.py
|
...

sonra __init__.py 'de sabitlerinizi @unutbu'nun önerdiği gibi koyun:

ACCURACY = 1e-3
NSTEP = 10
__all__ = ['ACCURACY', 'NSTEP']

then in fmod.py

from mypackage import *
def f(accuracy=ACCURACY, nstep=NSTEP):
    ...

ve gmod.py ve diğer tüm modüller sabitlerinizi alır.

from mypackage import *
def g(accuracy=ACCURACY, nstep=NSTEP):
    f(accuracy, nstep)
    ...

Veya paketleri kullanmıyorsanız, yalnızca myconstants.py adlı bir modül oluşturun ve mypackage öğesinden içe aktarmak yerine, __init__.py ile aynı işlemi yapın. , konsoloslarım 'dan içe aktarırsınız.

Bu stilin bir avantajı, sabitleri bir dosyadan (ya da bir işlevin argümanları olarak) var olduğunu varsayarak okumak istiyorsanız, kodu __init__.py veya myconstant'lara koyabileceğinizdir. .py bunu yapmak için.

3
katma

Benim favorim kwargs!

def f(**kwargs):
    kwargs.get('accuracy', 1e-3)
    ..

def g(**kwargs):
    f(**kwargs)

Tabii ki, yukarıda tarif edildiği gibi sabitleri kullanmaktan çekinmeyin.

2
katma
pop 'ı ve g içinde kwargs ' a ekleyebilirsiniz. Kwargs'ün amacı, kod boyunca birçok kez bir işlev çağırırken iki yerdeki değişikliği yapmanızdır (yani, tüm f çağrıları yerine bir, b, c, ... z ye sahip olursunuz). hepsinden daha fazla. Siz de isterseniz imzayı f olarak f (doğruluk = Yok ...) olarak tanımlayabilirsiniz.
katma yazar C.B., kaynak
Biri kwargs ile birbirini çağıran birden fazla işlev düzeyi oluşturursa, bir işlevde kwargs öğesini bir işlevde değiştirmek tehlikelidir, çünkü bir kardeş işlevi için tasarlanan bir seçeneği yeniden adlandırabilir. Bu yüzden değiştirilmeden önce bir kwargs kopyası çıkarmanız gerekir. Yine de, kwargs yaklaşımıyla ilgili iyi bir şey, g öğesinin f argümanlarının adları ve sayılarıyla bile artık agnostik olabilir. sadece varsayılan değerlerine. Biri f 'ya yeni bir parametre eklerse, g otomatik olarak onu destekler (bir koleksiyon yoksa).
katma yazar amaurea, kaynak
Bu yaklaşım, gerçek varsayılanların f gövdesi içinde tanımlandığı için Yok yaklaşımına benzer. Ancak kwargs g ile bağımsız değişkenleri yeniden adlandıramazsınız, bu nedenle g hem f1 hem de f2'yi çağırırsa ve bu işlevlerin çakışan argüman adları var, bir sorununuz var. kwargs yaklaşımı, bir işlevin gerçekte hangi argümanları aldığını gizlemesini de sağlar. Bu yüzden genel olarak None için bu yaklaşımı tercih ettiğime emin değilim.
katma yazar amaurea, kaynak