Helpex - Trao đổi & giúp đỡ Đăng nhập
130

Tôi đã đọc gần như tất cả các câu hỏi khác về chủ đề này, nhưng mã của tôi vẫn không hoạt động.

Tôi nghĩ rằng tôi đang thiếu một cái gì đó về phạm vi biến python.

Đây là mã của tôi:

PRICE_RANGES = {
                64:(25, 0.35),
                32:(13, 0.40),
                16:(7, 0.45),
                8:(4, 0.5)
                }

def get_order_total(quantity):
    global PRICE_RANGES
    _total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
        try:
            key = _i.next()
            if quantity % key != quantity:
                _total += PRICE_RANGES[key][0]
            return recurse(_i) 
        except StopIteration:
            return (key, quantity % key)

    res = recurse(_i)

Va tôi lây

"tên toàn cầu '_total' không được xác định"

Tôi biết vấn đề là ở _totalbài tập, nhưng tôi không thể hiểu tại sao. Không nên recurse()có quyền truy cập vào các biến của hàm cha?

Ai đó có thể giải thích cho tôi những gì tôi đang thiếu về phạm vi biến python không?

130 hữu ích 2 bình luận 94k xem chia sẻ
10 trả lời 10
67

Khi tôi chạy mã của bạn, tôi gặp lỗi này:

UnboundLocalError: local variable '_total' referenced before assignment

Sự cố này là do dòng này gây ra:

_total += PRICE_RANGES[key][0]

Tài liệu về Phạm vi và Không gian tên cho biết điều này:

Một điểm đặc biệt của Python là - nếu không có globalcâu lệnh nào có hiệu lực - các phép gán cho tên luôn đi vào phạm vi trong cùng . Bài tập không sao chép dữ liệu - chúng chỉ liên kết tên với các đối tượng.

Vì vậy, vì dòng đang nói một cách hiệu quả:

_total = _total + PRICE_RANGES[key][0]

nó tạo ra _totaltrong không gian tên của recurse(). Kể từ _totalđó là mới và chưa được chỉ định, bạn không thể sử dụng nó trong phần bổ sung.

67 hữu ích 0 bình luận chia sẻ
213

Trong Python 3, bạn có thể sử dụng nonlocalcâu lệnh để truy cập các phạm vi không cục bộ, không toàn cục.

Câu nonlocallệnh gây ra một định nghĩa biến để liên kết với một biến đã tạo trước đó trong phạm vi gần nhất. Dưới đây là một số ví dụ để minh họa:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):
        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

Ví dụ trên sẽ không thành công với lỗi: UnboundLocalError: local variable 'total' referenced before assignment

Sử dụng, nonlocalchúng tôi có thể làm cho mã hoạt động:

def sum_list_items(_list):
    total = 0

    def do_the_sum(_list):

        # Define the total variable as non-local, causing it to bind
        # to the nearest non-global variable also called total.
        nonlocal total

        for i in _list:
            total += i

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

Nhưng "gần nhất" có nghĩa là gì? Đây là một ví dụ khác:

def sum_list_items(_list):

    total = 0

    def do_the_sum(_list):

        # The nonlocal total binds to this variable.
        total = 0

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

Trong ví dụ trên, totalsẽ liên kết với biến được xác định bên trong do_the_sumhàm chứ không phải biến bên ngoài được xác định trong sum_list_itemshàm, vì vậy mã sẽ trả về 0.

def sum_list_items(_list):

    # The nonlocal total binds to this variable.
    total = 0

    def do_the_sum(_list):

        def do_core_computations(_list):

            # Define the total variable as non-local, causing it to bind
            # to the nearest non-global variable also called total.
            nonlocal total

            for i in _list:
                total += i

        do_core_computations(_list)

    do_the_sum(_list)

    return total

sum_list_items([1, 2, 3])

Trong ví dụ trên, phép gán phi địa phương đi qua hai cấp trước khi nó định vị totalbiến là cục bộ sum_list_items.

213 hữu ích 5 bình luận chia sẻ
177

Đây là một minh họa về bản chất của câu trả lời của David.

def outer():
    a = 0
    b = 1

    def inner():
        print a
        print b
        #b = 4

    inner()

outer()

Với tuyên bố được b = 4nhận xét ra, mã này xuất ra 0 1, đúng như những gì bạn mong đợi.

Nhưng nếu bạn bỏ ghi chú dòng đó, trên dòng đó print b, bạn sẽ gặp lỗi

UnboundLocalError: local variable 'b' referenced before assignment

Có vẻ bí ẩn rằng sự hiện diện của b = 4có thể bằng cách nào đó làm cho bbiến mất trên các dòng trước nó. Nhưng văn bản mà David trích dẫn giải thích tại sao: trong quá trình phân tích tĩnh, trình thông dịch xác định rằng b được gán cho trong innervà do đó nó là một biến cục bộ của inner. Dòng in cố gắng in bphạm vi bên trong đó trước khi nó được gán.

177 hữu ích 4 bình luận chia sẻ
32

Thay vì khai báo một đối tượng hoặc bản đồ hoặc mảng đặc biệt, người ta cũng có thể sử dụng một thuộc tính hàm. Điều này làm cho phạm vi của biến thực sự rõ ràng.

def sumsquares(x,y):
  def addsquare(n):
    sumsquares.total += n*n

  sumsquares.total = 0
  addsquare(x)
  addsquare(y)
  return sumsquares.total

Tất nhiên thuộc tính này thuộc về hàm (defintion), và không thuộc về lời gọi hàm. Vì vậy, người ta phải lưu tâm đến luồng và đệ quy.

32 hữu ích 2 bình luận chia sẻ
17

Đây là một biến thể của giải pháp redman, nhưng sử dụng không gian tên riêng thay vì một mảng để đóng gói biến:

def foo():
    class local:
        counter = 0
    def bar():
        print(local.counter)
        local.counter += 1
    bar()
    bar()
    bar()

foo()
foo()

Tôi không chắc liệu việc sử dụng một đối tượng lớp theo cách này có bị coi là một vụ hack xấu xí hay một kỹ thuật viết mã thích hợp trong cộng đồng python hay không, nhưng nó hoạt động tốt trong python 2.x và 3.x (đã thử nghiệm với 2.7.3 và 3.2.3 ). Tôi cũng không chắc chắn về hiệu quả thời gian chạy của giải pháp này.

17 hữu ích 1 bình luận chia sẻ
8

Bạn có thể đã có câu trả lời cho câu hỏi của mình. Nhưng tôi muốn chỉ ra một cách mà tôi thường dùng để giải quyết vấn đề này và đó là bằng cách sử dụng danh sách. Ví dụ, nếu tôi muốn làm điều này:

X=0
While X<20:
    Do something. ..
    X+=1

Thay vào đó tôi sẽ làm điều này:

X=[0]
While X<20:
   Do something....
   X[0]+=1

Bằng cách này, X không bao giờ là một biến cục bộ

8 hữu ích 4 bình luận chia sẻ
4

Mặc dù tôi đã từng sử dụng cách tiếp cận dựa trên danh sách của @ redman, nhưng nó không tối ưu về khả năng đọc.

Đây là cách tiếp cận của @Hans đã được sửa đổi, ngoại trừ việc tôi sử dụng một thuộc tính của hàm bên trong, thay vì bên ngoài. Điều này sẽ tương thích hơn với đệ quy và thậm chí có thể đa luồng:

def outer(recurse=2):
    if 0 == recurse:
        return

    def inner():
        inner.attribute += 1

    inner.attribute = 0
    inner()
    inner()
    outer(recurse-1)
    inner()
    print "inner.attribute =", inner.attribute

outer()
outer()

Bản in này:

inner.attribute = 3
inner.attribute = 3
inner.attribute = 3
inner.attribute = 3

Nếu tôi s/inner.attribute/outer.attribute/g, chúng tôi nhận được:

outer.attribute = 3
outer.attribute = 4
outer.attribute = 3
outer.attribute = 4

Vì vậy, thực sự, có vẻ tốt hơn nếu biến chúng thành thuộc tính của hàm bên trong.

Ngoài ra, nó có vẻ hợp lý về mặt dễ đọc: bởi vì khi đó biến liên quan đến khái niệm với hàm bên trong, và ký hiệu này nhắc nhở người đọc rằng biến được chia sẻ giữa phạm vi của hàm bên trong và hàm bên ngoài. Một nhược điểm nhỏ đối với khả năng đọc là inner.attributechỉ có thể được đặt theo cú pháp sau def inner(): ....

4 hữu ích 0 bình luận chia sẻ
3

Từ quan điểm triết học hơn, một câu trả lời có thể là "nếu bạn đang gặp vấn đề về không gian tên, hãy tạo cho nó một không gian tên rất riêng!"

Cung cấp nó trong lớp riêng của nó không chỉ cho phép bạn gói gọn vấn đề mà còn làm cho việc kiểm tra dễ dàng hơn, loại bỏ những khối cầu khó chịu đó và giảm nhu cầu phân chia các biến xung quanh giữa các hàm cấp cao nhất khác nhau (chắc chắn sẽ có nhiều hơn chỉ get_order_total).

Giữ nguyên mã OP để tập trung vào thay đổi thiết yếu,

class Order(object):
  PRICE_RANGES = {
                  64:(25, 0.35),
                  32:(13, 0.40),
                  16:(7, 0.45),
                  8:(4, 0.5)
                  }


  def __init__(self):
    self._total = None

  def get_order_total(self, quantity):
      self._total = 0
      _i = self.PRICE_RANGES.iterkeys()
      def recurse(_i):
          try:
              key = _i.next()
              if quantity % key != quantity:
                  self._total += self.PRICE_RANGES[key][0]
              return recurse(_i) 
          except StopIteration:
              return (key, quantity % key)

      res = recurse(_i)

#order = Order()
#order.get_order_total(100)

Là một PS, một bản hack là một biến thể trong danh sách ý tưởng trong một câu trả lời khác, nhưng có lẽ rõ ràng hơn,

def outer():
  order = {'total': 0}

  def inner():
    order['total'] += 42

  inner()

  return order['total']

print outer()
3 hữu ích 0 bình luận chia sẻ
0
>>> def get_order_total(quantity):
    global PRICE_RANGES

    total = 0
    _i = PRICE_RANGES.iterkeys()
    def recurse(_i):
    print locals()
    print globals()
        try:
            key = _i.next()
            if quantity % key != quantity:
                total += PRICE_RANGES[key][0]
            return recurse(_i)
        except StopIteration:
            return (key, quantity % key)
    print 'main function', locals(), globals()

    res = recurse(_i)


>>> get_order_total(20)
main function {'total': 0, 'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20} {'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}
{'recurse': <function recurse at 0xb76baed4>, '_i': <dictionary-keyiterator object at 0xb6473e64>, 'quantity': 20}
{'__builtins__': <module '__builtin__' (built-in)>, 'PRICE_RANGES': {64: (25, 0.34999999999999998), 32: (13, 0.40000000000000002), 16: (7, 0.45000000000000001), 8: (4, 0.5)}, '__package__': None, 's': <function s at 0xb646adf4>, 'get_order_total': <function get_order_total at 0xb646ae64>, '__name__': '__main__', '__doc__': None}

Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    get_order_total(20)
  File "<pyshell#31>", line 18, in get_order_total
    res = recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 13, in recurse
    return recurse(_i)
  File "<pyshell#31>", line 12, in recurse
    total += PRICE_RANGES[key][0]
UnboundLocalError: local variable 'total' referenced before assignment
>>> 

như bạn thấy, tổng số nằm trong phạm vi cục bộ của hàm chính, nhưng nó không nằm trong phạm vi cục bộ của đệ quy (rõ ràng) nhưng cũng không nằm trong phạm vi toàn cục, vì nó chỉ được định nghĩa trong phạm vi cục bộ của get_order_total

0 hữu ích 0 bình luận chia sẻ
-1

Con đường của tôi xung quanh ...

def outer():

class Cont(object):
    var1 = None
    @classmethod
    def inner(cls, arg):
        cls.var1 = arg


Cont.var1 = "Before"
print Cont.var1
Cont.inner("After")
print Cont.var1

outer()
-1 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ python variables scope , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading