Python ที่เทียบเท่ากับตัวแปรคงที่ภายในฟังก์ชั่นคืออะไร?


630

งูอะไรที่เทียบเท่ากับรหัส C / C ++ นี้?

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

โดยเฉพาะอย่างยิ่งหนึ่งจะใช้สมาชิกคงที่ในระดับฟังก์ชั่นเมื่อเทียบกับระดับชั้นเรียนได้อย่างไร และการวางฟังก์ชั่นในชั้นเรียนเปลี่ยนอะไรหรือไม่?


22
มีไม่เท่าเทียมกันฉันกลัว แม้ว่าคุณจะแฮ็คมัณฑนากรพร้อมกับฟังก์ชั่นฟังก์ชั่นคุณจะสามารถเข้าถึงตัวแปรภายนอกซึ่งเป็นจุดที่น่าเศร้า นอกจากนี้คุณจะต้องใช้รหัสฟังก์ชั่นในฟังก์ชั่นซึ่งเป็นเรื่องที่น่ารำคาญมาก ฉันขอแนะนำให้ใช้ตัวแปรโกลบอลคลาสหรือโมดูลแทนพร้อมกับส่วน_นำหน้าแบบเดิม
lpapp

8
สำหรับผู้ที่ไม่ใช่โปรแกรมเมอร์ C, [ stackoverflow.com/questions/5033627/ …ตัวแปรแบบคงที่ภายในฟังก์ชั่นจะมองเห็นได้เฉพาะภายในขอบเขตของฟังก์ชั่นนั้น แต่อายุการใช้งานของโปรแกรมนั้นคืออายุการใช้งานทั้งหมดของโปรแกรม โดยพื้นฐานแล้วตัวนับแบบต่อเนื่องหรือตัวแปรหน่วยเก็บข้อมูลที่อยู่ระหว่างการเรียกใช้ฟังก์ชัน
smci

2
@lpapp: มีชนิดของคือมันเป็นสมาชิกชั้นเรียน คุณถูกต้องที่เราไม่สามารถป้องกันรหัสอื่นดูหรือเปลี่ยนแปลงมัน
smci

คำตอบ:


681

กลับด้านเล็กน้อย แต่ควรทำงานได้:

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

หากคุณต้องการรหัสการเริ่มต้นเคาน์เตอร์ที่ด้านบนแทนด้านล่างคุณสามารถสร้างมัณฑนากร:

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

จากนั้นใช้รหัสดังนี้:

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

มันจะยังคงต้องการให้คุณใช้foo.คำนำหน้า แต่น่าเสียดายที่

(เครดิต: @ony )


23
มีเพียงอินสแตนซ์ของ foo เพียงฟังก์ชันเดียวเท่านั้น การเรียกใช้ทั้งหมดเข้าถึงตัวแปรเดียวกัน
Claudiu

121
ขออภัยสำหรับการขุดขึ้นมานี้ แต่ฉันอยากจะใส่เป็นบรรทัดแรกของif "counter" not in foo.__dict__: foo.counter = 0 foo()สิ่งนี้จะช่วยหลีกเลี่ยงโค้ดที่อยู่นอกฟังก์ชั่น ไม่แน่ใจว่าสิ่งนี้เป็นไปได้ในปี 2008 แม้ว่า ป.ล. พบคำตอบนี้ในขณะที่ค้นหาความเป็นไปได้ในการสร้างตัวแปรฟังก์ชั่นแบบคงที่ดังนั้นกระทู้นี้ยังคงเป็น "มีชีวิตอยู่" :)
binaryLV

8
@binaryLV: ฉันอาจต้องการที่จะเป็นวิธีแรก ปัญหาของแนวทางแรกคือไม่ชัดเจนในทันทีfooและfoo.counter = เกี่ยวข้องอย่างใกล้ชิด อย่างไรก็ตามในที่สุดฉันก็ชอบแนวทางของมัณฑนากรเนื่องจากไม่มีวิธีที่จะเรียกมัณฑนากรและมันชัดเจนกว่าความหมายว่ามันทำอะไร ( @static_var("counter", 0)ง่ายกว่า & ทำให้รู้สึกดีต่อสายตามากกว่าif "counter" not in foo.__dict__: foo.counter = 0โดยเฉพาะอย่างยิ่งในภายหลังที่คุณต้องใช้ ชื่อฟังก์ชั่น (สองครั้ง) ซึ่งอาจมีการเปลี่ยนแปลง)
Claudiu

6
@lpapp: ขึ้นอยู่กับจุดของตัวแปรสแตติก ฉันคิดเสมอว่ามันจะเป็นค่าเดียวกันในการเรียกใช้ฟังก์ชั่นหลายครั้ง ฉันไม่เคยคิดที่จะซ่อนความแปรปรวนซึ่งไม่ได้เป็นอย่างที่คุณพูด
Claudiu

3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Erik Aronesty

221

คุณสามารถเพิ่มคุณสมบัติให้กับฟังก์ชั่นและใช้มันเป็นตัวแปรแบบคงที่

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

# attribute must be initialized
myfunc.counter = 0

หรือหากคุณไม่ต้องการตั้งค่าตัวแปรนอกฟังก์ชั่นคุณสามารถใช้hasattr()เพื่อหลีกเลี่ยงAttributeErrorข้อยกเว้น:

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

อย่างไรก็ตามตัวแปรคงที่ค่อนข้างหายากและคุณควรหาที่ที่ดีกว่าสำหรับตัวแปรนี้ซึ่งน่าจะเป็นในชั้นเรียน


6
ทำไมไม่ลองแทนคำสั่ง if?
ravwojdyla

12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1ควรทำเช่นเดียวกันโดยใช้ข้อยกเว้นแทน
sleblanc

ควรใช้ข้อยกเว้นสำหรับสถานการณ์เจ๋ง ๆ นั่นคือสิ่งที่โปรแกรมเมอร์คาดหวังว่าจะไม่เกิดขึ้นเช่นไฟล์อินพุตที่เปิดสำเร็จไม่สามารถใช้งานได้ทันที นี่เป็นสถานการณ์ที่คาดหวังคำสั่ง if จะเหมาะสมกว่า
Hack Saw

11
@Hack_Saw: นี่คือ Pythonic (ดีกว่าที่จะขอการอภัยมากกว่าการอนุญาต) แนะนำให้ใช้จริงในเทคนิคการปรับให้เหมาะสมของ Python เพราะจะช่วยประหยัดค่าใช้จ่ายหาก (แม้ว่าฉันจะไม่แนะนำการปรับให้เหมาะสมก่อนเวลา) กฎของคุณเกี่ยวกับกรณีพิเศษ: 1. ความล้มเหลวเป็นกรณีพิเศษที่นี่ในแง่หนึ่ง มันเกิดขึ้นเพียงครั้งเดียว 2. ฉันคิดว่ากฎนั้นเกี่ยวกับการใช้ข้อยกเว้น (เช่นการเพิ่ม) นี่คือข้อยกเว้นสำหรับสิ่งที่คุณคาดว่าจะทำงาน แต่มีแผนสำรองซึ่งเป็นเรื่องปกติในภาษาส่วนใหญ่
leewz

@leewangzhong: การปิดล้อมบล็อกที่ไม่ทำให้เกิดข้อยกเว้นภายในtryเพิ่มค่าใช้จ่ายหรือไม่ แค่สงสัย.
trss

201

หนึ่งอาจพิจารณา:

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

เหตุผล:

  • pythonic มาก ("ขอการอภัยไม่ได้รับอนุญาต")
  • ใช้ข้อยกเว้น (โยนเพียงครั้งเดียว) แทนifสาขา (คิดว่าStopIteration exception)

11
ฉันยังไม่ได้ทำหลามยาว แต่ตอบสนองนี้เป็นหนึ่งในที่อยู่อาศัยโดยนัยของภาษา: หากยังไม่ได้ (ธรรม) เรื่องง่ายที่คุณกำลังทำมันผิด
ZX9

ไม่สามารถใช้งานได้ทันทีด้วยวิธีการเรียน "self.foo.counter = 1" เพิ่ม AttributeError อีกครั้ง
villasv

16
นี่เป็นทางออกที่ถูกต้องและควรเป็นคำตอบที่ยอมรับได้เนื่องจากรหัสการเริ่มต้นจะทำงานเมื่อมีการเรียกใช้ฟังก์ชันและไม่ใช่เมื่อมีการเรียกใช้งานโมดูลหรือเมื่อมีการนำเข้าบางสิ่งจากมันซึ่งเป็นกรณีถ้าคุณใช้วิธีตกแต่งภายในจาก คำตอบที่ยอมรับในปัจจุบัน ดูงูหลามดำเนินฟังก์ชั่นมัณฑนากร หากคุณมีโมดูลห้องสมุดขนาดใหญ่แล้วมัณฑนากรทุกคนจะทำงานรวมถึงฟังก์ชั่นที่คุณไม่ได้นำเข้า
Nils Lindemann

3
วิธีที่ง่ายกว่า: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne

5
@MANU การใช้hasattr()งานนี้ไม่ง่ายและมีประสิทธิภาพน้อยกว่า
moooeeeep

48

คำตอบอื่น ๆ แสดงให้เห็นถึงวิธีที่คุณควรทำ นี่คือวิธีที่คุณไม่ควรทำ:

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

ค่าเริ่มต้นจะเริ่มต้นได้ก็ต่อเมื่อฟังก์ชั่นได้รับการประเมินครั้งแรกไม่ใช่ทุกครั้งที่มีการดำเนินการดังนั้นคุณสามารถใช้รายการหรือวัตถุที่ไม่แน่นอนอื่น ๆ เพื่อจัดเก็บค่าคงที่


ฉันลองมัน แต่ด้วยเหตุผลบางอย่างพารามิเตอร์ของฟังก์ชั่นเริ่มต้นเองที่ 140 ไม่ใช่ 0 ทำไมถึงเป็นเช่นนั้น
andrewdotnich

1
@bouvard สำหรับฟังก์ชั่นวนซ้ำที่ต้องการตัวแปรคงที่นี่เป็นเพียงหนึ่งเดียวที่อ่านได้ดีจริงๆ
อายุการใช้งาน

1
ฉันลองวิธีการหลายวิธีและฉันหวังว่าวิธีนี้จะได้รับการยอมรับในฐานะ pythonic ด้วยชื่อเต็มความหมายบางอย่างdef foo(arg1, arg2, _localstorage=DataClass(counter=0))ฉันพบว่าอ่านได้ดี อีกจุดที่ดีคือการเปลี่ยนชื่อฟังก์ชั่นง่าย
VPfB

2
ทำไมคุณถึงบอกว่าคุณไม่ควรทำอย่างนั้น? ดูสมเหตุสมผลกับฉันอย่างสมบูรณ์แบบ!
Konstantin

1
@VPfB: สำหรับการจัดเก็บข้อมูลทั่วไปคุณสามารถใช้types.SimpleNamespaceทำให้มันdef foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):โดยไม่จำเป็นต้องกำหนดชั้นเรียนพิเศษ
ShadowRanger

43

หลายคนได้แนะนำการทดสอบ 'hasattr' แล้ว แต่มีคำตอบที่ง่ายกว่า:

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

ไม่ลอง / ยกเว้น, ไม่มีการทดสอบ hasattr, เพียงแค่ getattr ด้วยค่าเริ่มต้น


2
ให้ความสนใจกับ parm ที่สามของ getattr เมื่อคุณใส่ func ที่นั่นตัวอย่าง: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 เมื่อคุณโทร func, foo จะถูกเรียกเสมอ!
Codefor

1
เพียงแค่โทรหา getattr ทุกครั้งที่ func ถูกเรียก ไม่เป็นไรถ้าปัญหาไม่ได้ผลถ้าลอง / ยกเว้นจะได้รับรางวัล
Mark Lawrence

2
@ MarkLawrence: จริง ๆ แล้วอย่างน้อยในการติดตั้ง Windows ของฉัน x64 3.8.0 ความแตกต่างของประสิทธิภาพระหว่างคำตอบนี้และวิธีการเทียบเท่าtry/ exceptตามravwojdyla ของไม่มีความหมายสวย ipython %%timeitmicrobenchmark ง่ายๆให้ค่าใช้จ่ายของtry/ exceptที่ 255 ns ต่อการโทรเทียบกับ 263 ns สำหรับการgetattrแก้ปัญหาตาม ใช่ the try/ exceptเร็วกว่า แต่ก็ไม่ได้ "ชนะมืออย่างแน่นอน"; มันเป็นการเพิ่มประสิทธิภาพขนาดเล็ก เขียนโค้ดอะไรก็ได้ที่ชัดเจนกว่าไม่ต้องกังวลกับความแตกต่างด้านประสิทธิภาพเล็กน้อยเช่นนี้
ShadowRanger

@ShadowRanger ขอบคุณสำหรับการเปรียบเทียบว่า ฉันสงสัยเกี่ยวกับคำแถลงการณ์ของ MarkLawrence เป็นเวลา 2 ปีและฉันดีใจมากที่คุณทำการวิจัย ฉันเห็นด้วยกับประโยคสุดท้ายของคุณ - "เขียนโค้ดอะไรก็ได้ที่ชัดเจน" นั่นคือเหตุผลที่ฉันเขียนคำตอบนี้
Jonathan

28

นี่คือเวอร์ชันที่ถูกห่อหุ้มอย่างสมบูรณ์ที่ไม่ต้องการการเรียกใช้การเริ่มต้นภายนอก:

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

ในหลาม, __dict__ฟังก์ชั่นเป็นวัตถุและเราก็สามารถเพิ่มหรือแพทช์ลิงตัวแปรสมาชิกกับพวกเขาผ่านทางคุณลักษณะพิเศษ ในตัวผลตอบแทนคุณลักษณะพิเศษ vars()__dict__

แก้ไข: หมายเหตุแตกต่างจากtry:except AttributeErrorคำตอบทางเลือกด้วยวิธีนี้ตัวแปรจะพร้อมเสมอสำหรับตรรกะรหัสต่อไปนี้การเริ่มต้น ฉันคิดว่าtry:except AttributeErrorทางเลือกต่อไปนี้จะน้อยกว่า DRY และ / หรือมีการไหลเชื่องช้า:

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

แก้ไข 2: ฉันแนะนำวิธีข้างต้นเมื่อฟังก์ชันจะถูกเรียกจากหลายตำแหน่งเท่านั้น หากมีการเรียกใช้ฟังก์ชันในที่เดียวควรใช้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))
    ...

2
ปัญหาเดียวของเรื่องนี้ก็คือมันไม่เรียบร้อยเลยและเมื่อใดก็ตามที่คุณต้องการใช้รูปแบบนี้คุณต้องตัดและวางรหัส ... ดังนั้นฉันจึงใช้มัณฑนากร
Claudiu

2
อาจจะใช้สิ่งที่ชอบtry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith

2
กรุณาใช้X not in Yมากกว่าnot X in Y(หรือให้คำแนะนำการใช้ถ้าคุณเป็นเพียงแค่ใช้มันเพื่อประโยชน์ของการเปรียบเทียบที่ดูคล้ายกันมากขึ้นระหว่างนั้นและhasattr)
นิคที

แล้วเรื่องนี้: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne

มันไม่เหมาะเพราะถ้าประโยคเพิ่มการซ้อนที่ไม่จำเป็นในสถานการณ์นี้ฉันชอบ setdefault
Riaz Rizvi

27

Python ไม่มีตัวแปรคงที่ แต่คุณสามารถปลอมมันได้โดยการกำหนดวัตถุคลาส callable แล้วใช้มันเป็นฟังก์ชั่น ดูคำตอบนี้ด้วย

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

โปรดทราบว่า__call__ทำให้ตัวอย่างของคลาส (วัตถุ) callable โดยชื่อของตัวเอง นั่นเป็นเหตุผลที่การโทรfoo()ด้านบนเรียก__call__วิธีการเรียน จากเอกสาร :

อินสแตนซ์ของคลาสที่กำหนดเองสามารถทำให้ callable ได้โดยการกำหนด__call__()เมธอดในคลาสของพวกเขา


15
ฟังก์ชั่นเป็นวัตถุอยู่แล้วดังนั้นนี่เป็นการเพิ่มเลเยอร์ที่ไม่จำเป็น
DasIch

ดูคำตอบ SO นี้สำหรับความคิดเห็นที่ยาวนานว่านี่เป็นความคิดที่ดีจริงๆ stackoverflow.com/questions/460586 ฉันยอมรับว่าการทำให้คลาสใด ๆ เป็นแบบซิงเกิลบางทีเช่นstackoverflow.com/questions/6760685นี้ก็เป็นความคิดที่ดีเช่นกัน ฉันไม่รู้ว่า @ S.Lott แปลว่า "... ย้ายตัวนับเข้าสู่คำจำกัดความของคลาส ... " เพราะดูเหมือนว่ามันอยู่ในตำแหน่งตัวแปรระดับแล้วสำหรับฉัน
Reb.Cabin

1
จากการวิจัยของฉันเทคนิคระดับนี้ดูเหมือนจะเป็น "Pythonic" ที่สุดของวิธีการที่นำเสนอในหน้านี้และใช้กลอุบายน้อยที่สุด ดังนั้นฉันจึงวางแผนที่จะใช้มันเป็นของฉันไปแทนตัวแปร C-static-like ในฟังก์ชั่นในฐานะนักพัฒนา Python ใหม่ด้วยตัวเอง
Gabriel Staples

1
จะเกิดอะไรขึ้นถ้าฉันต้องการ foo1 = Foo () และ foo2 = Foo ()
Mark Lawrence

@ MarkLawrence แล้วคุณมีสองกรณีที่แตกต่างกันของคลาส callable แต่ละคนมีเคาน์เตอร์ของตัวเอง ซึ่งเป็นสิ่งที่คุณควรคาดหวังหากคุณไม่ได้ใช้อินสแตนซ์fooที่มีให้เป็นแบบซิงเกิล
Aaron McMillin

14

ใช้ฟังก์ชันตัวสร้างเพื่อสร้างตัววนซ้ำ

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

จากนั้นใช้มันเหมือน

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

หากคุณต้องการขีด จำกัด บน:

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

หากตัววนซ้ำสิ้นสุดลง (เช่นตัวอย่างด้านบน) คุณสามารถวนซ้ำโดยตรงเช่น

for i in foo_gen(20):
    print i

แน่นอนในกรณีง่าย ๆ เหล่านี้ควรใช้ xrange :)

นี่คือเอกสารเกี่ยวกับที่คำสั่งผลผลิต


11

โซลูชันอื่น ๆ แนบแอ็ตทริบิวต์ตัวนับเข้ากับฟังก์ชันโดยปกติจะมีตรรกะที่ซับซ้อนเพื่อจัดการกับการเริ่มต้น นี่ไม่เหมาะสมสำหรับรหัสใหม่

ใน Python 3 วิธีที่ถูกต้องคือการใช้nonlocalคำสั่ง:

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

ดูPEP 3104สำหรับข้อมูลจำเพาะของnonlocalข้อความสั่ง

หากตัวนับมีวัตถุประสงค์ให้เป็นส่วนตัวกับโมดูลควรตั้งชื่อ_counterแทน


แม้กระทั่งก่อนหน้า Python 3 คุณสามารถทำสิ่งนี้ได้โดยใช้global counterคำสั่งแทนnonlocal counter( nonlocalเพียงให้คุณเขียนสถานะการปิดในฟังก์ชันซ้อนกัน) เหตุผลที่คนจะติดแอตทริบิวต์ฟังก์ชั่นคือการหลีกเลี่ยงการก่อให้เกิดมลพิษ namespace โลกสำหรับรัฐที่เฉพาะเจาะจงกับฟังก์ชั่นเพื่อให้คุณไม่ต้องทำสิ่งที่ยิ่ง hackier เมื่อทั้งสองฟังก์ชั่นต้องเป็นอิสระcounters การแก้ปัญหานี้ไม่ได้ระดับ; คุณลักษณะในฟังก์ชั่นทำ คำตอบของ kdbคือวิธีที่nonlocalสามารถช่วยได้ แต่มันเพิ่มความซับซ้อน
ShadowRanger

เอ๊ะฉันคิดว่าความซับซ้อนของฟังก์ชั่นโรงงานหรือมัณฑนากรเกินความจริงเว้นแต่ว่าคุณทำสิ่งนี้มากและในกรณีนี้การออกแบบมีกลิ่นเหม็นเล็กน้อย สำหรับหนึ่งครั้งเพียงเพิ่มตัวนับ nonlocal และทำได้ด้วย ฉันได้เพิ่มคำตอบเกี่ยวกับการตั้งชื่ออนุสัญญาเล็กน้อย นอกจากนี้เหตุผลที่ฉันแนะนำnonlocalไปglobalนั้นตรงตามที่คุณชี้ - มันทำงานได้ในสถานการณ์ที่เข้มงวดมากขึ้น
cbarrick

8

การใช้แอททริบิวต์ของฟังก์ชั่นเป็นตัวแปรคงที่มีข้อบกพร่องที่อาจเกิดขึ้น

  • ทุกครั้งที่คุณต้องการเข้าถึงตัวแปรคุณต้องจดชื่อเต็มของฟังก์ชัน
  • โค้ดภายนอกสามารถเข้าถึงตัวแปรได้อย่างง่ายดายและยุ่งกับค่า

Idiomatic python สำหรับปัญหาที่สองอาจจะตั้งชื่อตัวแปรด้วยเครื่องหมายขีดเส้นใต้เพื่อส่งสัญญาณว่าไม่ได้หมายถึงการเข้าถึงในขณะที่ยังคงสามารถเข้าถึงได้หลังจากข้อเท็จจริง

อีกทางเลือกหนึ่งคือรูปแบบการใช้คำศัพท์ปิดซึ่งได้รับการสนับสนุนด้วยnonlocalคำหลักในหลาม 3

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

น่าเศร้าที่ฉันรู้วิธีที่จะไม่แค็ปซูลโซลูชั่นนี้เป็นมัณฑนากร


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)

เช่นเดียวกับโค้ดของ vincent ข้างต้นสิ่งนี้จะถูกใช้เป็นตัวประดับฟังก์ชั่นและตัวแปรแบบคงที่จะต้องเข้าถึงได้ด้วยชื่อฟังก์ชั่นเป็นคำนำหน้า ข้อดีของรหัสนี้ (แม้ว่าเป็นที่ยอมรับว่าทุกคนอาจฉลาดพอที่จะเข้าใจได้) ก็คือคุณสามารถมีตัวแปรคงที่หลายตัวและกำหนดค่าเริ่มต้นในแบบธรรมดา


7

สามารถอ่านได้มากขึ้น แต่ verbose มากขึ้น (Zen of Python: ชัดแจ้งดีกว่า implicit):

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

ดูที่นี่สำหรับคำอธิบายวิธีการทำงานนี้


คุณสามารถอธิบายอย่างละเอียดถึงสาเหตุที่รหัสนี้ทำงานได้อย่างไร ครั้งที่สองfoo()ควรเริ่มต้นพจนานุกรมใหม่เป็นค่าที่ระบุในการกำหนดฟังก์ชั่น (เช่นเดียวกับที่สำคัญเคาน์เตอร์มีค่า 0) ทำไมมันไม่ได้?
raffaem

3
@raffamaiden: อาร์กิวเมนต์เริ่มต้นจะถูกประเมินเพียงครั้งเดียวเมื่อมีการกำหนดฟังก์ชันและไม่ใช่ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน
Daniel K.

6
_counter = 0
def foo ():
   _counter ทั่วโลก
   _counter + = 1
   พิมพ์ 'counter is', _counter

Python ใช้เครื่องหมายขีดล่างเพื่อระบุตัวแปรส่วนตัว เหตุผลเดียวใน C ที่จะประกาศตัวแปรคงที่ภายในฟังก์ชั่นคือการซ่อนมันนอกฟังก์ชั่นซึ่งไม่ใช่ Python ที่เป็นสำนวนจริงๆ


4

หลังจากลองวิธีการต่าง ๆ ฉันจะใช้คำตอบของ @ warvariuc รุ่นปรับปรุง:

import types

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

3

สำนวนวิธีคือการใช้ระดับซึ่งจะมีคุณลักษณะ หากคุณต้องการอินสแตนซ์ที่ไม่ต้องแยกให้ใช้ซิงเกิลตัน

มีหลายวิธีที่คุณสามารถปลอมหรือลบตัวแปร "คงที่" ลงใน Python (ซึ่งไม่ได้กล่าวถึงในตอนนี้คือการมีอาร์กิวเมนต์เริ่มต้นที่ไม่แน่นอน) แต่นี่ไม่ใช่วิธีPythonic ที่เป็นสำนวนที่จะทำ เพียงแค่ใช้คลาส

หรืออาจเป็นตัวกำเนิดหากรูปแบบการใช้งานของคุณเหมาะสม


สำหรับฟังก์ชั่นวนซ้ำแบบสแตนด์อะโลนdefaultอาร์กิวเมนต์เป็นฟังก์ชันที่หรูหราที่สุด
อายุการใช้งาน

3

ได้รับคำแนะนำจากคำถามนี้ฉันขอเสนอทางเลือกอื่นซึ่งอาจใช้งานได้ดีกว่าและจะเหมือนกันสำหรับทั้งวิธีการและฟังก์ชั่น:

@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    

หากคุณชอบการใช้งานนี่คือการใช้งาน:

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

3

อีก (ไม่แนะนำ!) บิดบนวัตถุ callable เช่นhttps://stackoverflow.com/a/279598/916373ถ้าคุณไม่ทราบใช้ลายเซ็นขี้ขลาดโทรจะทำ

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

3

แทนที่จะสร้างฟังก์ชั่นที่มีตัวแปรสแตติกท้องถิ่นคุณสามารถสร้างสิ่งที่เรียกว่า "ฟังก์ชั่นวัตถุ" และให้มันเป็นสมาชิกมาตรฐาน (ไม่คงที่) ตัวแปรสมาชิก

เนื่องจากคุณให้ตัวอย่างเขียน C ++ ฉันจะอธิบายว่า "function object" ใน C ++ เป็นครั้งแรก A "ฟังก์ชั่นของวัตถุ" operator()เป็นเพียงชั้นใดที่มีมากเกินไป อินสแตนซ์ของคลาสจะทำงานเหมือนกับฟังก์ชัน ตัวอย่างเช่นคุณสามารถเขียนint x = square(5);ได้แม้ว่าsquareจะเป็นวัตถุ (ที่มีการโอเวอร์โหลดoperator()) และไม่ใช่ในทางเทคนิคไม่ใช่ "ฟังก์ชั่น" คุณสามารถให้ฟังก์ชั่นวัตถุใด ๆ ของคุณสมบัติที่คุณสามารถให้วัตถุคลาส

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

ใน Python เราสามารถโอเวอร์โหลดได้operator()ยกเว้นว่าเมธอดนั้นมีชื่อว่า__call__:

นี่คือคำจำกัดความของคลาส:

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

นี่คือตัวอย่างของการใช้คลาส:

from foo import foo

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

เอาต์พุตที่พิมพ์ไปยังคอนโซลคือ:

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

หากคุณต้องการให้ฟังก์ชันรับอาร์กิวเมนต์อินพุตคุณสามารถเพิ่มฟังก์ชันเหล่านั้น__call__ใน:

# 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


3

ประกาศทั่วโลกให้ฟังก์ชั่นนี้ ในตัวอย่างด้านล่าง (python 3.5 หรือสูงกว่าเพื่อใช้ "f") ตัวแปรตัวนับถูกกำหนดไว้ภายนอกฟังก์ชั่น การกำหนดให้เป็นโกลบอลในฟังก์ชั่นหมายความว่าเวอร์ชั่น "โกลบอล" ภายนอกฟังก์ชั่นควรจะมีให้สำหรับฟังก์ชั่น ดังนั้นทุกครั้งที่ฟังก์ชั่นทำงานจะปรับเปลี่ยนค่าภายนอกฟังก์ชั่นรักษามันไว้นอกเหนือจากฟังก์ชั่น

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"

วิธีนี้ใช้ได้เหมือนกันหากใช้อย่างถูกต้อง ความแตกต่างกับรหัส c คือในตัวอย่าง c ของ OP ตัวแปรตัวนับสามารถสัมผัสได้โดยฟังก์ชันเท่านั้น ตัวแปรระดับโลกในไพ ธ อนอาจใช้หรือเปลี่ยนแปลงที่ใดก็ได้ในสคริปต์
MortenSickel

2

ตัวแปรแบบคงที่ภายในเมธอด 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

1

โดยส่วนตัวฉันชอบสิ่งต่อไปนี้กับผู้ตกแต่ง เพื่อให้แต่ละคนนั้นเอง

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)

3
โปรดอย่าโกรธเคือง แต่โซลูชันนี้ทำให้ฉันนึกถึง "รูปแบบ บริษัท ขนาดใหญ่" :-) willa.me/2013/11/the-six-most-common-spon-species-of-code.html
JJC

ใช่การใช้งานที่ไม่ใช่แบบพกพา (การจัดการสแต็คโดยทั่วไปคือรายละเอียดการใช้งาน CPython ไม่ใช่สิ่งที่คุณสามารถพึ่งพาได้ใน PyPy, Jython, IronPython สิ่งที่คุณมี), การจัดการกองซ้อนที่เปราะบาง เป็นวิธีที่ดีกว่ามัณฑนากรง่าย ... </s>
ShadowRanger

1

คำตอบนี้สร้างจากคำตอบของ @claudiu

ฉันพบว่ารหัสของฉันชัดเจนน้อยลงเมื่อฉันต้องใส่ชื่อฟังก์ชั่นเสริมทุกครั้งที่ฉันต้องการเข้าถึงตัวแปรแบบคงที่

คือในรหัสฟังก์ชั่นของฉันฉันต้องการที่จะเขียน:

print(statics.foo)

แทน

print(my_function_name.foo)

ดังนั้นทางออกของฉันคือ:

  1. เพิ่มstaticsคุณสมบัติให้กับฟังก์ชั่น
  2. ในขอบเขตฟังก์ชั่นเพิ่มตัวแปรท้องถิ่นstaticsเป็นนามแฝงmy_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))

สังเกต

เมธอดของฉันใช้คลาสที่มีชื่อBunchซึ่งเป็นพจนานุกรมที่รองรับการเข้าถึงสไตล์แอ็ตทริบิวต์, la JavaScript (ดูบทความต้นฉบับเกี่ยวกับมันประมาณ 2000)

มันสามารถติดตั้งผ่าน pip install bunch

มันสามารถเขียนด้วยมือได้เช่น:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

หมายเหตุ: types.SimpleNamespace(มีให้ตั้งแต่ 3.3) สนับสนุนพฤติกรรมนี้นอกกรอบ (และใช้งานใน C บน CPython ดังนั้นจึงเร็วพอ ๆ กับที่เป็นไปได้)
ShadowRanger

0

การสร้างคำตอบของ Daniel (เพิ่มเติม):

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)

เหตุผลที่ฉันต้องการเพิ่มส่วนนี้คือตัวแปรสแตติกถูกใช้ไม่เพียง แต่สำหรับการเพิ่มโดยค่าบางอย่าง แต่ยังตรวจสอบว่า var แบบคงที่จะเท่ากับค่าบางอย่างเป็นตัวอย่างชีวิตจริง

ตัวแปรคงที่ยังคงได้รับการป้องกันและใช้ภายในขอบเขตของฟังก์ชัน use_foo ()

ในตัวอย่างนี้ call to foo () ทำหน้าที่เหมือนกับ (เทียบกับ 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

ถ้า class Foo ถูกกำหนดอย่าง จำกัด เป็นคลาสเดี่ยวนั่นจะเหมาะ นี่จะทำให้เป็น pythonic มากขึ้น


-1

แน่ใจว่านี่เป็นคำถามเก่า แต่ฉันคิดว่าฉันอาจให้การปรับปรุงบางอย่าง

ดูเหมือนว่าข้อโต้แย้งด้านประสิทธิภาพล้าสมัยแล้ว ชุดทดสอบเดียวกันนั้นให้ผลคล้ายกันสำหรับ siInt_try และ isInt_re2 แน่นอนผลลัพธ์ต่างกันไป แต่นี่เป็นหนึ่งเซสชันบนคอมพิวเตอร์ของฉันด้วย python 3.4.4 บนเคอร์เนล 4.3.01 กับ Xeon W3550 ฉันเรียกใช้หลายครั้งและผลลัพธ์ดูเหมือนจะคล้ายกัน ฉันย้าย regex ทั่วโลกเป็นฟังก์ชันคงที่ แต่ความแตกต่างด้านประสิทธิภาพนั้นเล็กน้อย

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

ด้วยปัญหาด้านประสิทธิภาพดูเหมือนว่าการลอง / จับจะสร้างรหัสที่พิสูจน์ได้ในอนาคตและ cornercase ที่สุดดังนั้นบางทีอาจจะห่อในฟังก์ชั่น


1
คุณเปรียบเทียบอะไรที่นี่ ดูเหมือนว่าจะเป็นความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ แต่ก็ไม่ชัดเจนว่าคำตอบใดและไม่ตอบคำถามตัวเอง
ShadowRanger
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.