Có thể sửa đổi biến trong python nằm trong phạm vi bên ngoài, nhưng không phải toàn cục không?


107

Cho mã sau:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

Đối với mã trong B()biến hàm bnằm trong phạm vi bên ngoài, nhưng không ở phạm vi toàn cục. Có thể sửa đổi bbiến từ bên trong B()hàm không? Chắc chắn tôi có thể đọc nó từ đây vàprint() , nhưng làm thế nào để sửa đổi nó?


Xin lỗi, tất nhiên là 2.7 :). Đối với python 3 quy tắc phạm vi đã thay đổi.
grigoryvp

Bạn có thể miễn blà có thể thay đổi. Việc gán cho bsẽ che phạm vi bên ngoài.
JimB

4
Đó là một trong những điểm xấu hổ của Python khi nonlocalchưa được phản hồi cho 2.x. Đó là một phần nội tại của hỗ trợ đóng cửa.
Glenn Maynard

Câu trả lời:


96

Python 3.x có nonlocaltừ khóa . Tôi nghĩ rằng điều này làm những gì bạn muốn, nhưng tôi không chắc liệu bạn đang chạy python 2 hay 3.

Câu lệnh phi địa phương làm cho số nhận dạng được liệt kê tham chiếu đến các biến được ràng buộc trước đó trong phạm vi bao quanh gần nhất. Điều này rất quan trọng vì hành vi mặc định cho ràng buộc là tìm kiếm không gian tên cục bộ trước. Câu lệnh cho phép mã được đóng gói để gắn lại các biến bên ngoài phạm vi cục bộ bên cạnh phạm vi toàn cục (mô-đun).

Đối với python 2, tôi thường chỉ sử dụng một đối tượng có thể thay đổi (như danh sách hoặc dict) và thay đổi giá trị thay vì gán lại.

thí dụ:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

Kết quả đầu ra:

[1, 1]

16
Một cách hay để làm điều này là class nonlocal: passở phạm vi bên ngoài. Sau đó, nonlocal.xcó thể được chỉ định trong phạm vi bên trong.
kindall

1
Cho đến bây giờ, tôi đã có hai mẹo python rất đơn giản nhưng rất hữu ích: mẹo thứ hai của bạn :) Cảm ơn @kindall!
swdev

@kindall đó là một bản hack tuyệt vời - nó khác biệt nhỏ so với cú pháp Python 3 và dễ đọc hơn nhiều so với việc truyền xung quanh một đối tượng có thể thay đổi.
dimo414,

2
@kindall rất gọn gàng, cảm ơn đống :) có lẽ cần một tên khác vì nó phá vỡ khả năng tương thích về phía trước. Trong python 3, nó là một xung đột từ khóa và sẽ gây ra a SyntaxError. Có lẽ NonLocal?
Adam Terrey

hoặc, vì nó là một lớp học về mặt kỹ thuật Nonlocal,? :-)
kindall

19

Bạn có thể sử dụng một lớp trống để giữ một phạm vi tạm thời. Nó giống như biến thể nhưng đẹp hơn một chút.

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

Điều này tạo ra kết quả tương tác sau:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

Điều kỳ lạ là lớp với các trường của nó là "hiển thị" trong một hàm bên trong nhưng các biến thì không, trừ khi bạn xác định biến bên ngoài bằng từ khóa "phi địa phương".
Celdor

12

Tôi hơi mới đối với Python, nhưng tôi đã đọc một chút về điều này. Tôi tin rằng điều tốt nhất bạn sẽ nhận được tương tự như công việc Java, đó là bao bọc biến bên ngoài của bạn trong một danh sách.

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

Chỉnh sửa: Tôi đoán điều này có lẽ đúng trước Python 3. Có vẻ như đây nonlocallà câu trả lời của bạn.


4

Không, bạn không thể, ít nhất là theo cách này.

Bởi vì "hoạt động thiết lập" sẽ tạo ra một tên mới trong phạm vi hiện tại, bao gồm tên bên ngoài.


"cái nào bọc cái bên ngoài" ý bạn là gì? Xác định một đối tượng với tên b trong một hàm lồng nhau không ảnh hưởng đến một đối tượng có cùng tên trong không gian vũ trụ của chức năng này
Eyquem

1
@eyquem có nghĩa là, dù câu lệnh gán ở đâu, nó sẽ giới thiệu tên trong toàn bộ phạm vi hiện tại. Chẳng hạn như mã mẫu của câu hỏi, nếu là: def C (): print (b) b = 2 thì "b = 2" sẽ giới thiệu tên b trong toàn bộ phạm vi C func, vì vậy khi in (b), nó sẽ cố gắng lấy b trong phạm vi func C cục bộ nhưng không phải là bên ngoài, b cục bộ chưa được khởi tạo nên sẽ có lỗi.
zchenah

1

Đối với bất kỳ ai xem xét điều này sau này về một giải pháp an toàn hơn nhưng nặng hơn là. Không cần truyền các biến làm tham số.

def outer():
    a = [1]
    def inner(a=a):
        a[0] += 1
    inner()
    return a[0]

1

Câu trả lời ngắn gọn sẽ hoạt động tự động

Tôi đã tạo một thư viện python để giải quyết vấn đề cụ thể này. Nó được phát hành dưới dạng không còn tồn tại, vì vậy hãy sử dụng nó theo cách bạn muốn. Bạn có thể cài đặt nó bằng pip install seapiehoặc xem trang chủ tại đây https://github.com/hirsimaki-markus/SEAPIE

user@pc:home$ pip install seapie

from seapie import Seapie as seapie
def A():
    b = 1

    def B():
        seapie(1, "b=2")
        print(b)

    B()
A()

đầu ra

2

các đối số có ý nghĩa sau:

  • Đối số đầu tiên là phạm vi thực thi. 0 có nghĩa là địa phương B(), 1 có nghĩa là cha mẹ A()và 2 có nghĩa là ông bà<module> hay toàn cầu
  • Đối số thứ hai là một chuỗi hoặc đối tượng mã bạn muốn thực thi trong phạm vi đã cho
  • Bạn cũng có thể gọi nó mà không cần đối số cho trình bao tương tác bên trong chương trình của bạn

Câu trả lời dài

Điều này phức tạp hơn. Seapie hoạt động bằng cách chỉnh sửa khung trong ngăn xếp cuộc gọi bằng CPython api. CPython là tiêu chuẩn trên thực tế nên hầu hết mọi người không phải lo lắng về nó.

Những từ ma thuật mà bạn có thể gặp nhiều nhất nếu bạn đang đọc cái này là:

frame = sys._getframe(1)          # 1 stands for previous frame
parent_locals = frame.f_locals    # true dictionary of parent locals
parent_globals = frame.f_globals  # true dictionary of parent globals

exec(codeblock, parent_globals, parent_locals)

ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
# the magic value 1 stands for ability to introduce new variables. 0 for update-only

Sau đó sẽ buộc các bản cập nhật chuyển vào phạm vi cục bộ. Tuy nhiên, phạm vi cục bộ được tối ưu hóa khác với phạm vi toàn cục vì vậy việc chèn vào các đối tượng mới có một số vấn đề khi bạn cố gắng gọi chúng trực tiếp nếu chúng không được khởi tạo theo bất kỳ cách nào. Tôi sẽ sao chép một số cách để tránh những vấn đề này từ trang github

  • Assingn, import và xác định các đối tượng của bạn trước
  • Chỉ định trình giữ chỗ cho các đối tượng của bạn trước
  • Gán lại đối tượng cho chính nó trong chương trình chính để cập nhật bảng ký hiệu: x = local () ["x"]
  • Sử dụng lệnh execute () trong chương trình chính thay vì gọi trực tiếp để tránh tối ưu hóa. Thay vì gọi x do: execute ("x")

Nếu bạn cảm thấy rằng việc sử dụng exec()không phải là thứ bạn muốn đi cùng, bạn có thể mô phỏng hành vi bằng cách cập nhật từ điển địa phương thực sự (không phải từ điển do người dân địa phương trả lại ()). Tôi sẽ sao chép một ví dụ từ https://faster-cpython.readthedocs.io/mutable.html

import sys
import ctypes

def hack():
    # Get the frame object of the caller
    frame = sys._getframe(1)
    frame.f_locals['x'] = "hack!"
    # Force an update of locals array from locals dict
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                          ctypes.c_int(0))

def func():
    x = 1
    hack()
    print(x)

func()

Đầu ra:

hack!

0

Tôi không nghĩ bạn nên muốn làm điều này. Các hàm có thể thay đổi mọi thứ trong ngữ cảnh đi kèm của chúng là rất nguy hiểm, vì ngữ cảnh đó có thể được viết mà không có kiến ​​thức về hàm.

Bạn có thể làm cho nó rõ ràng, bằng cách đặt B là phương thức công khai và C là phương thức riêng trong một lớp (có lẽ là cách tốt nhất); hoặc bằng cách sử dụng kiểu có thể thay đổi, chẳng hạn như danh sách và chuyển nó một cách rõ ràng tới C:

def A():
    x = [0]
    def B(var): 
        var[0] = 1
    B(x)
    print x

A()

2
Làm thế nào bạn có thể viết một hàm mà không biết về các hàm lồng nhau bên trong nó? Chức năng lồng nhau và đóng cửa là một phần nội tại của hàm họ đang đính kèm trong.
Glenn Maynard

Bạn cần biết về giao diện của các chức năng có trong chức năng của bạn, nhưng bạn không cần phải biết về những gì diễn ra bên trong chúng. Ngoài ra, bạn không thể mong đợi để biết những gì diễn ra trong các chức năng mà họ gọi, v.v.! Nếu một hàm sửa đổi một bộ nhớ không toàn cục hoặc không phải là phân loại, nó thường phải làm cho điều đó rõ ràng thông qua giao diện của nó, tức là lấy nó làm tham số.
Sideshow Bob

Tất nhiên, Python không buộc bạn phải giỏi như vậy, do đó là nonlocaltừ khóa - nhưng bạn có thể sử dụng nó một cách thận trọng hay không.
Sideshow Bob

5
@Bob: Tôi chưa bao giờ thấy việc sử dụng các cách đóng như thế này là nguy hiểm cả, ngoại trừ các vấn đề về ngôn ngữ. Hãy coi các local như một lớp tạm thời và các hàm cục bộ là các phương thức trên lớp và nó không phức tạp hơn thế. YMMV, tôi đoán vậy.
Glenn Maynard

0

Bạn có thể, nhưng bạn sẽ phải sử dụng trạng thái toàn cục (không phải là một giải pháp thực sự tốt như mọi khi khi sử dụng các biến toàn cục, nhưng nó hoạt động):

def A():
    global b
    b = 1

    def B():
      global b
      print( b )
      b = 2

    B()
A()

Xem câu trả lời của tôi giải thích nhược điểm tiềm ẩn của giải pháp này
eyquem

4
Việc sử dụng một biến toàn cục là hoàn toàn khác.
Glenn Maynard

0

Tôi không biết liệu có thuộc tính của một hàm cung cấp __dict__không gian bên ngoài của hàm hay không khi không gian bên ngoài này không phải là không gian chung == mô-đun, đây là trường hợp khi hàm là một hàm lồng nhau, trong Python 3.

Nhưng trong Python 2, theo như tôi biết, không có thuộc tính như vậy.

Vì vậy, khả năng duy nhất để làm những gì bạn muốn là:

1) sử dụng một đối tượng có thể thay đổi, như những người khác đã nói

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

kết quả

b before B() == 1
b == 10
b after B() == 10

.

Nota

Giải pháp của Cédric Julien có một nhược điểm:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

kết quả

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

Toàn cầu b sau khi thực hiệnA() đã được sửa đổi và nó có thể không được đánh dấu như vậy

Đó chỉ là trường hợp nếu có một đối tượng có định danh b trong không gian tên chung

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.