Jika fungsi A hanya diperlukan oleh fungsi B haruskah A didefinisikan dalam B? [Tutup]

147

Contoh sederhana. Dua metode, satu dipanggil dari yang lain:

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

def method_b(arg):
    return some_data

Dengan Python kita bisa mendeklarasikan defdi dalam yang lain def. Jadi, jika method_bdiperlukan dan dipanggil hanya dari method_a, haruskah saya menyatakan method_bdi dalam method_a? seperti ini :

def method_a(arg):

    def method_b(arg):
        return some_data

    some_data = method_b(arg)

Atau haruskah saya menghindari melakukan ini?

nukl
sumber
7
Anda tidak perlu mendefinisikan suatu fungsi di dalam fungsi yang lain kecuali jika Anda melakukan sesuatu yang BENAR-BENAR funky. Namun, tolong jelaskan apa yang Anda coba lakukan, sehingga kami dapat memberikan jawaban yang lebih bermanfaat
inspectorG4dget
6
Apakah Anda menyadari bahwa contoh kedua berbeda, karena Anda tidak menelepon method_b ? (@inspector: Anda benar-benar perlu, tetapi ini sangat berguna ketika Anda masuk ke sedikit pemrograman fungsional, khususnya penutupan).
3
@delnan: Saya pikir Anda maksudkan "Anda tidak perlu, secara tegas, tapi ..."
martineau
4
Casing penggunaan untuk fungsi dalam diringkas dengan luar biasa di tautan: https://realpython.com/blog/python/inner-functions-what-are-they-good-for/ . Jika penggunaan Anda tidak cocok dengan salah satu kasing, lebih baik hindari.
1
Pertanyaan yang bagus tapi sepertinya tidak ada jawaban nyata ...
Mayou36

Jawaban:

136
>>> 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

Apakah ini yang Anda cari? Ini disebut penutupan .

pengguna225312
sumber
14
Ini penjelasan yang jauh lebih baik. Saya telah menghapus jawaban saya
pyfunc
mengapa tidak hanya melakukan def sum (x, y): return x + y?
mangga
4
@mango: Ini hanya contoh mainan untuk menyampaikan konsep - dalam penggunaan sebenarnya apa yang do_it()mungkin akan sedikit lebih rumit daripada apa yang bisa ditangani oleh beberapa aritmatika dalam satu returnpernyataan.
martineau
2
@mango Menjawab pertanyaan Anda dengan contoh yang sedikit lebih bermanfaat. stackoverflow.com/a/24090940/2125392
CivFan
10
Itu tidak menjawab pertanyaan.
Hunsu
49

Anda tidak benar-benar mendapatkan banyak dengan melakukan ini, bahkan melambat method_akarena akan mendefinisikan dan mengkompilasi ulang fungsi lain setiap kali dipanggil. Mengingat itu, mungkin akan lebih baik untuk hanya awalan nama fungsi dengan garis bawah untuk menunjukkan itu metode pribadi - yaitu _method_b.

Saya kira Anda mungkin ingin melakukan ini jika definisi fungsi bersarang bervariasi setiap kali karena beberapa alasan, tetapi itu mungkin menunjukkan kesalahan dalam desain Anda. Yang mengatakan, ada adalah alasan yang sah untuk melakukan hal ini untuk memungkinkan fungsi bersarang menggunakan argumen yang dilewatkan ke fungsi luar tapi tidak secara eksplisit diteruskan kepada mereka, yang kadang-kadang terjadi ketika menulis dekorator fungsi, misalnya. Itu yang ditunjukkan dalam jawaban yang diterima meskipun dekorator tidak didefinisikan atau digunakan.

Memperbarui:

Inilah bukti bahwa bersarangnya lebih lambat (menggunakan Python 3.6.1), meskipun diakui tidak banyak dalam kasus sepele ini:

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

Catatan saya menambahkan beberapa selfargumen ke fungsi sampel Anda untuk membuatnya lebih seperti metode nyata (meskipun method_b2secara teknis masih bukan metode Testkelas). Fungsi bersarang juga disebut dalam versi itu, tidak seperti milik Anda.

martineau
sumber
21
Ini sebenarnya tidak sepenuhnya mengkompilasi fungsi dalam setiap kali fungsi luar dipanggil, meskipun itu memang harus membuat objek fungsi, yang memang membutuhkan waktu sedikit. Di sisi lain, nama fungsi menjadi lokal bukan global, jadi lebih cepat memanggil fungsi. Dalam uji coba saya, waktu-bijaksana itu pada dasarnya mencuci sebagian besar waktu; bahkan mungkin lebih cepat dengan fungsi batin jika Anda menyebutnya berkali-kali.
hati
7
Ya, Anda perlu beberapa panggilan ke fungsi bagian dalam. Jika Anda memanggilnya dalam satu lingkaran, atau lebih dari beberapa kali, manfaat memiliki nama lokal untuk fungsi akan mulai lebih besar daripada biaya pembuatan fungsi. Dalam cobaan saya, ini terjadi ketika Anda memanggil fungsi dalam sekitar 3-4 kali. Tentu saja, Anda bisa mendapatkan manfaat yang sama (tanpa biaya yang hampir sama) dengan mendefinisikan nama lokal untuk fungsi tersebut, misalnya method_b = self._method_bdan kemudian menelepon method_buntuk menghindari pencarian atribut berulang. (Kebetulan saya telah melakukan BANYAK pengaturan waktu belakangan ini. :)
kindall
3
@indall: Ya, itu benar. Saya memodifikasi tes waktu saya sehingga membuat 30 panggilan ke fungsi sekunder dan hasilnya berbalik. Saya akan menghapus jawaban saya setelah Anda memiliki kesempatan untuk melihat balasan ini. Terima kasih atas pencerahannya.
martineau
2
Hanya ingin menjelaskan -1 saya karena ini adalah jawaban informatif namun: Saya memberikan ini -1 karena menempatkan fokus pada perbedaan kinerja yang sepele (dalam kebanyakan skenario pembuatan objek kode hanya akan mengambil sebagian kecil dari waktu eksekusi fungsi) ). Yang penting untuk dipertimbangkan dalam kasus-kasus ini adalah apakah memiliki fungsi inline dapat meningkatkan keterbacaan dan pemeliharaan kode, yang menurut saya sering dilakukan karena Anda tidak perlu mencari / menggulir file untuk menemukan kode yang relevan.
Blixt
2
@Blixt: Saya pikir logika Anda cacat (dan turunkan suara tidak adil) karena sangat tidak mungkin bahwa metode lain dari kelas yang sama akan sangat "jauh" dari yang lain di kelas yang sama bahkan ketika itu tidak disarangkan (dan sangat tidak mungkin untuk berada di file yang berbeda). Juga kalimat pertama dalam jawaban saya adalah "Anda tidak mendapatkan banyak hal dengan melakukan ini" yang menunjukkan bahwa itu adalah perbedaan yang sepele.
martineau
27

Fungsi di dalam fungsi biasanya digunakan untuk penutupan .

(Ada banyak perdebatan tentang apa yang sebenarnya membuat penutupan menjadi penutupan .)

Berikut ini contoh menggunakan built-in sum(). Ini mendefinisikan startsekali dan menggunakannya sejak saat itu:

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

Digunakan:

>>> 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

Built-in python closure

functools.partial adalah contoh penutupan.

Dari python docs , kira-kira sama dengan:

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

(Kudos ke @ user225312 di bawah untuk jawabannya. Saya menemukan contoh ini lebih mudah untuk diketahui, dan mudah-mudahan akan membantu menjawab komentar @ mangga.)

CivFan
sumber
-1 ya, mereka biasanya digunakan sebagai penutup. Sekarang baca kembali pertanyaannya. Dia pada dasarnya bertanya apakah konsep yang dia tunjukkan dapat digunakan untuk kasus b. Memberitahu dia bahwa ini sering digunakan untuk kasus a bukanlah jawaban yang buruk, tetapi salah untuk pertanyaan ini. Dia tertarik apakah itu hal yang baik untuk dilakukan di atas untuk enkapsulasi misalnya.
Mayou36
@ Mayou36 Agar adil, pertanyaannya agak terbuka - daripada mencoba menjawab setiap kasus yang mungkin, saya pikir lebih baik untuk fokus pada satu. Juga pertanyaannya tidak terlalu jelas. Sebagai contoh, itu menyiratkan penutupan dalam contoh kedua, meskipun itu mungkin tidak apa yang dimaksud.
CivFan
@ Mayou36 "Dia pada dasarnya bertanya apakah konsep yang dia tunjukkan dapat digunakan untuk kasus b." Tidak, pertanyaannya bertanya apakah harus digunakan. OP sudah tahu itu bisa digunakan.
CivFan
1
well, "jika method_b diperlukan untuk dan dipanggil hanya dari method_a, haruskah saya mendeklarasikan method_b di dalam method_a?" cukup jelas dan tidak ada hubungannya dengan penutupan, kan? Ya, saya setuju, saya bisa menggunakan cara yang seharusnya . Tapi itu tidak ada hubungannya dengan penutupan ... Saya hanya terkejut bahwa begitu banyak jawaban tentang penutupan dan bagaimana menggunakannya ketika OP mengajukan pertanyaan yang sama sekali berbeda.
Mayou36
@ Mayou36 Mungkin membantu untuk menulis ulang pertanyaan bagaimana Anda melihatnya, dan membuka pertanyaan lain untuk mengatasinya.
CivFan
17

Secara umum, tidak, jangan mendefinisikan fungsi di dalam fungsi.

Kecuali Anda memiliki alasan yang sangat bagus. Yang mana tidak.

Kenapa tidak?

Apa adalah alasan yang sangat baik untuk mendefinisikan fungsi dalam fungsi?

Ketika apa yang sebenarnya Anda inginkan adalah penutupan dingdang .

CivFan
sumber
1
Jawaban ini untuk Anda @ Mayou36.
CivFan
2
ya, terima kasih, ini jawaban yang saya cari. Ini menjawab pertanyaan dengan cara terbaik. Meskipun tidak secara tegas mengatakan satu atau yang lain, itu sangat tidak mendukung definisi sebaris dengan menyatakan alasan. Begitulah seharusnya jawaban (pythonic :))!
Mayou36
10

Sebenarnya tidak masalah untuk mendeklarasikan satu fungsi di dalam fungsi lainnya. Ini berguna khusus membuat dekorator.

Namun, sebagai aturan praktis, jika fungsinya kompleks (lebih dari 10 baris), mungkin lebih baik untuk mendeklarasikannya pada level modul.

vz0
sumber
2
Itu mungkin, tetapi saya setuju, Anda perlu alasan yang baik untuk melakukannya. Akan lebih python untuk menggunakan garis bawah sebelumnya untuk fungsi yang dimaksudkan hanya digunakan di dalam kelas Anda.
chmullig
3
Di dalam kelas, ya, tetapi bagaimana dengan hanya di dalam suatu fungsi ? Enkapsulasi bersifat hierarkis.
Paul Draper
@PaulDraper "Enkapsulasi bersifat hierarkis" - Tidak! Siapa yang mengatakan itu? Enkapsulasi adalah prinsip yang jauh lebih luas dari sekedar pewarisan.
Mayou36
7

Saya menemukan pertanyaan ini karena saya ingin mengajukan pertanyaan mengapa ada dampak kinerja jika seseorang menggunakan fungsi bersarang. Saya menjalankan tes untuk fungsi-fungsi berikut menggunakan Python 3.2.5 pada Notebook Windows dengan prosesor Intel Core i5-2530M Quad Core 2.5 GHz 2.5 GHz

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

Saya mengukur 20 kali berikut, juga untuk square1, square2, dan square5:

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

dan mendapat hasil sebagai berikut

>>> 
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]
>>> 

square0tidak memiliki fungsi bersarang, square1memiliki satu fungsi bersarang, square2memiliki dua fungsi bersarang dan square5memiliki lima fungsi bersarang. Fungsi bersarang hanya dideklarasikan tetapi tidak dipanggil.

Jadi, jika Anda telah menetapkan 5 fungsi tersarang dalam fungsi yang tidak Anda panggil maka waktu eksekusi dari fungsi tersebut adalah dua kali dari fungsi tanpa fungsi bersarang. Saya pikir harus berhati-hati saat menggunakan fungsi bersarang.

File Python untuk seluruh tes yang menghasilkan output ini dapat ditemukan di ideone .

keajaiban173
sumber
5
Perbandingan yang Anda lakukan tidak terlalu berguna. Ini seperti menambahkan pernyataan dummy ke dalam fungsi dan mengatakan itu lebih lambat. Contoh martineau sebenarnya menggunakan fungsi enkapsulasi dan saya tidak melihat adanya perbedaan kinerja dengan menjalankan contohnya.
kon psych
-1 tolong baca kembali pertanyaannya. Meskipun Anda mungkin mengatakan bahwa itu mungkin sedikit lebih lambat, dia bertanya apakah itu ide yang baik untuk melakukan itu, bukan terutama karena kinerja tetapi dari praktik umum.
Mayou36
@ Mayou36 maaf, tiga tahun setelah pertanyaan dan jawaban diposting saya tidak akan.
miracle173
Oke, sayangnya, masih belum ada jawaban yang diterima (atau bagus).
Mayou36
4

Itu hanya prinsip tentang API eksposur.

Menggunakan python, Ini ide yang bagus untuk menghindari paparan API di luar angkasa (modul atau kelas), fungsinya adalah tempat enkapsulasi yang baik.

Itu bisa jadi ide yang bagus. ketika Anda memastikan

  1. fungsi dalam HANYA digunakan oleh fungsi luar.
  2. fungsi orang dalam memiliki nama yang baik untuk menjelaskan tujuannya karena kode berbicara.
  3. kode tidak dapat langsung dipahami oleh kolega Anda (atau pembaca kode lainnya).

Meskipun demikian, penyalahgunaan teknik ini dapat menyebabkan masalah dan menyiratkan cacat desain.

Hanya dari exp saya, Mungkin salah paham pertanyaan Anda.

chao787
sumber
4

Jadi pada akhirnya sebagian besar pertanyaan tentang seberapa pintar implementasi python adalah atau tidak, terutama dalam kasus fungsi dalam tidak menjadi penutup tetapi hanya dalam fungsi yang dibutuhkan penolong saja.

Dalam desain bersih yang dapat dimengerti hanya memiliki fungsi di mana mereka dibutuhkan dan tidak diekspos di tempat lain adalah desain yang baik apakah mereka tertanam dalam modul, kelas sebagai metode, atau di dalam fungsi atau metode lain. Ketika dilakukan dengan baik, mereka benar-benar meningkatkan kejelasan kode.

Dan ketika fungsi bagian dalam adalah penutup yang juga dapat membantu dengan kejelasan sedikit bahkan jika fungsi itu tidak dikembalikan dari fungsi yang mengandung untuk digunakan di tempat lain.

Jadi saya akan mengatakan secara umum menggunakannya tetapi waspada terhadap kemungkinan hit kinerja ketika Anda benar-benar khawatir tentang kinerja dan hanya menghapusnya jika Anda membuat profil aktual yang menunjukkan mereka sebaiknya dihapus.

Jangan lakukan optimasi prematur hanya dengan menggunakan "fungsi dalam BURUK" di seluruh kode python yang Anda tulis. Silahkan.


sumber
Seperti yang ditunjukkan oleh jawaban lain, Anda tidak benar-benar memiliki hit kinerja.
Mayou36
1

Tidak apa-apa melakukannya seperti itu, tetapi kecuali jika Anda perlu menggunakan penutupan atau mengembalikan fungsi saya mungkin akan dimasukkan ke tingkat modul. Saya membayangkan dalam contoh kode kedua yang Anda maksud:

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

jika tidak, some_data akan menjadi fungsinya.

Setelah itu di tingkat modul akan memungkinkan fungsi lain menggunakan method_b () dan jika Anda menggunakan sesuatu seperti Sphinx (dan autodoc) untuk dokumentasi, itu akan memungkinkan Anda untuk mendokumentasikan method_b juga.

Anda juga mungkin ingin mempertimbangkan hanya menempatkan fungsionalitas dalam dua metode di kelas jika Anda melakukan sesuatu yang dapat diwakili oleh suatu objek. Ini juga mengandung logika jika hanya itu yang Anda cari.

mikelikespie
sumber
1

Lakukan sesuatu seperti:

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

jika Anda menjalankannya some_function()maka akan menjalankan some_other_function()dan mengembalikan 42.

EDIT: Saya awalnya menyatakan bahwa Anda tidak boleh mendefinisikan fungsi di dalam yang lain tapi itu menunjukkan bahwa praktis untuk melakukan hal ini kadang-kadang.

mdlp0716
sumber
Saya menghargai upaya orang-orang di atas yang telah Anda masukkan dalam jawaban mereka, tetapi jawaban Anda langsung dan langsung pada intinya. Bagus.
C0NFUS3D
1
Mengapa? Mengapa Anda tidak melakukan itu? Bukankah enkapsulasi hebat itu? Saya kehilangan argumen. -1
Mayou36
@ Mayou36 Saya tidak tahu enkapsulasi apa pada saat menulis komentar saya, saya juga tidak tahu apa itu sekarang. Saya hanya berpikir itu tidak baik untuk dilakukan. Bisakah Anda menjelaskan mengapa akan bermanfaat untuk mendefinisikan fungsi di dalam yang lain, daripada hanya mendefinisikan di luar itu?
mdlp0716
2
Ya saya bisa. Anda mungkin mencari konsep enkapsulasi, tetapi singkatnya: menyembunyikan informasi yang tidak diperlukan dan hanya memaparkan kepada pengguna apa yang perlu diketahui. Yang berarti, mendefinisikan some_other_function di luar hanya menambahkan sesuatu yang lebih ke namespace yang sebenarnya terikat erat dengan fungsi pertama. Atau pikirkan dalam hal variabel: mengapa Anda memerlukan variabel lokal vs variabel global? Mendefinisikan semua variabel di dalam suatu fungsi, jika mungkin, jauh lebih baik daripada menggunakan variabel global untuk variabel yang hanya digunakan di dalam fungsi yang satu ini . Ini semua tentang mengurangi kompleksitas pada akhirnya.
Mayou36
0

Anda dapat menggunakannya untuk menghindari mendefinisikan variabel global. Ini memberi Anda alternatif untuk desain lain. 3 desain menghadirkan solusi untuk masalah.

A) Menggunakan fungsi tanpa global

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) Menggunakan fungsi dengan global

_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) Menggunakan fungsi di dalam fungsi lain

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

Solusi C) memungkinkan untuk menggunakan variabel dalam lingkup fungsi luar tanpa harus menyatakannya dalam fungsi dalam. Mungkin bermanfaat dalam beberapa situasi.

Elmex80s
sumber
0

Function In function python

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))
pengguna12069064
sumber