İç içe geçmiş işlev, yalnızca bir işlev gerektirdiğinde iyi bir yaklaşım mıdır? [kapalı]


149

Diyelim ki a function Asadece function BB içinde mi tanımlanmalı?

Basit bir örnek. Biri diğerinden çağrılan iki yöntem:

def method_a(arg):
    some_data = method_b(arg)

def method_b(arg):
    return some_data

Python'da bir defbaşkasının içinde ilan edebiliriz def. Öyleyse, method_bgerekliyse ve sadece içinden çağrılıyorsa method_a, method_biçeriden method_ami beyan etmeliyim ? bunun gibi :

def method_a(arg):
    
    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Yoksa bunu yapmaktan kaçınmalı mıyım?


7
GERÇEKTEN korkak bir şey yapmıyorsanız, başka bir fonksiyonun içinde tanımlamanıza gerek yoktur. Ancak, daha yararlı bir cevap verebilmemiz için lütfen ne yapmaya çalıştığınızı ayrıntılı olarak
inspectorG4dget

6
Eğer yok çünkü ikinci örnek, farklı olduğunu farkında mısın diyoruz method_b ? (@inspector: Kesinlikle buna ihtiyacınız var, ancak biraz işlevsel programlamaya, özellikle de kapanışlara girdiğinizde son derece yararlıdır).

3
@delnan: Sanırım "Buna ihtiyacın yok , ama ..." demek istedin
martineau

@Delnan'ın da belirttiği gibi, bu kapanma durumunda yaygındır, bu yüzden korkak olarak nitelendirildiğini düşünmüyorum; ancak, kapatmalar gerekli olmadıkça (ki bu durumda olmadıklarını tahmin ediyorum), bir işlevi diğerinin içine koymak gerekli, verimli veya düzenli görünmüyor. Kapanışa ihtiyacın olmadığı sürece, ilk kalıba sadık kalırım.
Trevor

4
İç işlevlerin kullanım durumları bağlantıda harika bir şekilde özetlenmiştir: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Kullanımınız vakaların hiçbirine uymuyorsa, bundan kaçının.

Yanıtlar:


137
>>> def sum(x, y):
...     def do_it():
...             return x + y
...     return do_it
... 
>>> a = sum(1, 3)
>>> a
<function do_it at 0xb772b304>
>>> a()
4

Aradığın bu muydu? Buna kapatma deniyor .


neden sadece def toplamı (x, y): x + y döndürmüyor?
mango

4
@mango: Bu, kavramı ifade etmek için sadece bir oyuncak örneğidir - gerçek kullanımda do_it(), muhtemelen bir aritmetik tarafından tek bir returnifadede ele alınabilecek olandan biraz daha karmaşık olacaktır .
martineau

2
@mango Sorunuzu biraz daha faydalı bir örnekle yanıtladı. stackoverflow.com/a/24090940/2125392
CivFan

12
Soruyu cevaplamıyor.
Hunsu

Güzel cevap ama soru nerede? Burada değil. OP, method_b yalnızca method_a tarafından kullanılıyorsa, yukarıdakilerin yapılıp yapılamayacağını sordu. Başka bir şey sormadı. Kapatma yok.
Mayou36

49

Bunu yaparak pek bir şey kazanmazsınız, aslında yavaşlar method_açünkü her çağrıldığında diğer işlevi tanımlar ve yeniden derler. Buna göre, bunun özel bir yöntem olduğunu belirtmek için işlev adının önüne alt çizgi koymak muhtemelen daha iyi olacaktır - yani _method_b.

Sana varsayalım olabilir iç içe işlevin tanımı nedense her seferinde değişik eğer bunu yapmak istiyor, ama bu senin tasarımında bir kusur gösterebilir. Oradaki söyledi olup iç içe işlev dış işleve geçirilen ancak açıkça örneğin, fonksiyon dekoratörler yazarken bazen meydana geldiği, onlara bindirilmemekte argümanları kullanmasına izin vermek Bunu yapmak için geçerli bir sebep. Bir dekoratör tanımlanmamasına veya kullanılmamasına rağmen, kabul edilen cevapta gösterilen şey budur.

Güncelleme:

Kuşkusuz bu önemsiz durumda çok fazla olmasa da, onları iç içe yerleştirmenin (Python 3.6.1 kullanarak) daha yavaş olduğunun kanıtı:

setup = """
class Test(object):
    def separate(self, arg):
        some_data = self._method_b(arg)

    def _method_b(self, arg):
        return arg+1

    def nested(self, arg):

        def method_b2(self, arg):
            return arg+1

        some_data = method_b2(self, arg)

obj = Test()
"""
from timeit import Timer
print(min(Timer(stmt='obj.separate(42)', setup=setup).repeat()))  # -> 0.24479823284461724
print(min(Timer(stmt='obj.nested(42)', setup=setup).repeat()))    # -> 0.26553459700452575

Not selfÖrnek işlevlerinize onları gerçek yöntemler gibi yapmak için bazı argümanlar ekledim ( method_b2yine de teknik olarak Testsınıfın bir yöntemi olmasa da ). Ayrıca, sizinkinden farklı olarak, bu sürümde yuvalanmış işlev de çağrılır.


21
Aslında, dış işlev her çağrıldığında iç işlevi tam olarak derlemez, ancak biraz zaman alan bir işlev nesnesi oluşturması gerekir. Öte yandan, işlev adı yerel olarak küresel hale gelir, bu nedenle işlevi çağırmak daha hızlıdır. Denemelerimde, zaman açısından temelde çoğu zaman bir yıkamadır; birçok kez çağırırsanız, iç işlevle daha da hızlı olabilir.
biraz

7
Evet, iç işlev için birden fazla aramaya ihtiyacınız olacak. Bunu bir döngü içinde veya birkaç kereden fazla çağırıyorsanız, işlev için yerel bir ada sahip olmanın yararı, işlevi oluşturma maliyetinden daha ağır basmaya başlayacaktır. Denemelerimde, bu, iç işlevi yaklaşık 3-4 kez çağırdığınızda olur. Elbette, işlev için yerel bir ad tanımlayarak (neredeyse maliyet olmadan) aynı faydayı elde edebilirsiniz, örneğin method_b = self._method_bve ardından method_btekrarlanan öznitelik aramalarından kaçınmak için çağrı yapabilirsiniz . (Son zamanlarda çok fazla zamanlama yapıyorum. :)
lütfen

3
@kindall: Evet, bu doğru. Zamanlama testimi değiştirdim, böylece ikincil işleve 30 çağrı yaptı ve sonuçlar tersine döndü. Bu yanıtı görme şansınız olduktan sonra cevabımı sileceğim. Aydınlanma için teşekkürler.
martineau

3
Sadece -1'imi açıklamak istedim çünkü bu yine de bilgilendirici bir cevap: Buna -1 verdim çünkü önemsiz bir performans farkına odaklanıyor (çoğu senaryoda kod nesnesi oluşturma, işlevin yürütme süresinin yalnızca bir kısmını alacaktır. ). Bu durumlarda dikkate alınması gereken önemli olan şey, bir satır içi işleve sahip olmanın kod okunabilirliğini ve sürdürülebilirliğini iyileştirip iyileştiremeyeceğidir; bence bu, ilgili kodu bulmak için dosyalarda arama / kaydırma yapmanız gerekmediğinden bunu sıklıkla yapar.
Blixt

2
@Blixt: Bence mantığınız kusurlu (ve haksız oy) çünkü aynı sınıftaki başka bir yöntemin aynı sınıftaki bir diğerinden çok "uzak" olması, iç içe olmasa bile (ve son derece olası değil) farklı bir dosyada olmak). Ayrıca cevabımdaki ilk cümle, "Bunu yaparak pek fazla kazanmıyorsunuz", bunun önemsiz bir fark olduğuna işaret ediyor.
martineau

27

Bir işlevin içindeki işlev, genellikle kapanışlar için kullanılır .

(A Orada çekişme çok üzerinde tam olarak ne yapar bir kapatma kapatma .)

İşte yerleşik kullanan bir örnek sum(). Bir startkez tanımlar ve bundan sonra onu kullanır:

def sum_partial(start):
    def sum_start(iterable):
        return sum(iterable, start)
    return sum_start

Kullanımda:

>>> sum_with_1 = sum_partial(1)
>>> sum_with_3 = sum_partial(3)
>>> 
>>> sum_with_1
<function sum_start at 0x7f3726e70b90>
>>> sum_with_3
<function sum_start at 0x7f3726e70c08>
>>> sum_with_1((1,2,3))
7
>>> sum_with_3((1,2,3))
9

Yerleşik python kapatma

functools.partial bir kapanış örneğidir.

Python belgelerinden kabaca şuna eşdeğerdir:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

(Yanıt için aşağıdaki @ user225312'ye tebrikler. Bu örneği daha kolay buluyorum ve umarım @ mango'nun yorumunu yanıtlamaya yardımcı olurum.)


-1 evet, genellikle kapak olarak kullanılırlar. Şimdi soruyu tekrar okuyun. Temel olarak, gösterdiği kavramın durum b için kullanılıp kullanılamayacağını sordu. Ona bunun genellikle a durumu için kullanıldığını söylemek kötü bir cevap değil, bu soru için yanlış cevaptır. Örneğin kapsülleme için yukarıdakileri yapmanın iyi bir şey olup olmadığı ile ilgileniyor.
Mayou36

@ Mayou36 Dürüst olmak gerekirse, soru oldukça açık uçlu - olası her vakayı yanıtlamaya çalışmak yerine, en iyisi birine odaklanmanın iyi olacağını düşündüm. Ayrıca soru çok net değil. Örneğin, kastedilen bu olmasa da, ikinci örnekte kapanışı ima ediyor.
CivFan

@ Mayou36 "Temelde gösterdiği konseptin olabilir vaka b kullanılacaktır." O Hayır, soru sorar gerektiğini kullanılabilir. OP zaten kullanılabileceğini biliyor.
CivFan

1
iyi, "method_b için gerekliyse ve yalnızca method_a'dan çağrıldıysa, method_b'yi method_a içinde bildirmeli miyim?" oldukça açık ve kapanışlarla hiçbir ilgisi yok, değil mi? Evet, kabul ediyorum, gerektiği şekilde can kullandım . Ancak bunun kapanışlarla bir ilgisi yok ... OP tamamen farklı bir soru sorduğunda kapanışlar ve bunların nasıl kullanılacağı hakkında bu kadar çok yanıt almasına şaşırdım.
Mayou36

@ Mayou36 Soruyu nasıl gördüğünüzü yeniden ifade etmek ve onu ele almak için başka bir soru açmak yardımcı olabilir.
CivFan

18

Genel olarak hayır, fonksiyonların içindeki fonksiyonları tanımlamayın.

Gerçekten iyi bir sebebin yoksa. Sen yapmıyorsun.

Neden olmasın?

Ne olduğunu fonksiyonları içinde işlevlerini tanımlamak için gerçekten iyi bir sebebi var mı?

Ne zaman ne aslında istediğiniz bir Dingdang olan kapatma .


10

Bir işlevi diğerinin içinde belirtmek aslında sorun değil. Bu, özellikle dekoratörler oluşturmak için kullanışlıdır.

Bununla birlikte, genel bir kural olarak, işlev karmaşıksa (10 satırdan fazla), onu modül düzeyinde bildirmek daha iyi bir fikir olabilir.


2
Mümkün, ama kabul ediyorum, bunu yapmak için iyi bir nedene ihtiyacın var. Yalnızca kendi sınıfınızda kullanılması amaçlanan bir işlev için önceki bir alt çizgi kullanmak daha fazla python olacaktır.
chmullig

3
Bir sınıf içinde, evet, peki ya sadece bir işlevin içinde ? Kapsülleme hiyerarşiktir.
Paul Draper

@PaulDraper "Kapsülleme hiyerarşiktir" - Hayır! Bunu kim söyledi? Kapsülleme, bazı kalıtımlardan çok daha geniş bir prensiptir.
Mayou36

8

Bu soruyu buldum çünkü iç içe geçmiş işlevler kullanılıyorsa neden performans etkisi olduğunu sormak istedim. Dört Çekirdekli 2.5 GHz Intel i5-2530M işlemciye sahip bir Windows dizüstü bilgisayarda Python 3.2.5 kullanarak aşağıdaki işlevler için testler yaptım

def square0(x):
    return x*x

def square1(x):
    def dummy(y):
        return y*y
    return x*x

def square2(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    return x*x

def square5(x):
    def dummy1(y):
        return y*y
    def dummy2(y):
        return y*y
    def dummy3(y):
        return y*y
    def dummy4(y):
        return y*y
    def dummy5(y):
        return y*y
    return x*x

Aşağıdaki 20 kez kare1, kare2 ve kare5 için ölçtüm:

s=0
for i in range(10**6):
    s+=square0(i)

ve aşağıdaki sonuçları aldı

>>> 
m = mean, s = standard deviation, m0 = mean of first testcase
[m-3s,m+3s] is a 0.997 confidence interval if normal distributed

square? m     s       m/m0  [m-3s ,m+3s ]
square0 0.387 0.01515 1.000 [0.342,0.433]
square1 0.460 0.01422 1.188 [0.417,0.503]
square2 0.552 0.01803 1.425 [0.498,0.606]
square5 0.766 0.01654 1.979 [0.717,0.816]
>>> 

square0yuvalanmış bir işlevi yoktur square1, bir yuvalanmış işlevi square2vardır, iki yuvalanmış işlevi vardır vesquare5 beş yuvalanmış işlevi vardır. İç içe geçmiş işlevler yalnızca bildirilir ancak çağrılmaz.

Dolayısıyla, çağırmadığınız bir işlevde 5 iç içe geçmiş işlev tanımladıysanız, işlevin yürütme süresi iç içe geçmiş bir işlev olmadan işlevin iki katıdır. İç içe geçmiş işlevleri kullanırken dikkatli olunması gerektiğini düşünüyorum.

Bu çıktıyı oluşturan tüm testin Python dosyası ideone'da bulunabilir .


5
Yaptığınız karşılaştırma gerçekten yararlı değil. Bu, işleve sahte ifadeler eklemek ve daha yavaş olduğunu söylemek gibidir. Martineau örneği aslında kapsüllenmiş işlevleri kullanıyor ve onun örneğini çalıştırdığımda herhangi bir performans farkı görmüyorum.
kon psych

-1 lütfen soruyu tekrar okuyunuz. Belki biraz daha yavaş olduğunu söyleseniz de, bunu yapmanın iyi bir fikir olup olmadığını sordu, esas olarak performans nedeniyle değil, genel uygulama için.
Mayou36

4

Bu sadece maruziyet API'leri ile ilgili bir ilkedir.

Python kullanarak, API dış uzayda (modül veya sınıf) maruziyetten kaçınmak iyi bir fikirdir, işlev iyi bir kapsülleme yeridir.

İyi bir fikir olabilir. emin olduğunuzda

  1. iç işlev YALNIZCA dış işlev tarafından kullanılır.
  2. içeriden öğrenen işlevin amacını açıklamak için iyi bir adı vardır çünkü kod konuşur.
  3. kod, meslektaşlarınız (veya diğer kod okuyucular) tarafından doğrudan anlaşılamaz.

Yine de, bu tekniği Kötüye Kullan, sorunlara neden olabilir ve bir tasarım hatası anlamına gelir.

Sadece uzmanımdan, Belki sorunuzu yanlış anlarsınız.


4

Dolayısıyla, sonuçta bu büyük ölçüde python uygulamasının ne kadar akıllı olduğu veya olmadığı ile ilgili bir sorudur, özellikle de iç işlevin bir kapanma olmadığı, yalnızca yalnızca bir işlev için gerekli olan yardımcı olması durumunda.

Yalnızca ihtiyaç duyulduğu yerde işlevlere sahip olan ve başka bir yerde açığa çıkmayan temiz anlaşılır tasarımda, bunlar bir modüle, bir yöntem olarak bir sınıfa veya başka bir işlev veya yönteme gömülmüş olsalar da, iyi tasarımdır. İyi yapıldığında, kodun netliğini gerçekten geliştirirler.

Ve iç işlev, başka bir yerde kullanılmak üzere kapsayıcı işlevden döndürülmemiş olsa bile, açıklığa oldukça yardımcı olabilecek bir kapanış olduğunda.

Bu nedenle, genel olarak bunları kullanın derim, ancak gerçekten performans konusunda endişelendiğinizde olası performans vuruşunun farkında olun ve yalnızca, en iyi şekilde kaldırılabileceklerini gösteren gerçek bir profilleme yaparsanız kaldırın.

Yazdığınız tüm python kodlarında sadece "iç işlevler KÖTÜ" kullanarak erken optimizasyon yapmayın. Lütfen.


Diğer yanıtların da gösterdiği gibi, gerçekten performans isabetlerine sahip değilsiniz.
Mayou36

1

Bunu bu şekilde yapmak tamamen sorun değil, ancak bir kapatma kullanmanız veya işlevi geri almanız gerekmedikçe, muhtemelen modül seviyesine koyardım. İkinci kod örneğinde şunu kastettiğinizi hayal ediyorum:

...
some_data = method_b() # not some_data = method_b

aksi takdirde, bazı_veriler işlev olacaktır.

Modül düzeyinde olması, diğer işlevlerin method_b () kullanmasına izin verir ve Sphinx gibi bir şey kullanıyorsanız ve dokümantasyon için (ve autodoc) , method_b'yi de belgelemenizi sağlar.

Ayrıca, bir nesne tarafından temsil edilebilecek bir şey yapıyorsanız, işlevselliği bir sınıftaki iki yönteme koymayı da düşünebilirsiniz. Bu da mantığı içerir, eğer aradığınız buysa.


1

Şöyle bir şey yapın:

def some_function():
    return some_other_function()
def some_other_function():
    return 42 

Eğer çalıştıracak some_function()olsaydın, o zaman çalışır some_other_function()ve 42'yi döndürür.

DÜZENLEME: Başlangıçta başka bir fonksiyonun içinde bir fonksiyon tanımlamamanız gerektiğini söylemiştim, ancak bazen bunu yapmanın pratik olduğu belirtilmişti.


Yukarıdakilerin cevaplarına gösterdiğiniz çabayı takdir ediyorum, ancak sizinki doğrudan ve konuyla ilgiliydi. Güzel.
C0NFUS3D

1
Neden? Bunu neden yapmıyorsun? Bu harika bir kapsülleme değil mi? Herhangi bir argümanı kaçırıyorum. -1
Mayou36

@ Mayou36 Yorumumu yazarken kapsüllemenin ne olduğunu bilmiyordum, ne de şimdi ne olduğunu gerçekten bilmiyorum. Yapmanın iyi olmadığını düşündüm. Bir işlevi sadece dışında tanımlamak yerine, başka birinin içinde tanımlamanın neden yararlı olacağını açıklayabilir misiniz?
mdlp0716

2
Evet yapabilirim. Kapsülleme kavramına bakabilirsiniz, ancak kısaca: gerekli olmayan bilgileri gizleyin ve yalnızca bilmesi gerekenleri kullanıcıya gösterin. Bunun anlamı, bir_diğer_fonksiyonun dışarıda tanımlanması, ad alanına aslında ilk işleve sıkı sıkıya bağlı olan bir şey daha ekler. Veya değişkenler açısından düşünün: neden yerel değişkenlere karşı küresel değişkenlere ihtiyacınız var? Mümkünse, bir işlev içindeki tüm değişkenleri tanımlamak, yalnızca bu işlev içinde kullanılan değişkenler için genel değişkenleri kullanmaktan çok daha iyidir . Her şey sonunda karmaşıklığı azaltmakla ilgili.
Mayou 36

0

Global değişkenleri tanımlamaktan kaçınmak için kullanabilirsiniz. Bu size diğer tasarımlar için bir alternatif sunar. Bir probleme çözüm sunan 3 tasarım.

A) Küresel olmayan işlevleri kullanma

def calculate_salary(employee, list_with_all_employees):
    x = _calculate_tax(list_with_all_employees)

    # some other calculations done to x
    pass

    y = # something 

    return y

def _calculate_tax(list_with_all_employees):
    return 1.23456 # return something

B) Küresellerle işlevleri kullanma

_list_with_all_employees = None

def calculate_salary(employee, list_with_all_employees):

    global _list_with_all_employees
    _list_with_all_employees = list_with_all_employees

    x = _calculate_tax()

    # some other calculations done to x
    pass

    y = # something

    return y

def _calculate_tax():
    return 1.23456 # return something based on the _list_with_all_employees var

C) Başka bir işlevin içindeki işlevleri kullanma

def calculate_salary(employee, list_with_all_employees):

    def _calculate_tax():
        return 1.23456 # return something based on the list_with_a--Lemployees var

    x = _calculate_tax()

    # some other calculations done to x
    pass
    y = # something 

    return y

Çözüm C) , değişkenleri iç işlevde bildirmeye gerek kalmadan dış işlev kapsamında kullanmaya izin verir. Bazı durumlarda faydalı olabilir.


0

İşlev python işlevinde

def Greater(a,b):
    if a>b:
        return a
    return b

def Greater_new(a,b,c,d):
    return Greater(Greater(a,b),Greater(c,d))

print("Greater Number is :-",Greater_new(212,33,11,999))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.