Atribut fungsi Python - menggunakan dan menyalahgunakan [ditutup]

195

Tidak banyak yang mengetahui fitur ini, tetapi fungsi (dan metode) Python dapat memiliki atribut . Melihat:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

Apa kegunaan dan penyalahgunaan yang mungkin dari fitur ini di Python? Satu kegunaan yang baik yang saya ketahui adalah penggunaan dokumen oleh PLY untuk mengaitkan aturan sintaksis dengan suatu metode. Tetapi bagaimana dengan atribut khusus? Apakah ada alasan bagus untuk menggunakannya?

Eli Bendersky
sumber
3
Lihatlah PEP 232 .
user140352
2
Apakah ini sangat mengejutkan? Secara umum, objek Python mendukung atribut ad-hoc. Tentu saja, beberapa tidak, terutama yang memiliki tipe builtin. Bagi saya, mereka yang tidak mendukung ini tampaknya merupakan pengecualian, bukan aturan.
allyourcode
3
Satu Aplikasi di Django: Kustomisasi daftar perubahan admin
Grijesh Chauhan
2
@GrijeshChauhan Saya datang ke pertanyaan ini setelah melihat dokumen ini!
Alexander Suraphel
5
Sayang sekali ini ditutup, saya ingin menambahkan bahwa Anda dapat melampirkan pengecualian kustom yang fungsi mungkin naikkan, untuk memberikan akses mudah ketika menangkapnya dalam kode panggilan. Saya akan memberikan contoh ilustratif, tetapi sebaiknya dilakukan dalam jawaban.
Will Hardy

Jawaban:

153

Saya biasanya menggunakan atribut fungsi sebagai penyimpanan untuk anotasi. Misalkan saya ingin menulis, dengan gaya C # (menunjukkan bahwa metode tertentu harus menjadi bagian dari antarmuka layanan web)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

maka saya bisa mendefinisikan

def webmethod(func):
    func.is_webmethod = True
    return func

Kemudian, ketika panggilan layanan web tiba, saya mencari metode, memeriksa apakah fungsi yang mendasari memiliki atribut is_webmethod (nilai sebenarnya tidak relevan), dan menolak layanan jika metode tersebut tidak ada atau tidak dimaksudkan untuk dipanggil melalui web.

Martin v. Löwis
sumber
2
Apakah Anda pikir ada sisi buruknya? mis. Bagaimana jika dua perpustakaan mencoba menulis atribut ad-hoc yang sama?
kode Anda
18
Saya sedang berpikir untuk melakukan ini. Lalu aku berhenti sendiri. "Apakah ini ide yang buruk?" Aku bertanya-tanya. Kemudian, saya berjalan ke SO. Setelah beberapa kikuk sekitar, saya menemukan pertanyaan / jawaban ini. Masih tidak yakin apakah ini ide yang bagus.
allyourcode
7
Ini jelas merupakan penggunaan atribut fungsi yang paling sah dari semua jawaban (per November 2012). Sebagian besar (jika tidak semua) jawaban lain menggunakan atribut fungsi sebagai pengganti variabel global; namun, mereka TIDAK menyingkirkan kondisi global, yang justru merupakan masalah dengan variabel global. Ini berbeda, karena begitu nilainya ditetapkan, tidak berubah; itu konstan. Konsekuensi yang bagus dari hal ini adalah Anda tidak mengalami masalah sinkronisasi, yang melekat pada variabel global. Ya, Anda dapat memberikan sinkronisasi sendiri, tetapi itulah intinya: itu tidak aman secara otomatis.
allyourcode
Memang, saya katakan, selama atribut tidak mengubah perilaku fungsi yang dimaksud, itu bagus. Bandingkan dengan.__doc__
Dima Tisnek
Pendekatan ini juga dapat digunakan untuk melampirkan deskripsi keluaran ke fungsi yang didekorasi, yang tidak ada dalam python 2. *.
Juh_
125

Saya telah menggunakannya sebagai variabel statis untuk suatu fungsi. Misalnya, diberi kode C berikut:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

Saya dapat mengimplementasikan fungsi serupa di Python:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

Ini pasti akan jatuh ke ujung "penyalahgunaan" spektrum.

mipadi
sumber
2
Menarik. Apakah ada cara lain untuk mengimplementasikan variabel statis di python?
Eli Bendersky
4
-1, ini akan diimplementasikan dengan generator di python.
124
Itu alasan yang cukup buruk untuk menurunkan jawaban ini, yang menunjukkan analogi antara C dan Python, tidak menganjurkan cara terbaik untuk menulis fungsi khusus ini.
Robert Rossney
3
@ RobertTossossney Tapi jika generator adalah cara untuk pergi, maka ini adalah penggunaan atribut fungsi yang buruk. Jika demikian, maka ini merupakan penyalahgunaan. Tidak yakin apakah akan memperbaiki pelanggaran, karena pertanyaannya juga meminta mereka: P
allyourcode
1
Jelas tampak seperti penyalahgunaan, per PEP 232, terima kasih @ user140352, dan komentar hop dan jawaban SO ini
hobs
52

Anda dapat melakukan objek dengan cara JavaScript ... Tidak masuk akal tetapi berfungsi;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo
defnull
sumber
36
+1 Contoh yang bagus dari penyalahgunaan fitur ini, yang merupakan salah satu hal yang ditanyakan.
Michael Dunn
1
Bagaimana ini berbeda dari jawaban mipadi? Tampaknya menjadi hal yang sama, kecuali alih-alih int, nilai atribut adalah fungsi.
allyourcode
apakah def test()benar-benar diperlukan?
Keerthana Prabhakaran
15

Saya menggunakannya dengan hemat, tetapi mereka bisa sangat nyaman:

def log(msg):
   log.logfile.write(msg)

Sekarang saya dapat menggunakan logseluruh modul saya, dan mengarahkan ulang output hanya dengan mengatur log.logfile. Ada banyak dan banyak cara lain untuk mencapai itu, tetapi yang ini ringan dan sederhana. Dan walaupun baunya lucu ketika pertama kali melakukannya, saya mulai percaya bahwa baunya lebih enak daripada memiliki logfilevariabel global .

Robert Rossney
sumber
7
kembali bau: Ini tidak menghilangkan logfile global sekalipun. Itu hanya menggeliatnya di global lain, fungsi log.
allyourcode
2
@allyourcode: Tetapi dapat membantu menghindari bentrokan nama jika Anda harus memiliki banyak file log global untuk berbagai fungsi dalam modul yang sama.
firegurafiku
11

Atribut fungsi dapat digunakan untuk menulis penutupan ringan yang membungkus kode dan data terkait:

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1,00934004784

2.00644397736

3.01593494415

Kevin Little
sumber
3
Mengapa fungsi push ketika kita memiliki kelas berguna? Dan jangan lupa kelas bisa meniru fungsi.
muhuk
1
juga time.sleep(1)lebih baik daripadaos.system('sleep 1')
Boris Gorelik
3
@ bbg Benar, meskipun contoh ini bukan tentang tidur.
allyourcode
Ini jelas merupakan penyalahgunaan; penggunaan fungsi di sini benar-benar gratis. Muhuk memang benar: kelas adalah solusi yang lebih baik.
allyourcode
1
Saya juga akan bertanya, "Apa manfaatnya dari kelas ini?" untuk mengatasi kelemahan ini menjadi tidak jelas bagi banyak programmer Python.
cjs
6

Saya telah membuat dekorator pembantu ini untuk dengan mudah mengatur atribut fungsi:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

Use case adalah untuk membuat kumpulan pabrik dan menanyakan tipe data yang dapat mereka buat pada tingkat meta fungsi.
Misalnya (sangat bodoh):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None
DiogoNeves
sumber
Bagaimana dekorator membantu? Mengapa tidak mengatur factory1.datatype=listtepat di bawah deklarasi seperti dekorator gaya lama?
Ceasar Bautista
2
2 perbedaan utama: gaya, banyak atribut lebih mudah diatur. Anda pasti dapat menetapkan sebagai atribut tetapi mendapatkan verbose dengan beberapa atribut menurut saya dan Anda juga mendapatkan kesempatan untuk memperluas dekorator untuk diproses lebih lanjut (seperti memiliki default yang didefinisikan di satu tempat, bukan semua tempat yang menggunakan fungsi atau harus panggil fungsi tambahan setelah atribut ditetapkan). Ada cara lain untuk mencapai semua hasil ini, saya hanya menemukan ini lebih bersih, tetapi senang mengubah pikiran saya;)
DiogoNeves
Pembaruan cepat: dengan Python 3 Anda harus menggunakan items()sebagai ganti iteritems().
Scott Means
4

Terkadang saya menggunakan atribut fungsi untuk caching nilai yang sudah dihitung. Anda juga dapat memiliki dekorator generik yang menggeneralisasikan pendekatan ini. Waspadai masalah konkurensi dan efek samping dari fungsi-fungsi tersebut!


sumber
Aku suka ide ini! Trik yang lebih umum untuk caching nilai yang dihitung adalah menggunakan dict sebagai nilai default dari atribut yang tidak pernah diberikan oleh pemanggil - karena Python mengevaluasi bahwa hanya sekali ketika mendefinisikan fungsi, Anda dapat menyimpan data di sana dan menyimpannya sekitar. Meskipun menggunakan atribut fungsi mungkin tidak terlalu jelas, rasanya secara signifikan lebih sedikit retas bagi saya.
Soren Bjornstad
1

Saya selalu berasumsi bahwa satu-satunya alasan ini mungkin adalah karena itu ada tempat yang logis untuk meletakkan doc-string atau hal-hal semacam itu. Saya tahu jika saya menggunakannya untuk kode produksi apa pun itu akan membingungkan kebanyakan orang yang membacanya.

Dale Reidy
sumber
1
Saya setuju dengan poin utama Anda tentang kemungkinan besar ini membingungkan, tetapi ulangi dokumentasi: Ya, tetapi mengapa fungsi memiliki atribut AD-HOC? Mungkin ada satu set atribut tetap, satu untuk memegang dokumen.
allyourcode
@allyourcode Mempunyai case umum alih-alih case ad-hoc spesifik yang dirancang ke dalam bahasa membuat segalanya lebih mudah dan meningkatkan kompatibilitas dengan versi Python yang lebih lama. (Misalnya, kode yang menetapkan / memanipulasi dokumen masih akan bekerja dengan versi Python yang tidak melakukan dokumen, asalkan menangani kasus di mana atribut tidak ada.)
cjs