Memanggil metode statis dalam tubuh kelas?

159

Ketika saya mencoba menggunakan metode statis dari dalam tubuh kelas, dan mendefinisikan metode statis menggunakan staticmethodfungsi bawaan sebagai dekorator, seperti ini:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Saya mendapatkan kesalahan berikut:

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

Saya mengerti mengapa hal ini terjadi (penjilidan deskriptor) , dan dapat mengatasinya dengan secara manual mengubahnya _stat_func()menjadi metode statis setelah penggunaan terakhir, seperti:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Jadi pertanyaan saya adalah:

Apakah ada cara yang lebih baik, seperti yang lebih bersih atau lebih "Pythonic", untuk mencapai ini?

martineau
sumber
4
Jika Anda bertanya tentang Pythonicity, maka saran standar tidak digunakan staticmethodsama sekali. Mereka biasanya lebih berguna sebagai fungsi tingkat modul, dalam hal ini masalah Anda bukan masalah. classmethod, di sisi lain ...
Benjamin Hodgson
1
@poorsod: Ya, saya tahu alternatif itu. Namun dalam kode aktual di mana saya mengalami masalah ini, menjadikan fungsi metode statis daripada meletakkannya di tingkat modul lebih masuk akal daripada dalam contoh sederhana yang digunakan dalam pertanyaan saya.
martineau

Jawaban:

180

staticmethodobjek tampaknya memiliki __func__atribut yang menyimpan fungsi mentah asli (masuk akal bahwa mereka harus). Jadi ini akan berhasil:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

Sebagai tambahan, meskipun saya curiga bahwa objek metode statis memiliki semacam atribut yang menyimpan fungsi asli, saya tidak tahu secara spesifik. Dalam semangat mengajar seseorang untuk memancing daripada memberi mereka ikan, inilah yang saya lakukan untuk menyelidiki dan mencari tahu (C&P dari sesi Python saya):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

Jenis penggalian yang serupa dalam sesi interaktif ( dirsangat membantu) seringkali dapat memecahkan pertanyaan semacam ini dengan sangat cepat.

Ben
sumber
Pembaruan yang bagus ... Saya baru saja menanyakan bagaimana Anda mengetahui hal ini karena saya tidak melihatnya di dokumentasi - yang membuat saya sedikit gugup menggunakannya karena mungkin itu merupakan "detail implementasi".
martineau
Setelah membaca lebih lanjut saya melihat bahwa __func__itu hanya nama lain untuk im_funcdan ditambahkan ke Py 2.6 untuk kompatibilitas ke depan Python 3.
martineau
2
Begitu ya, jadi secara teknis tidak terdokumentasi dalam konteks ini.
martineau
1
@AkshayHazari __func__Atribut metode statis memberi Anda referensi ke fungsi aslinya, persis seperti jika Anda belum pernah menggunakan staticmethoddekorator. Jadi, jika fungsi Anda memerlukan argumen, Anda harus meneruskannya saat memanggil __func__. Pesan kesalahan Anda terdengar seperti Anda belum memberikan argumen apa pun. Jika stat_funcdalam contoh tulisan ini mengambil dua argumen, Anda akan menggunakan_ANS = stat_func.__func__(arg1, arg2)
Ben
1
@AkshayHazari Saya tidak yakin saya mengerti. Mereka bisa menjadi variabel, mereka hanya harus menjadi variabel dalam lingkup: baik didefinisikan sebelumnya dalam lingkup yang sama di mana kelas didefinisikan (sering global), atau didefinisikan sebelumnya dalam lingkup kelas ( stat_funcitu sendiri adalah variabel seperti itu). Apakah maksud Anda Anda tidak dapat menggunakan atribut instance dari kelas yang Anda tetapkan? Itu benar, tetapi tidak mengejutkan; kita tidak memiliki turunan kelas, karena kita masih mendefinisikan kelas! Tetapi bagaimanapun juga, saya hanya memaksudkan mereka sebagai orang yang dapat mempertahankan argumen apa pun yang ingin Anda sampaikan; Anda bisa menggunakan literal di sana.
Ben
24

Ini adalah cara yang saya sukai:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

Saya lebih suka solusi ini Klass.stat_func, karena prinsip KERING . Mengingatkan saya pada alasan mengapa ada yang barusuper() di Python 3 :)

Tapi saya setuju dengan yang lain, biasanya pilihan terbaik adalah mendefinisikan fungsi level modul.

Misalnya dengan @staticmethodfungsi, rekursi mungkin tidak terlihat sangat bagus (Anda harus melanggar prinsip KERING dengan memanggil ke Klass.stat_funcdalam Klass.stat_func). Itu karena Anda tidak memiliki referensi ke selfmetode statis di dalam. Dengan fungsi level modul, semuanya akan terlihat OK.

Jan Vorcak
sumber
Sementara saya setuju bahwa menggunakan self.__class__.stat_func()metode biasa memiliki kelebihan (KERING dan semua itu) daripada menggunakan Klass.stat_func(), itu tidak benar-benar topik pertanyaan saya - sebenarnya saya menghindari menggunakan yang pertama karena tidak mengaburkan masalah ketidakkonsistenan.
martineau
1
Ini sebenarnya bukan masalah KERING per se. self.__class__lebih baik karena jika subclass menimpa stat_func, maka Subclass.methodakan memanggil subclass stat_func. Tapi jujur ​​saja, dalam situasi itu jauh lebih baik menggunakan metode nyata daripada statis.
penanggung jawab
@asmeurer: Saya tidak bisa menggunakan metode nyata karena belum ada instance dari kelas yang dibuat - mereka tidak bisa karena kelas itu sendiri bahkan belum sepenuhnya didefinisikan.
martineau
Saya ingin cara untuk memanggil metode statis untuk kelas saya (yang diwarisi; jadi orang tua sebenarnya memiliki fungsi) dan ini tampaknya yang terbaik sejauh ini (dan masih akan bekerja jika saya menimpanya nanti).
whitey04
11

Bagaimana dengan menyuntikkan atribut kelas setelah definisi kelas?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value
Pedro Romano
sumber
1
Ini mirip dengan upaya pertama saya untuk bekerja di sekitar, tetapi saya lebih suka sesuatu yang ada di dalam kelas ... sebagian karena atribut yang terlibat memiliki garis bawah utama dan bersifat pribadi.
martineau
11

Ini karena metode static menjadi deskriptor dan membutuhkan atribut level kelas untuk menjalankan protokol deskriptor dan mendapatkan callable sejati.

Dari kode sumber:

Ia dapat dipanggil baik di kelas (mis C.f()) atau pada instance (mis C().f()); instance diabaikan kecuali untuk kelasnya.

Tetapi tidak langsung dari dalam kelas saat sedang didefinisikan.

Tetapi seperti disebutkan oleh seorang komentator, ini sebenarnya bukan desain "Pythonic". Cukup gunakan fungsi level modul saja.

Keith
sumber
Seperti yang saya katakan dalam pertanyaan saya, saya mengerti mengapa kode asli tidak berfungsi. Bisakah Anda menjelaskan (atau memberikan tautan ke sesuatu yang bermanfaat) mengapa itu dianggap tidakPythonic?
martineau
Metode statis membutuhkan objek kelas itu sendiri untuk bekerja dengan benar. Tetapi jika Anda menyebutnya dari dalam kelas di tingkat kelas, kelas tersebut tidak sepenuhnya didefinisikan pada saat itu. Jadi Anda belum bisa merujuknya. Tidak apa-apa memanggil metode statis dari luar kelas, setelah didefinisikan. Tapi " Pythonic " sebenarnya tidak didefinisikan dengan baik, seperti estetika. Saya dapat memberi tahu Anda kesan pertama saya tentang kode itu tidak menguntungkan.
Keith
Kesan versi yang mana (atau keduanya)? Dan mengapa, khususnya?
martineau
Saya hanya bisa menebak apa tujuan akhir Anda, tetapi bagi saya tampaknya Anda menginginkan atribut tingkat kelas yang dinamis. Ini terlihat seperti pekerjaan untuk metaclasses. Tetapi sekali lagi mungkin ada cara yang lebih sederhana, atau cara lain untuk melihat desain yang menghilangkan dan menyederhanakan tanpa mengorbankan fungsi.
Keith
3
Tidak, apa yang ingin saya lakukan sebenarnya cukup sederhana - yaitu memfaktorkan beberapa kode umum dan menggunakannya kembali, baik untuk membuat atribut kelas privat pada waktu pembuatan kelas, dan kemudian dalam satu atau beberapa metode kelas. Kode umum ini tidak digunakan di luar kelas, jadi saya tentu ingin menjadi bagian dari itu. Menggunakan metaclass (atau dekorator kelas) akan berhasil, tetapi itu sepertinya memerlukan banyak usaha keras untuk sesuatu yang seharusnya mudah dilakukan, IMHO.
martineau
8

Bagaimana dengan solusi ini? Itu tidak bergantung pada pengetahuan @staticmethodimplementasi dekorator. Kelas dalam StaticMethod berperan sebagai wadah fungsi inisialisasi statis.

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret
Schatten
sumber
2
+1 untuk kreativitas, tetapi saya tidak lagi khawatir tentang penggunaan __func__karena sekarang telah didokumentasikan secara resmi (lihat bagian Perubahan Bahasa Lain dari Apa yang Baru di Python 2.7 dan rujukannya ke Masalah 5982 ). Solusi Anda bahkan lebih portabel, karena mungkin juga akan bekerja dalam versi Python sebelum ke 2.6 (ketika __func__pertama kali diperkenalkan sebagai sinonim dari im_func).
martineau
Ini adalah satu-satunya solusi yang berfungsi dengan Python 2.6.
benselme
@ Benselme: Saya tidak dapat memverifikasi klaim Anda karena saya tidak menginstal Python 2.6, tetapi satu-satunya keraguan yang serius ...
martineau