Python tương đương với các biến tĩnh bên trong một hàm là gì?


630

Python tương đương với mã C / C ++ này là gì?

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

cụ thể, làm thế nào để thực hiện một thành viên tĩnh ở cấp độ chức năng, trái ngược với cấp độ lớp? Và việc đặt hàm vào một lớp có thay đổi gì không?


22
NO tương đương tôi sợ. Ngay cả khi bạn thực hiện hack trang trí với các thuộc tính chức năng, bạn sẽ có thể truy cập vào biến bên ngoài, điều này hơi thất bại. Hơn nữa, bạn sẽ phải mã cứng tên hàm trong hàm, điều này rất khó chịu. Tôi sẽ đề nghị sử dụng một biến toàn cục của lớp hoặc mô-đun thay vì với _tiền tố thông thường .
lpapp

8
Đối với những người không lập trình C, [ stackoverflow.com/questions/5033627/ Biến tĩnh bên trong một hàm chỉ hiển thị bên trong phạm vi của hàm đó, nhưng thời gian tồn tại của nó là toàn bộ vòng đời của chương trình và nó chỉ được khởi tạo một lần). Về cơ bản, một bộ đếm hoặc biến lưu trữ liên tục sống giữa các lệnh gọi hàm.
smci

2
@lpapp: có một loại, đó là một thành viên trong lớp . Bạn đúng rằng chúng tôi không thể ngăn chặn các mã khác xem nó hoặc thay đổi nó.
smci

Câu trả lời:


681

Một chút đảo ngược, nhưng điều này sẽ làm việc:

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

Nếu bạn muốn mã khởi tạo bộ đếm ở trên cùng thay vì dưới cùng, bạn có thể tạo một trang trí:

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

Sau đó sử dụng mã như thế này:

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

Thật foo.không may, nó vẫn sẽ yêu cầu bạn sử dụng tiền tố.

(Tín dụng: @ony )


23
chỉ có một ví dụ của foo - chức năng này. tất cả các yêu cầu truy cập cùng một biến.
Claudiu

121
Xin lỗi vì đã đào cái này lên, nhưng tôi muốn đặt if "counter" not in foo.__dict__: foo.counter = 0những dòng đầu tiên foo(). Điều này sẽ giúp tránh mã bên ngoài chức năng. Không chắc chắn nếu điều này có thể trở lại vào năm 2008 mặc dù. PS đã tìm thấy câu trả lời này trong khi tìm kiếm khả năng tạo các biến hàm tĩnh, vì vậy luồng này vẫn còn "sống" :)
binaryLV

8
@binaryLV: Có lẽ tôi thích cách tiếp cận đầu tiên. Vấn đề với cách tiếp cận đầu tiên là nó không rõ ràng ngay lập tức foofoo.counter = có liên quan mật thiết với nhau. tuy nhiên, cuối cùng tôi thích cách tiếp cận trang trí hơn, vì không có cách nào mà người trang trí sẽ không được gọi và nó rõ ràng hơn về mặt ngữ nghĩa của nó ( @static_var("counter", 0)dễ dàng hơn và có ý nghĩa hơn đối với mắt tôi hơn if "counter" not in foo.__dict__: foo.counter = 0, đặc biệt là sau này bạn phải sử dụng tên hàm (hai lần) có thể thay đổi).
Claudiu

6
@lpapp: Nó phụ thuộc vào điểm của các biến tĩnh là gì. Tôi luôn nghĩ rằng nó sẽ có cùng giá trị trên nhiều lệnh gọi hàm, điều này đáp ứng. Tôi chưa bao giờ coi đó là về việc che giấu biến, điều này không như bạn nói.
Claudiu

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

221

Bạn có thể thêm các thuộc tính cho một hàm và sử dụng nó như một biến tĩnh.

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

# attribute must be initialized
myfunc.counter = 0

Ngoài ra, nếu bạn không muốn thiết lập biến ngoài hàm, bạn có thể sử dụng hasattr()để tránh AttributeErrorngoại lệ:

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

Dù sao các biến tĩnh là khá hiếm, và bạn nên tìm một nơi tốt hơn cho biến này, rất có thể trong một lớp.


6
Tại sao không thử thay vì tuyên bố if?
ravwojdyla

12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1nên làm như vậy, sử dụng ngoại lệ thay thế.
sleblanc

Các ngoại lệ nên được sử dụng cho các tình huống ngoại lệ, tức là những trường hợp mà lập trình viên mong đợi sẽ không xảy ra, chẳng hạn như một tệp đầu vào mà nó đã mở thành công đột nhiên không có sẵn. Đây là một tình huống dự kiến, một tuyên bố if có ý nghĩa hơn.
Hack Saw

11
@Hack_Saw: Chà, đây là Pythonic (tốt hơn là xin tha thứ hơn là cho phép). Điều này thực sự được khuyến nghị trong các kỹ thuật tối ưu hóa Python vì nó giúp tiết kiệm chi phí nếu (mặc dù tôi không khuyến nghị tối ưu hóa sớm). Quy tắc của bạn về các trường hợp ngoại lệ: 1. Thất bại là một trường hợp đặc biệt ở đây, theo một nghĩa nào đó. Nó chỉ xảy ra một lần. 2. Tôi nghĩ rằng quy tắc đó là về việc sử dụng (tức là nâng cao) ngoại lệ. Điều này đang bắt một ngoại lệ cho một cái gì đó bạn mong đợi để làm việc nhưng có một kế hoạch dự phòng, đó là một điều phổ biến trong hầu hết các ngôn ngữ.
leewz

@leewangzhong: Có kèm theo một khối không tăng ngoại lệ trong trybất kỳ chi phí nào không? Chỉ tò mò thôi.
trss

201

Người ta cũng có thể xem xét:

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

Lý do:

  • nhiều pythonic ("yêu cầu sự tha thứ không được phép")
  • sử dụng ngoại lệ (chỉ ném một lần) thay vì ifnhánh (nghĩ ngoại lệ StopIteration )

11
Tôi đã không làm Python lâu, nhưng điều này thỏa mãn một trong những điều khoản ngầm của ngôn ngữ: nếu nó không (khá) dễ dàng, thì bạn đã làm sai .
ZX9

Không hoạt động ngay lập tức với các phương thức lớp, "self.foo.count = 1" làm tăng AttributionError một lần nữa.
biệt thự

16
Đây là giải pháp chính xác và nó phải là câu trả lời được chấp nhận vì mã khởi tạo sẽ được chạy khi hàm được gọi và không phải khi mô-đun được thực thi hoặc khi một cái gì đó từ nó được nhập, đó là trường hợp nếu bạn sử dụng phương pháp trang trí từ câu trả lời hiện đang được chấp nhận. Xem thực thi chức năng trang trí Python . Nếu bạn có một mô-đun thư viện khổng lồ thì mọi trình trang trí sẽ được chạy, bao gồm cả các chức năng bạn không nhập.
Nils Lindemann

3
Một cách tiếp cận đơn giản hơn: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCantlyOne

5
@MANU Sử dụng hasattr()cho việc này không đơn giản và cũng kém hiệu quả hơn.
moooeeeep

48

Các câu trả lời khác đã chứng minh cách bạn nên làm điều này. Đây là một cách bạn không nên:

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

Các giá trị mặc định chỉ được khởi tạo khi hàm được đánh giá lần đầu tiên, không phải mỗi lần nó được thực thi, do đó bạn có thể sử dụng danh sách hoặc bất kỳ đối tượng có thể thay đổi nào khác để lưu trữ giá trị tĩnh.


Tôi đã thử điều đó, nhưng vì một số lý do, tham số hàm đã tự khởi tạo thành 140, không phải 0. Tại sao lại như vậy?
andrewdotnich

1
@bouvard Đối với các hàm đệ quy cần một biến tĩnh, đây là hàm duy nhất thực sự đọc tốt.
cuộc sống

1
Tôi đã thử một vài cách tiếp cận và tôi ước nó sẽ được chấp nhận là pythonic. Với một số tên đầy ý nghĩa như def foo(arg1, arg2, _localstorage=DataClass(counter=0))tôi thấy nó cũng dễ đọc. Một điểm tốt khác là dễ dàng đổi tên chức năng.
VPfB

2
Tại sao bạn nói bạn không nên làm theo cách đó? Trông hoàn toàn hợp lý với tôi!
Konstantin

1
@VPfB: Để lưu trữ chung, bạn có thể sử dụng types.SimpleNamespace, tạo nó def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):mà không cần xác định một lớp đặc biệt.
ShadowRanger

43

Nhiều người đã đề nghị thử nghiệm 'hasattr', nhưng có một câu trả lời đơn giản hơn:

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

Không thử / ngoại trừ, không có thử nghiệm hasattr, chỉ có getattr với mặc định.


2
chú ý đến parm thứ ba của getattr khi bạn đặt func ở đó, ví dụ: def func (): def foo (): return 1112 func.c gặp = getattr (func, 'counter', foo ()) + 1 khi bạn gọi func, foo sẽ luôn được gọi!
Codefor

1
Chỉ cần một cuộc gọi để getattr mỗi khi func được gọi. Điều đó tốt nếu hiệu suất không phải là một vấn đề, nếu đó là thử / ngoại trừ sẽ chiến thắng.
Đánh dấu Lawrence

2
@MarkLawrence: Trên thực tế, ít nhất là trên bản cài đặt Windows x64 3.8.0 của tôi, sự khác biệt về hiệu năng giữa câu trả lời này và cách tiếp cận tương đương try/ exceptdựa trên ravwojdyla là khá vô nghĩa. Một ipython %%timeitmicrobenchmark đơn giản đã cho chi phí của try/ exceptở mức 255 ns mỗi cuộc gọi, so với 263 ns cho getattrgiải pháp dựa trên. Vâng, try/ exceptlà nhanh hơn, nhưng nó không chính xác là "chiến thắng"; đó là một tối ưu hóa vi mô nhỏ. Viết bất cứ mã nào có vẻ rõ ràng hơn, đừng lo lắng về sự khác biệt hiệu suất tầm thường như thế này.
ShadowRanger

@ShadowRanger cảm ơn vì đã chấm điểm đó. Tôi đã tự hỏi về tuyên bố của MarkLawrence trong 2 năm và tôi rất vui vì bạn đã nghiên cứu. Tôi chắc chắn đồng ý với câu cuối cùng của bạn - "viết bất cứ mã nào có vẻ rõ ràng hơn" - đó chính xác là lý do tại sao tôi viết câu trả lời này.
Jonathan

28

Đây là một phiên bản được đóng gói đầy đủ mà không yêu cầu một cuộc gọi khởi tạo bên ngoài:

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

Trong Python, các hàm là các đối tượng và chúng ta có thể chỉ cần thêm hoặc các bản vá khỉ, các biến thành viên cho chúng thông qua thuộc tính đặc biệt __dict__. Tích hợp vars()trả về thuộc tính đặc biệt __dict__.

EDIT: Lưu ý, không giống như try:except AttributeErrorcâu trả lời thay thế , với cách tiếp cận này, biến sẽ luôn sẵn sàng cho logic mã sau khi khởi tạo. Tôi nghĩ rằng giải try:except AttributeErrorpháp thay thế sau đây sẽ ít DRY hơn và / hoặc có dòng chảy vụng về:

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: Tôi chỉ đề xuất cách tiếp cận trên khi hàm sẽ được gọi từ nhiều vị trí. Nếu thay vào đó, chức năng chỉ được gọi ở một nơi, tốt hơn là sử dụng 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
vấn đề duy nhất với điều này là nó thực sự không gọn gàng chút nào, và bất cứ khi nào bạn muốn sử dụng mẫu này, bạn phải cắt và dán mã ... do đó tôi sử dụng một công cụ trang trí
Claudiu

2
có lẽ nên sử dụng một cái gì đó nhưtry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith 6/12/13

2
Vui lòng sử dụng X not in Ythay vì not X in Y(hoặc khuyên bạn sử dụng nếu bạn chỉ sử dụng nó vì mục đích so sánh trông giống nhau hơn giữa điều đó và hasattr)
Nick T

làm thế nào về điều này: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCantlyOne

nó không lý tưởng bởi vì mệnh đề if thêm lồng nhau không cần thiết, trong tình huống này tôi thích setdefault hơn
Riaz Rizvi

27

Python không có biến tĩnh nhưng bạn có thể giả mạo nó bằng cách định nghĩa một đối tượng lớp có thể gọi được và sau đó sử dụng nó làm hàm. Cũng xem câu trả lời này .

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

Lưu ý rằng __call__làm cho một thể hiện của một lớp (đối tượng) có thể gọi được bằng tên riêng của nó. Đó là lý do tại sao gọi foo()ở trên gọi __call__phương thức của lớp . Từ tài liệu :

Thể hiện của các lớp tùy ý có thể được thực hiện bằng cách định nghĩa một __call__()phương thức trong lớp của chúng.


15
Các hàm đã là đối tượng nên điều này chỉ cần thêm một lớp không cần thiết.
DasIch

Xem câu trả lời SO này cho một ý kiến ​​dài rằng đây thực sự là một ý tưởng tốt. stackoverflow.com/questions/460586 . Tôi đồng ý rằng làm cho bất kỳ lớp nào như vậy trở thành một singleton, có lẽ như stackoverflow.com/questions/6760685 này , cũng sẽ là một ý tưởng tốt. Tôi không biết @ S.Lott có nghĩa là gì khi "... chuyển bộ đếm sang định nghĩa lớp ..." bởi vì có vẻ như nó đã ở vị trí biến lớp đối với tôi.
Reb.Cabin

1
Dựa trên nghiên cứu của tôi, kỹ thuật lớp học này dường như là "Pythonic" nhất trong số các phương pháp được trình bày trên trang này và sử dụng ít mánh khóe nhất. Do đó, tôi có kế hoạch chấp nhận nó như là sự thay thế của tôi cho các biến giống như C-static trong các hàm, với tư cách là một nhà phát triển Python mới.
Gabriel Staples

1
Điều gì xảy ra nếu tôi muốn foo1 = Foo () và foo2 = Foo ()?
Đánh dấu Lawrence

@MarkLawrence Sau đó, bạn có hai phiên bản khác nhau của một lớp có thể gọi được, mỗi trường hợp có bộ đếm riêng. Mà chính xác những gì bạn sẽ mong đợi nếu bạn không sử dụng thể foohiện được cung cấp dưới dạng đơn lẻ.
Aaron McMillin

14

Sử dụng hàm tạo để tạo iterator.

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

Sau đó sử dụng nó như

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

Nếu bạn muốn giới hạn trên:

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

Nếu trình vòng lặp chấm dứt (như ví dụ trên), bạn cũng có thể lặp qua nó trực tiếp, như

for i in foo_gen(20):
    print i

Tất nhiên, trong những trường hợp đơn giản này, tốt hơn là sử dụng xrange :)

Dưới đây là tài liệu về báo cáo năng suất .


11

Các giải pháp khác gắn một thuộc tính truy cập vào hàm, thường với logic phức tạp để xử lý việc khởi tạo. Điều này là không phù hợp cho mã mới.

Trong Python 3, cách đúng là sử dụng nonlocalcâu lệnh:

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

Xem PEP 3104 để biết thông số kỹ thuật của nonlocaltuyên bố.

Nếu bộ đếm được dự định là riêng tư cho mô-đun, nó nên được đặt tên _counterthay thế.


Ngay cả trước Python 3, bạn luôn có thể làm điều này bằng một global countercâu lệnh thay vì nonlocal counter( nonlocalchỉ cho phép bạn viết vào trạng thái đóng trong một hàm lồng nhau). Lý do mọi người gắn một thuộc tính cho hàm là để tránh gây ô nhiễm không gian tên toàn cầu cho trạng thái cụ thể của hàm, vì vậy bạn không phải thực hiện các thao tác hackier khi hai hàm cần counters độc lập . Giải pháp này không có quy mô; thuộc tính trên hàm làm. Câu trả lời của kdb là làm thế nào nonlocalcó thể giúp đỡ, nhưng nó làm tăng thêm sự phức tạp.
ShadowRanger

Eh, tôi nghĩ rằng sự phức tạp của một chức năng nhà máy hoặc trang trí là quá mức trừ khi bạn làm điều này rất nhiều, và trong trường hợp đó thiết kế đã có một chút mùi. Đối với một lần, chỉ cần thêm bộ đếm phi tiêu điểm và được thực hiện với nó. Tôi đã thêm một chút vào câu trả lời về quy ước đặt tên. Ngoài ra, lý do tôi khuyên nonlocaltrên globallà chính xác như bạn chỉ ra - nó hoạt động trong những trường hợp nghiêm hơn.
cbarrick

8

Sử dụng một thuộc tính của hàm làm biến tĩnh có một số nhược điểm tiềm năng:

  • Mỗi khi bạn muốn truy cập vào biến, bạn phải viết tên đầy đủ của hàm.
  • Mã bên ngoài có thể truy cập biến dễ dàng và gây rối với giá trị.

Con trăn thành ngữ cho vấn đề thứ hai có lẽ sẽ đặt tên biến với dấu gạch dưới hàng đầu để báo hiệu rằng nó không có nghĩa là được truy cập, trong khi vẫn giữ cho nó có thể truy cập được sau thực tế.

Một thay thế sẽ là một mẫu sử dụng các bao đóng từ vựng, được hỗ trợ với nonlocaltừ khóa trong python 3.

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

Đáng buồn là tôi biết không có cách nào để gói gọn giải pháp này vào một trang trí.


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)

Giống như mã của vincent ở trên, điều này sẽ được sử dụng như một công cụ trang trí hàm và các biến tĩnh phải được truy cập với tên hàm làm tiền tố. Ưu điểm của mã này (mặc dù phải thừa nhận rằng bất kỳ ai cũng có thể đủ thông minh để tìm ra nó) là bạn có thể có nhiều biến tĩnh và khởi tạo chúng theo cách thông thường hơn.


7

Dễ đọc hơn một chút, nhưng dài dòng hơn (Zen of Python: rõ ràng là tốt hơn ẩn):

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

Xem ở đây để giải thích về cách thức này hoạt động.


bạn có thể giải thích tại sao mã này hoạt động? Thứ hai foo()nên khởi tạo lại từ điển thành giá trị được chỉ định trong định nghĩa hàm (vì vậy với khóa bộ đếm có giá trị 0). Tại sao nó không?
raffaem

3
@raffamaiden: Đối số mặc định chỉ được đánh giá một lần khi hàm được xác định và không phải mỗi lần hàm được gọi.
Daniel K.

6
_count = 0
def foo ():
   toàn cầu
   _count + = 1
   in 'bộ đếm là', _count

Python thường sử dụng dấu gạch dưới để chỉ ra các biến riêng tư. Lý do duy nhất trong C để khai báo biến tĩnh bên trong hàm là để ẩn nó bên ngoài hàm, đây không phải là Python thực sự.


4

Sau khi thử một vài cách tiếp cận, cuối cùng tôi sử dụng một phiên bản cải tiến của câu trả lời của @ warvariuc:

import types

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

3

Cách thành ngữ là sử dụng một lớp , có thể có các thuộc tính. Nếu bạn cần các trường hợp không tách rời, hãy sử dụng một singleton.

Có một số cách bạn có thể giả mạo hoặc chuyển các biến "tĩnh" thành Python (một cách không được đề cập cho đến nay là có một đối số mặc định có thể thay đổi), nhưng đây không phải là cách thức thành ngữ của Pythonic . Chỉ cần sử dụng một lớp học.

Hoặc có thể là một máy phát điện, nếu mô hình sử dụng của bạn phù hợp.


Đối với các hàm đệ quy độc lập, defaultđối số là thanh lịch nhất.
cuộc sống

3

Được thúc đẩy bởi câu hỏi này , tôi có thể trình bày một phương án khác có thể dễ sử dụng hơn một chút và sẽ giống nhau cho cả hai phương thức và hàm:

@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    

Nếu bạn thích cách sử dụng, đây là cách thực hiện:

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

Một cách khác (không được khuyến nghị!) Xoay quanh đối tượng có thể gọi được như https://stackoverflow.com/a/279598/916373 , nếu bạn không phiền khi sử dụng chữ ký cuộc gọi sôi nổi, sẽ phải làm

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

Thay vì tạo một hàm có một biến cục bộ tĩnh, bạn luôn có thể tạo một cái gọi là "đối tượng hàm" và cung cấp cho nó một biến thành viên tiêu chuẩn (không tĩnh).

Vì bạn đã đưa ra một ví dụ viết C ++, trước tiên tôi sẽ giải thích "đối tượng hàm" là gì trong C ++. Một "đối tượng hàm" đơn giản là bất kỳ lớp nào có quá tải operator(). Trường hợp của lớp sẽ hành xử như các chức năng. Ví dụ: bạn có thể viết int x = square(5);ngay cả khi squarelà một đối tượng (bị quá tải operator()) và về mặt kỹ thuật không phải là "hàm". Bạn có thể cung cấp cho một đối tượng hàm bất kỳ tính năng nào mà bạn có thể cung cấp cho một đối tượng lớp.

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

Trong Python, chúng ta cũng có thể quá tải operator()ngoại trừ phương thức được đặt tên thay thế __call__:

Đây là một định nghĩa lớp:

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

Dưới đây là một ví dụ về lớp đang được sử dụng:

from foo import foo

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

Đầu ra được in ra bàn điều khiển là:

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

Nếu bạn muốn hàm của mình nhận các đối số đầu vào, bạn cũng có thể thêm chúng vào __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

Linh hồn n + = 1

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

3

Một tuyên bố toàn cầu cung cấp chức năng này. Trong ví dụ dưới đây (python 3.5 trở lên để sử dụng "f"), biến đếm được xác định bên ngoài hàm. Xác định nó là toàn cục trong hàm biểu thị rằng phiên bản "toàn cầu" bên ngoài hàm nên được cung cấp cho hàm. Vì vậy, mỗi khi hàm chạy, nó sẽ sửa đổi giá trị bên ngoài hàm, bảo toàn nó ngoài hàm.

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"

Điều này hoạt động theo cùng một cách nếu sử dụng đúng. Sự khác biệt của mã c là trong ví dụ c của OP, biến đếm chỉ có thể được chạm bởi hàm. Một biến toàn cục trong python có thể được sử dụng hoặc thay đổi ở bất kỳ đâu trong tập lệnh
MortenSickel

2

Một biến tĩnh bên trong một phương thức 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

Cá nhân tôi thích những điều sau đây để trang trí. Để mỗi người của họ.

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
Xin đừng xúc phạm, nhưng giải pháp này gợi cho tôi một chút về "phong cách công ty lớn" :-) willa.me/2013/11/the-six- most
JJC

Có, sử dụng không di động (thao tác ngăn xếp nói chung là chi tiết triển khai CPython, không phải là thứ bạn có thể dựa vào PyPy, Jython, IronPython, what-have-you), thao tác ngăn xếp dễ vỡ, với nửa tá lệnh gọi mỗi lần sử dụng là cách tốt hơn so với một trang trí đơn giản ... </ s>
ShadowRanger

1

Câu trả lời này được xây dựng dựa trên câu trả lời của @claudiu.

Tôi thấy rằng mã của tôi đã trở nên ít rõ ràng hơn khi tôi luôn phải thêm tên hàm, bất cứ khi nào tôi có ý định truy cập một biến tĩnh.

Cụ thể, trong mã chức năng của tôi, tôi muốn viết:

print(statics.foo)

thay vì

print(my_function_name.foo)

Vì vậy, giải pháp của tôi là:

  1. thêm một staticsthuộc tính cho hàm
  2. trong phạm vi hàm, thêm một biến cục bộ staticslàm bí danh chomy_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))

Ghi chú

Phương thức của tôi sử dụng một lớp có tên Bunch, đó là một từ điển hỗ trợ truy cập kiểu thuộc tính, la JavaScript (xem bài viết gốc về nó, khoảng năm 2000)

Nó có thể được cài đặt thông qua pip install bunch

Nó cũng có thể được viết bằng tay như vậy:

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

Lưu ý: types.SimpleNamespace(khả dụng từ 3.3) hỗ trợ hành vi này ra khỏi hộp (và được triển khai trong C trên CPython, vì vậy nó sẽ nhanh nhất có thể).
ShadowRanger

0

Dựa trên câu trả lời của Daniel (bổ sung):

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)

Lý do tại sao tôi muốn thêm phần này là, các biến tĩnh được sử dụng không chỉ để tăng theo một giá trị nào đó, mà còn kiểm tra xem var tĩnh có bằng một giá trị nào đó không, như một ví dụ thực tế.

Biến tĩnh vẫn được bảo vệ và chỉ được sử dụng trong phạm vi của hàm use_foo ()

Trong ví dụ này, gọi hàm foo () chính xác như (tương ứng với tương đương c ++ tương ứng):

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

nếu lớp Foo được định nghĩa hạn chế là một lớp đơn, đó sẽ là lý tưởng. Điều này sẽ làm cho nó nhiều pythonic.


-1

Chắc chắn đây là một câu hỏi cũ nhưng tôi nghĩ rằng tôi có thể cung cấp một số cập nhật.

Có vẻ như các đối số hiệu suất là lỗi thời. Bộ kiểm tra tương tự xuất hiện để cho kết quả tương tự cho siInt_try và isInt num2. Tất nhiên kết quả khác nhau, nhưng đây là một phiên trên máy tính của tôi với python 3.4.4 trên kernel 4.3.01 với Xeon W3550. Tôi đã chạy nó nhiều lần và kết quả có vẻ tương tự nhau. Tôi đã chuyển regex toàn cầu thành hàm tĩnh, nhưng sự khác biệt hiệu năng là không đáng kể.

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

Với vấn đề về hiệu năng, có vẻ như việc thử / bắt sẽ tạo ra mã chứng minh tương lai và cornercase nhất vì vậy có thể chỉ cần bọc nó trong chức năng


1
Bạn thậm chí đang so sánh ở đây là gì? Điều này có vẻ giống như một nhận xét về các câu trả lời khác, nhưng không rõ câu trả lời nào và nó không trả lời chính câu hỏi đó.
ShadowRanger
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.