Câu hỏi Những hạn chế nào có đóng trong Python so với ngôn ngữ X bị đóng?


Trường hợp X là bất kỳ ngôn ngữ lập trình (C #, Javascript, Lisp, Perl, Ruby, Đề án, vv) mà hỗ trợ một số hương vị của bao đóng.

Một số hạn chế được đề cập trong Đóng cửa bằng Python (so với đóng cửa của Ruby), nhưng bài báo cũ và nhiều hạn chế không tồn tại trong Python hiện đại nữa.

Nhìn thấy một ví dụ mã cho một giới hạn cụ thể sẽ là tuyệt vời.

Câu hỏi liên quan:


44
2017-09-26 20:06


gốc




Các câu trả lời:


Giới hạn quan trọng nhất, hiện tại, là bạn không thể gán cho một biến phạm vi ngoài. Nói cách khác, đóng cửa là chỉ đọc:

>>> def outer(x): 
...     def inner_reads():
...         # Will return outer's 'x'.
...         return x
...     def inner_writes(y):
...         # Will assign to a local 'x', not the outer 'x'
...         x = y
...     def inner_error(y):
...         # Will produce an error: 'x' is local because of the assignment,
...         # but we use it before it is assigned to.
...         tmp = x
...         x = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
5
>>> inner_error(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner_error
UnboundLocalError: local variable 'x' referenced before assignment

Tên được chỉ định trong phạm vi cục bộ (hàm) luôn là địa phương, trừ khi được khai báo khác. Trong khi có khai báo 'toàn cục' để khai báo một biến toàn cục ngay cả khi nó được gán cho, thì không có khai báo nào cho các biến kèm theo. Trong Python 3.0, có (sẽ là) khai báo 'nonlocal' chỉ làm điều đó.

Bạn có thể làm việc xung quanh giới hạn này trong thời gian trung bình bằng cách sử dụng loại vùng chứa có thể thay đổi:

>>> def outer(x):
...     x = [x]
...     def inner_reads():
...         # Will return outer's x's first (and only) element.
...         return x[0]
...     def inner_writes(y):
...         # Will look up outer's x, then mutate it.      
...         x[0] = y
...     def inner_error(y):
...         # Will now work, because 'x' is not assigned to, just referenced.
...         tmp = x[0]
...         x[0] = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
10
>>> inner_error(15)
10
>>> inner_reads()
15

43
2017-09-26 20:19



nonlocal khi bạn cố gắng giải quyết vấn đề này. Một lớp bên trong với __call__ cũng có thể giải quyết nó (nhưng phiên bản có danh sách gọn gàng hơn). - jfs


Khó khăn duy nhất mà tôi đã thấy mọi người gặp phải với Python đặc biệt là khi họ cố gắng kết hợp các tính năng phi chức năng như chuyển nhượng lại với đóng cửa và ngạc nhiên khi điều này không hoạt động:

def outer ():
    x = 1
    def inner ():
        print x
        x = 2
    return inner
outer () ()

Thông thường chỉ cần chỉ ra rằng một hàm có các biến cục bộ riêng của nó là đủ để ngăn chặn sự silliness đó.


6
2017-09-26 20:11



UnboundLocalError - jfs
bạn cần một sự trở lại bên trong - Moe
@Moe cảm ơn; @JF chính xác. Đóng cửa hoạt động giống như bất kỳ chức năng khác, nhưng đối với một số lý do mọi người nghĩ rằng họ nên thực hiện phép thuật khi gán các biến. - John Millikin
Nhận xét của tôi quá lớn. Tôi đã đăng nó như một câu trả lời. - jfs


Một hạn chế (hoặc "giới hạn") của việc đóng cửa Python, so với các bao đóng Javascript, là nó không thể được sử dụng cho hiệu quả ẩn dữ liệu

Javascript

var mksecretmaker = function(){
    var secrets = [];
    var mksecret = function() {
        secrets.push(Math.random())
    }
    return mksecret
}
var secretmaker = mksecretmaker();
secretmaker(); secretmaker()
// privately generated secret number list
// is practically inaccessible

Python

import random
def mksecretmaker():
    secrets = []
    def mksecret():
        secrets.append(random.random())
    return mksecret

secretmaker = mksecretmaker()
secretmaker(); secretmaker()
# "secrets" are easily accessible,
# it's difficult to hide something in Python:
secretmaker.__closure__[0].cell_contents # -> e.g. [0.680752847190161, 0.9068475951742101]

6
2017-08-09 00:05



Ngay cả khi sử dụng ẩn dữ liệu OO tiêu chuẩn, bạn có thể truy cập vào một lớp Python '__private_variable bằng cách sử dụng obj._Class__private_variable. Điểm ẩn dữ liệu là cung cấp trừu tượng hóa, không phải bảo mật và nếu mã khách hàng của bạn bị bẻ cong xung quanh. - yati sagade


Cố định trong Python 3 thông qua nonlocal tuyên bố:

Các nonlocal tuyên bố làm cho các định danh được liệt kê để tham chiếu đến các biến bị ràng buộc trước đó trong phạm vi bao quanh gần nhất, trừ các globals. Điều này 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 tiên. Câu lệnh cho phép mã đóng gói để khôi phục 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).


4
2017-09-26 20:29



Nhận xét của tôi quá lớn. Tôi đã đăng nó như một câu trả lời. - jfs
Hmmm ... có lẽ biến không phải cục bộ được chia sẻ giữa tất cả các trường hợp đóng cửa? Làm cho nó giống như một biến tĩnh / lớp? - interstar


@ John Millikin

def outer():
    x = 1 # local to `outer()`

    def inner():
        x = 2     # local to `inner()`
        print(x)
        x = 3
        return x

    def inner2():
        nonlocal x
        print(x)  # local to `outer()`
        x = 4     # change `x`, it is not local to `inner2()`
        return x

    x = 5         # local to `outer()`
    return (inner, inner2)

for inner in outer():
    print(inner()) 

# -> 2 3 5 4

2
2017-09-26 20:25





nhận xét cho Câu trả lời của @Kevin Little để bao gồm ví dụ về mã

nonlocal không giải quyết hoàn toàn vấn đề này trên python3.0:

x = 0 # global x
def outer():
    x = 1 # local to `outer`
    def inner():
        global x
        x = 2 # change global
        print(x) 
        x = 3 # change global
        return x
    def inner2():
##        nonlocal x # can't use `nonlocal` here
        print(x)     # prints global
##        x = 4      # can't change `x` here
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 3 3

Mặt khác:

x = 0
def outer():
    x = 1 # local to `outer`
    def inner():
##        global x
        x = 2
        print(x) # local to `inner` 
        x = 3 
        return x
    def inner2():
        nonlocal x
        print(x)
        x = 4  # local to `outer`
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 5 4

nó hoạt động trên python3.1-3.3


2
2017-09-26 20:47



Tôi biết đây là một câu trả lời thực sự cũ, nhưng nó là đồng bằng sai cho python3.2 - bạn hoàn toàn có thể sử dụng nonlocal trong ví dụ đầu tiên và nhận được kết quả mong đợi. Tôi không có một xây dựng python3.0 để kiểm tra nếu nó đã được khác nhau trở lại sau đó, nhưng nếu nó đã được tôi sẽ xem xét nó một lỗi, như PEP không nói nó nên khác. Hạn chế duy nhất (hiển nhiên) là bạn không thể sử dụng global x và nonlocal x trong cùng phạm vi. - l4mpi
@ l4mpi: Nó tạo ra SyntaxError: no binding for nonlocal 'x' found trên python3.0 nếu bạn bỏ ghi chú nonlocal. Nhưng nó hoạt động trên python3.1-3.3 - jfs
Ah, cảm ơn câu trả lời. Điều này dường như thậm chí còn giống như một lỗi bây giờ, mặc dù tôi đã không thể tìm thấy một báo cáo lỗi hoặc một lưu ý trong các ghi chú phát hành 3,1 ... - l4mpi


Cách giải quyết tốt hơn cho đến khi 3.0 là bao gồm biến làm tham số mặc định trong định nghĩa hàm được đính kèm:

def f ()
    x = 5
    def g (y, z, x = x):
        x = x + 1

-1
2018-06-12 21:33



Giá trị của bên ngoài x sẽ luôn là 5. - jfs