Apa persamaan Python dari variabel statis di dalam suatu fungsi?

631

Apa persamaan Python idiomatik dari kode C / C ++ ini?

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

khususnya, bagaimana seseorang mengimplementasikan anggota statis pada tingkat fungsi, yang bertentangan dengan tingkat kelas? Dan apakah menempatkan fungsi ke dalam kelas mengubah sesuatu?

andrewdotnich
sumber
22
Tidak ada kesetaraan yang saya takutkan. Bahkan jika Anda melakukan hack dekorator dengan atribut fungsi, Anda akan dapat mengakses variabel di luar, yang agak mengecewakan intinya, sayangnya. Selain itu, Anda harus membuat kode nama fungsi dalam harddisk, yang sangat mengganggu. Saya menyarankan untuk menggunakan variabel global kelas atau modul sebagai gantinya dengan _awalan konvensional .
lpapp
8
Untuk non-C-programmer, [ stackoverflow.com/questions/5033627/... variabel statis di dalam suatu fungsi hanya terlihat di dalam lingkup fungsi itu, tetapi masa pakainya adalah seluruh umur program, dan hanya diinisialisasi satu kali). Pada dasarnya, variabel konter atau penyimpanan persisten yang hidup di antara panggilan fungsi.
smci
2
@ lpapp: ada jenisnya, itu adalah anggota kelas . Anda benar bahwa kami tidak dapat mencegah kode lain melihatnya atau mengubahnya.
smci

Jawaban:

681

Agak terbalik, tetapi ini seharusnya berhasil:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

Jika Anda menginginkan kode inisialisasi penghitung di bagian atas dan bukan di bawah, Anda dapat membuat dekorator:

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

Kemudian gunakan kode seperti ini:

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

Sayangnya, Anda masih harus menggunakan foo.awalan.

(Kredit: @ony )

Claudiu
sumber
23
hanya ada satu instance dari foo - fungsi yang satu ini. semua doa mengakses variabel yang sama.
Claudiu
121
Maaf telah menggali ini, tapi saya lebih suka meletakkan if "counter" not in foo.__dict__: foo.counter = 0sebagai baris pertama foo(). Ini akan membantu menghindari kode di luar fungsi. Tidak yakin apakah ini mungkin kembali pada 2008. PS Menemukan jawaban ini saat mencari kemungkinan untuk membuat variabel fungsi statis, jadi utas ini masih "hidup" :)
binaryLV
8
@ binaryLV: Saya mungkin lebih suka itu daripada pendekatan pertama. Masalah dengan pendekatan pertama adalah itu tidak segera jelas foodan foo.counter = terkait erat. Namun, saya akhirnya lebih suka pendekatan dekorator, karena tidak ada cara dekorator tidak akan dipanggil dan secara semantik lebih jelas apa yang dilakukannya ( @static_var("counter", 0)lebih mudah & lebih masuk akal bagi mata saya daripada if "counter" not in foo.__dict__: foo.counter = 0, terutama karena pada yang terakhir Anda harus menggunakan nama fungsi (dua kali) yang mungkin berubah).
Claudiu
6
@ lpapp: Tergantung pada titik variabel statis. Saya selalu berpikir bahwa itu akan menjadi nilai yang sama di beberapa panggilan fungsi, yang memuaskan. Saya tidak pernah menganggapnya tentang persembunyian variabel, yang ini tidak, seperti yang Anda katakan.
Claudiu
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty
222

Anda dapat menambahkan atribut ke suatu fungsi, dan menggunakannya sebagai variabel statis.

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

Atau, jika Anda tidak ingin mengatur variabel di luar fungsi, Anda dapat menggunakan hasattr()untuk menghindari AttributeErrorpengecualian:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1

Bagaimanapun variabel statis agak jarang, dan Anda harus menemukan tempat yang lebih baik untuk variabel ini, kemungkinan besar di dalam kelas.

vincent
sumber
6
Mengapa tidak mencoba bukan pernyataan if?
ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1harus melakukan hal yang sama, menggunakan pengecualian.
sleblanc
Pengecualian harus digunakan untuk situasi Luar Biasa, yaitu yang diharapkan oleh programmer tidak akan terjadi, seperti file input yang berhasil dibuka tiba-tiba tidak tersedia. Ini adalah situasi yang diharapkan, pernyataan if lebih masuk akal.
Hack Saw
11
@ Hack_Saw: Ya, ini Pythonic (lebih baik minta maaf daripada izin). Ini sebenarnya direkomendasikan dalam teknik optimasi Python karena menghemat biaya if (walaupun saya tidak merekomendasikan optimasi prematur). Aturan Anda tentang kasus luar biasa: 1. Kegagalan ADALAH kasus luar biasa di sini, dalam arti tertentu. Itu hanya terjadi sekali. 2. Saya pikir aturannya adalah tentang menggunakan pengecualian (yaitu menaikkan). Ini menangkap pengecualian untuk sesuatu yang Anda harapkan berfungsi tetapi memiliki rencana cadangan untuk, yang merupakan hal umum di sebagian besar bahasa.
leewz
@leewangzhong: Apakah melampirkan blok yang tidak menimbulkan pengecualian dalam trymenambah biaya? Hanya penasaran.
trss
202

Seseorang juga dapat mempertimbangkan:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

Pemikiran:

  • banyak pythonic ("minta maaf bukan izin")
  • gunakan pengecualian (hanya dibuang satu kali) alih-alih ifcabang (pikirkan pengecualian StopIteration )
ravwojdyla
sumber
11
Saya belum pernah melakukan Python lama, tapi ini memuaskan salah satu rumah petak tersirat dari bahasa: jika itu tidak (cukup) mudah, Anda salah melakukannya .
ZX9
Tidak segera bekerja dengan metode kelas, "self.foo.counter = 1" memunculkan AttributeError lagi.
villasv
16
Ini adalah solusi yang benar dan itu harus menjadi jawaban yang diterima karena kode inisialisasi akan dijalankan ketika fungsi dipanggil dan bukan ketika modul dieksekusi atau ketika sesuatu dari itu diimpor, yang merupakan kasus jika Anda menggunakan pendekatan dekorator dari jawaban yang saat ini diterima. Lihat pelaksanaan fungsi dekorator Python . Jika Anda memiliki modul perpustakaan besar maka setiap dekorator akan dijalankan, termasuk fungsi yang tidak Anda impor.
Nils Lindemann
3
Pendekatan yang lebih sederhana: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@MANU Menggunakan hasattr()untuk ini tidak sederhana dan juga kurang efisien.
moooeeeep
48

Jawaban lain telah menunjukkan cara Anda melakukan ini. Inilah cara yang seharusnya tidak Anda lakukan:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

Nilai default diinisialisasi hanya ketika fungsi pertama kali dievaluasi, tidak setiap kali dieksekusi, sehingga Anda dapat menggunakan daftar atau objek yang dapat diubah lainnya untuk menyimpan nilai statis.

Jeremy Banks
sumber
Saya mencoba itu, tetapi untuk beberapa alasan, parameter fungsi menginisialisasi sendiri ke 140, bukan 0. Mengapa ini terjadi?
andrewdotnich
1
@ bouvard Untuk fungsi rekursif yang memerlukan variabel statis, ini adalah satu-satunya yang benar-benar terbaca dengan baik.
lifebalance
1
Saya mencoba beberapa pendekatan dan saya berharap yang satu ini diterima sebagai pythonic. Dengan beberapa nama yang bermakna seperti def foo(arg1, arg2, _localstorage=DataClass(counter=0))saya menemukannya dapat dibaca dengan baik. Poin bagus lainnya adalah penggantian nama fungsi mudah.
VPfB
2
Mengapa Anda mengatakan Anda tidak harus melakukannya dengan cara itu? Terlihat sangat masuk akal bagi saya!
Konstantin
1
@VPfB: Untuk penyimpanan umum, Anda bisa menggunakannya types.SimpleNamespace, membuatnya def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):tanpa perlu mendefinisikan kelas khusus.
ShadowRanger
43

Banyak orang telah menyarankan pengujian 'hasattr', tetapi ada jawaban yang lebih sederhana:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

Tidak mencoba / kecuali, tanpa pengujian hasattr, cukup getattr dengan default.

Jonathan
sumber
2
perhatikan parm ketiga getattr ketika Anda meletakkan func di sana misalnya: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) +1 ketika Anda menelepon Func, foo akan selalu dipanggil!
Kode untuk
1
Hanya panggilan ke getattr setiap kali fungsi dipanggil. Tidak masalah jika kinerja bukan masalah, jika dicoba / kecuali akan menang.
Mark Lawrence
2
@ MarkLawrence: Sebenarnya, setidaknya pada instalasi Windows x64 3.8.0 saya, perbedaan kinerja antara jawaban ini dan pendekatan setara try/ exceptberbasis ravwojdyla cukup berarti. ipython %%timeitMicrobenchmark sederhana memberi biaya try/ exceptpada 255 ns per panggilan, vs 263 ns untuk getattrsolusi berbasis. Ya, try/ exceptlebih cepat, tapi itu tidak sepenuhnya "menang telak"; ini adalah mikro-optimasi kecil. Tulis kode apa pun yang tampak lebih jelas, jangan khawatir tentang perbedaan kinerja sepele seperti ini.
ShadowRanger
@ShadowRanger, terima kasih telah membandingkannya. Saya telah bertanya-tanya tentang pernyataan MarkLawrence selama 2 tahun, dan saya sangat senang Anda melakukan penelitian. Saya pasti setuju dengan kalimat terakhir Anda - "tulis kode apa pun yang tampak lebih jelas" - itulah sebabnya saya menulis jawaban ini.
Jonathan
28

Ini adalah versi yang dienkapsulasi penuh yang tidak memerlukan panggilan inisialisasi eksternal:

def fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)

Dalam Python, fungsi adalah objek dan kita bisa menambahkan, atau tambalan monyet, variabel anggota melalui atribut khusus __dict__. Built-in vars()mengembalikan atribut khusus __dict__.

EDIT: Catatan, tidak seperti try:except AttributeErrorjawaban alternatif , dengan pendekatan ini variabel akan selalu siap untuk logika kode setelah inisialisasi. Saya pikir try:except AttributeErroralternatif untuk yang berikut ini akan kurang KERING dan / atau memiliki aliran yang canggung:

def Fibonacci(n):
   if n<2: return n
   Fibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cache
   return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2)) # lookup result in cache, if not available then calculate and store it

EDIT2: Saya hanya merekomendasikan pendekatan di atas ketika fungsi akan dipanggil dari beberapa lokasi. Jika alih-alih fungsi hanya dipanggil di satu tempat, lebih baik menggunakan nonlocal:

def TheOnlyPlaceStaticFunctionIsCalled():
    memo={}
    def Fibonacci(n):
       nonlocal memo  # required in Python3. Python2 can see memo
       if n<2: return n
       return memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
    ...
    print (Fibonacci(200))
    ...
Riaz Rizvi
sumber
2
satu-satunya masalah dengan ini adalah bahwa itu benar-benar tidak rapi sama sekali, dan setiap kali Anda ingin menggunakan pola ini Anda harus memotong & menempelkan kode ... maka saya menggunakan dekorator
Claudiu
2
mungkin harus menggunakan sesuatu sepertitry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith
2
Silakan gunakan X not in Ydaripada not X in Y(atau menyarankan menggunakan jika Anda hanya menggunakannya demi perbandingan yang lebih mirip antara itu dan hasattr)
Nick T
bagaimana dengan ini: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
itu tidak ideal karena klausa if menambahkan sarang yang tidak perlu, dalam situasi ini saya lebih suka setdefault
Riaz Rizvi
27

Python tidak memiliki variabel statis tetapi Anda bisa memalsukannya dengan mendefinisikan objek kelas yang dapat dipanggil dan kemudian menggunakannya sebagai fungsi. Lihat juga jawaban ini .

class Foo(object):
  # Class variable, shared by all instances of this class
  counter = 0

  def __call__(self):
    Foo.counter += 1
    print Foo.counter

# Create an object instance of class "Foo," called "foo"
foo = Foo()

# Make calls to the "__call__" method, via the object's name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3

Catatan yang __call__membuat instance kelas (objek) dapat dipanggil dengan namanya sendiri. Itu sebabnya memanggil di foo()atas memanggil metode kelas __call__. Dari dokumentasi :

Contoh kelas sewenang-wenang dapat dibuat dapat dipanggil dengan mendefinisikan __call__()metode di kelas mereka.

daniels
sumber
15
Fungsi sudah objek jadi ini hanya menambah lapisan yang tidak perlu.
DasIch
Lihat jawaban SO ini untuk pendapat yang panjang bahwa ini sebenarnya ide yang bagus. stackoverflow.com/questions/460586 . Saya setuju bahwa menjadikan kelas seperti itu sebagai singleton, mungkin seperti ini stackoverflow.com/questions/6760685 , juga merupakan ide yang bagus. Saya tidak tahu apa yang berarti @ S.Lott dengan "... pindahkan counter ke definisi kelas ..." karena sepertinya sudah dalam posisi variabel-kelas bagi saya.
Reb.Cabin
1
Berdasarkan penelitian saya, teknik kelas ini tampaknya menjadi yang paling "Pythonic" dari pendekatan yang disajikan pada halaman ini, dan menggunakan tipuan paling sedikit. Karena itu saya berencana untuk mengadopsinya sebagai pengganti untuk variabel C-static-like dalam fungsi, sebagai pengembang Python baru sendiri.
Gabriel Staples
1
Apa yang terjadi jika saya ingin foo1 = Foo () dan foo2 = Foo ()?
Mark Lawrence
@ MarkLawrence Kemudian Anda memiliki dua contoh berbeda dari kelas yang dapat dipanggil, masing-masing dengan counter mereka sendiri. Yang persis seperti apa yang Anda harapkan jika Anda tidak menggunakan instance fooyang disediakan sebagai singleton.
Aaron McMillin
14

Gunakan fungsi generator untuk menghasilkan iterator.

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

Kemudian gunakan seperti

foo = foo_gen().next
for i in range(0,10):
    print foo()

Jika Anda ingin batas atas:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

Jika iterator berakhir (seperti contoh di atas), Anda juga dapat mengulanginya secara langsung, seperti

for i in foo_gen(20):
    print i

Tentu saja, dalam kasus-kasus sederhana ini lebih baik menggunakan xrange :)

Berikut adalah dokumentasi pada pernyataan hasil .

Agak
sumber
11

Solusi lain melampirkan atribut penghitung ke fungsi, biasanya dengan logika berbelit-belit untuk menangani inisialisasi. Ini tidak pantas untuk kode baru.

Dalam Python 3, cara yang benar adalah dengan menggunakan nonlocalpernyataan:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

Lihat PEP 3104 untuk spesifikasi nonlocalpernyataan tersebut.

Jika penghitung dimaksudkan untuk pribadi ke modul, itu harus dinamai _countersebaliknya.

crickrick
sumber
Bahkan sebelum Python 3, Anda selalu bisa melakukan ini dengan global counterpernyataan alih-alih nonlocal counter( nonlocalhanya memungkinkan Anda menulis ke status penutupan dalam fungsi bersarang). Alasan orang melampirkan atribut ke fungsi adalah untuk menghindari mencemari namespace global untuk negara yang khusus untuk fungsi, sehingga Anda tidak perlu melakukan hal-hal yang bahkan peretasan ketika dua fungsi perlu independen counter. Solusi ini tidak berskala; atribut pada fungsi lakukan. Jawaban kdb adalah bagaimana nonlocalbisa membantu, tetapi menambah kompleksitas.
ShadowRanger
Eh, saya pikir kompleksitas fungsi pabrik atau penghias terlalu banyak kecuali jika Anda sering melakukan ini, dan dalam hal ini desainnya sudah agak bau. Untuk sekali saja, tambahkan saja penghitung nonlokal dan selesaikan. Saya telah menambahkan sedikit jawaban tentang konvensi penamaan. Juga, alasan saya merekomendasikan nonlocallebih globaladalah persis seperti yang Anda tunjukkan - itu bekerja dalam keadaan yang lebih ketat.
cbarrick
8

Menggunakan atribut fungsi sebagai variabel statis memiliki beberapa kelemahan potensial:

  • Setiap kali Anda ingin mengakses variabel, Anda harus menuliskan nama lengkap fungsi tersebut.
  • Kode luar dapat mengakses variabel dengan mudah dan mengacaukan nilainya.

Python idiomatik untuk masalah kedua mungkin akan memberi nama variabel dengan garis bawah utama untuk memberi sinyal bahwa itu tidak dimaksudkan untuk diakses, sambil tetap diakses setelah fakta.

Alternatif akan menjadi pola menggunakan penutupan leksikal, yang didukung dengan nonlocalkata kunci di python 3.

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

Sayangnya saya tidak tahu cara untuk merangkum solusi ini menjadi dekorator.

kdb
sumber
7
def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

Sama seperti kode vincent di atas, ini akan digunakan sebagai dekorator fungsi dan variabel statis harus diakses dengan nama fungsi sebagai awalan. Keuntungan dari kode ini (walaupun harus diakui siapa pun mungkin cukup pintar untuk mengetahuinya) adalah bahwa Anda dapat memiliki beberapa variabel statis dan menginisialisasinya dengan cara yang lebih konvensional.

Giorgian Borca-Tasciuc
sumber
7

Sedikit lebih mudah dibaca, tetapi lebih verbose (Zen dari Python: eksplisit lebih baik daripada implisit):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

Lihat di sini untuk penjelasan tentang cara kerjanya.

warvariuc
sumber
dapatkah Anda menjelaskan mengapa kode ini berfungsi? Yang kedua foo()harus menginisialisasi ulang kamus ke nilai yang ditentukan dalam definisi fungsi (jadi dengan tombol penghitung memiliki nilai 0). Kenapa tidak?
raffaem
3
@raffamaiden: Argumen default dievaluasi hanya sekali ketika fungsi didefinisikan dan tidak setiap kali fungsi dipanggil.
Daniel K.
6
_counter = 0
def foo ():
   global _counter
   _ penghitung + = 1
   cetak 'penghitung adalah', _counter

Python biasanya menggunakan garis bawah untuk menunjukkan variabel pribadi. Satu-satunya alasan di C untuk mendeklarasikan variabel statis di dalam fungsi adalah untuk menyembunyikannya di luar fungsi, yang sebenarnya bukan Python idiomatik.

Dave
sumber
4

Setelah mencoba beberapa pendekatan, saya akhirnya menggunakan versi jawaban @ warvariuc yang lebih baik:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
VPfB
sumber
3

Cara idiomatis adalah menggunakan kelas , yang dapat memiliki atribut. Jika Anda perlu contoh untuk tidak terpisah, gunakan singleton.

Ada beberapa cara Anda bisa memalsukan atau mengubah variabel "statis" menjadi Python (yang tidak disebutkan sejauh ini adalah memiliki argumen default yang bisa berubah-ubah), tetapi ini bukan cara Pythonic, idiomatis untuk melakukannya. Cukup gunakan kelas.

Atau mungkin generator, jika pola penggunaan Anda cocok.

Teddy
sumber
Untuk fungsi rekursif yang berdiri sendiri, defaultargumennya adalah yang paling elegan.
lifebalance
3

Diminta oleh pertanyaan ini , bolehkah saya menyajikan alternatif lain yang mungkin sedikit lebih baik untuk digunakan dan akan terlihat sama untuk kedua metode dan fungsi:

@static_var2('seed',0)
def funccounter(statics, add=1):
    statics.seed += add
    return statics.seed

print funccounter()       #1
print funccounter(add=2)  #3
print funccounter()       #4

class ACircle(object):
    @static_var2('seed',0)
    def counter(statics, self, add=1):
        statics.seed += add
        return statics.seed

c = ACircle()
print c.counter()      #1
print c.counter(add=2) #3
print c.counter()      #4
d = ACircle()
print d.counter()      #5
print d.counter(add=2) #7
print d.counter()      #8    

Jika Anda suka penggunaannya, inilah implementasinya:

class StaticMan(object):
    def __init__(self):
        self.__dict__['_d'] = {}

    def __getattr__(self, name):
        return self.__dict__['_d'][name]
    def __getitem__(self, name):
        return self.__dict__['_d'][name]
    def __setattr__(self, name, val):
        self.__dict__['_d'][name] = val
    def __setitem__(self, name, val):
        self.__dict__['_d'][name] = val

def static_var2(name, val):
    def decorator(original):
        if not hasattr(original, ':staticman'):    
            def wrapped(*args, **kwargs):
                return original(getattr(wrapped, ':staticman'), *args, **kwargs)
            setattr(wrapped, ':staticman', StaticMan())
            f = wrapped
        else:
            f = original #already wrapped

        getattr(f, ':staticman')[name] = val
        return f
    return decorator
Claudiu
sumber
3

Sentuhan lain (tidak disarankan!) Pada objek yang dapat dipanggil seperti https://stackoverflow.com/a/279598/916373 , jika Anda tidak keberatan menggunakan tanda tangan panggilan yang funky, adalah melakukan

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2
kalah
sumber
3

Alih-alih membuat fungsi yang memiliki variabel lokal statis, Anda selalu dapat membuat apa yang disebut "objek fungsi" dan memberinya variabel anggota standar (non-statis).

Karena Anda memberi contoh C ++ yang ditulis, saya pertama-tama akan menjelaskan apa "objek fungsi" di C ++. "Objek fungsi" adalah kelas apa saja dengan kelebihan beban operator(). Contoh kelas akan berperilaku seperti fungsi. Sebagai contoh, Anda dapat menulis int x = square(5);meskipun squareobjek (dengan kelebihan beban operator()) dan secara teknis bukan "fungsi." Anda bisa memberikan objek-fungsi salah satu fitur yang bisa Anda berikan pada objek kelas.

# C++ function object
class Foo_class {
    private:
        int counter;     
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d\n", counter);
        }     
   };
   Foo_class foo;

Dalam Python, kita juga bisa membebani operator()kecuali bahwa metode ini dinamai __call__:

Berikut adalah definisi kelas:

class Foo_class:
    def __init__(self): # __init__ is similair to a C++ class constructor
        self.counter = 0
        # self.counter is like a static member
        # variable of a function named "foo"
    def __call__(self): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
foo = Foo_class() # call the constructor

Berikut adalah contoh kelas yang digunakan:

from foo import foo

for i in range(0, 5):
    foo() # function call

Output yang dicetak ke konsol adalah:

counter is 1
counter is 2
counter is 3
counter is 4
counter is 5

Jika Anda ingin fungsi Anda mengambil argumen input, Anda dapat menambahkannya __call__juga:

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -

class Foo_class:
    def __init__(self):
        self.counter = 0
    def __call__(self, x, y, z): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
        print("x, y, z, are %d, %d, %d" % (x, y, z));
foo = Foo_class() # call the constructor

# FILE: main.py - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

from foo import foo

for i in range(0, 5):
    foo(7, 8, 9) # function call

# Console Output - - - - - - - - - - - - - - - - - - - - - - - - - - 

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9
IdleCustard
sumber
3

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count
Feca
sumber
3

Deklarasi global menyediakan fungsionalitas ini. Pada contoh di bawah ini (python 3.5 atau lebih besar untuk menggunakan "f"), variabel counter didefinisikan di luar fungsi. Mendefinisikannya sebagai global dalam fungsi menandakan bahwa versi "global" di luar fungsi harus tersedia untuk fungsi tersebut. Jadi setiap kali fungsi berjalan, ia memodifikasi nilai di luar fungsi, melestarikannya di luar fungsi.

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"
Richard Merren
sumber
Ini bekerja dengan cara yang sama jika digunakan dengan benar. Perbedaannya dengan kode-c adalah bahwa dalam contoh OP, variabel counter hanya bisa disentuh oleh fungsi. Variabel global dalam python dapat digunakan atau diubah di mana saja dalam skrip
MortenSickel
2

Variabel statis di dalam metode Python

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3
wannik
sumber
1

Saya pribadi lebih suka yang berikut ini daripada dekorator. Untuk masing-masing.

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)
dihabiskan sampai
sumber
3
Tolong jangan tersinggung, tetapi solusi ini sedikit mengingatkan saya pada "gaya perusahaan besar" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Ya, menggunakan non-portable (manipulasi stack secara umum adalah detail implementasi CPython, bukan sesuatu yang dapat Anda andalkan di PyPy, Jython, IronPython, what-have-you), manipulasi stack rapuh, dengan setengah lusin panggilan fungsi pada setiap penggunaan adalah cara yang lebih baik dari yang sederhana dekorator ... </ s>
ShadowRanger
1

Jawaban ini dibangun berdasarkan jawaban @claudiu.

Saya menemukan bahwa kode saya menjadi kurang jelas ketika saya harus selalu menambahkan nama fungsi, setiap kali saya ingin mengakses variabel statis.

Yaitu, dalam kode fungsi saya, saya lebih suka menulis:

print(statics.foo)

dari pada

print(my_function_name.foo)

Jadi, solusi saya adalah:

  1. tambahkan staticsatribut ke fungsi
  2. dalam lingkup fungsi, tambahkan variabel lokal staticssebagai alias kemy_function.statics
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

Ucapan

Metode saya menggunakan kelas bernama Bunch, yang merupakan kamus yang mendukung akses gaya-atribut, ala JavaScript (lihat artikel asli tentang itu, sekitar 2000)

Ini dapat diinstal melalui pip install bunch

Dapat juga ditulis tangan seperti ini:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Pascal T.
sumber
Catatan: types.SimpleNamespace(tersedia sejak 3.3) mendukung perilaku ini di luar kotak (dan diimplementasikan dalam C pada CPython, jadi ini secepat mungkin).
ShadowRanger
0

Membangun jawaban Daniel (tambahan):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

Alasan mengapa saya ingin menambahkan bagian ini adalah, variabel statis digunakan tidak hanya untuk menambah nilai, tetapi juga memeriksa apakah var statis sama dengan nilai tertentu, sebagai contoh kehidupan nyata.

Variabel statis masih dilindungi dan digunakan hanya dalam lingkup fungsi use_foo ()

Dalam contoh ini, panggilan ke foo () berfungsi persis seperti (sehubungan dengan setara c ++):

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

jika kelas Foo didefinisikan secara terbatas sebagai kelas tunggal, itu akan ideal. Ini akan membuatnya lebih pythonic.

yash
sumber
-1

Tentu ini adalah pertanyaan lama tapi saya pikir saya mungkin memberikan beberapa pembaruan.

Tampaknya argumen kinerja sudah usang. Rangkaian tes yang sama muncul untuk memberikan hasil yang serupa untuk siInt_try dan isInt_re2. Tentu saja hasilnya bervariasi, tetapi ini adalah satu sesi di komputer saya dengan python 3.4.4 pada kernel 4.3.01 dengan Xeon W3550. Saya telah menjalankannya beberapa kali dan hasilnya tampak serupa. Saya memindahkan regex global ke fungsi statis, tetapi perbedaan kinerja dapat diabaikan.

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

Dengan keluarnya masalah kinerja, tampaknya try / catch akan menghasilkan kode bukti masa depan dan cornercase-paling jadi mungkin hanya membungkusnya dalam fungsi

Keji Li
sumber
1
Apa yang bahkan Anda bandingkan di sini? Ini sepertinya komentar tentang jawaban lain, tetapi tidak jelas yang mana, dan itu tidak menjawab pertanyaan itu sendiri.
ShadowRanger