Quali limiti hanno le chiusure in Python rispetto alle chiusure in linguaggio X?

StackOverflow https://stackoverflow.com/questions/141642

  •  02-07-2019
  •  | 
  •  

Domanda

Dove X è un linguaggio di programmazione (C #, Javascript, Lisp, Perl, Ruby, Scheme, ecc.) che supporta un certo tipo di chiusure.

Alcune limitazioni sono menzionate nelle Chiusure in Python (rispetto alle chiusure di Ruby) , ma l'articolo è vecchio e non esistono più molte limitazioni nel moderno Python.

Vedere un esempio di codice per una limitazione concreta sarebbe fantastico.

Domande correlate :

È stato utile?

Soluzione

La limitazione più importante, attualmente, è che non è possibile assegnare a una variabile di ambito esterno. In altre parole, le chiusure sono di sola lettura:

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

Un nome a cui viene assegnato in un ambito locale (una funzione) è sempre locale, se non diversamente indicato. Mentre esiste una dichiarazione "globale" per dichiarare una variabile globale anche quando è assegnata, non esiste ancora una tale dichiarazione per le variabili racchiuse. In Python 3.0 esiste (sarà) la dichiarazione "non locale" che fa proprio questo.

Puoi aggirare questa limitazione nel frattempo usando un tipo di contenitore mutabile:

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

Altri suggerimenti

L'unica difficoltà che ho visto incontrare in particolare le persone di Python è quando provano a mescolare funzionalità non funzionali come la riassegnazione variabile con chiusure e sono sorpresi quando non funziona:

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

Di solito basta sottolineare che una funzione ha le sue variabili locali è sufficiente per scoraggiare tale stupidità.

Una limitazione (o "limitazione") delle chiusure Python, rispetto alle chiusure Javascript, è che non può essere utilizzato per nascondere i dati efficace

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]

Risolto in Python 3 tramite nonlocal dichiarazione:

  

L'istruzione nonlocal fa sì che gli identificatori elencati facciano riferimento a variabili precedentemente associate nell'ambito compreso più vicino esclusi i globali. Ciò è importante perché il comportamento predefinito per l'associazione è la ricerca prima nello spazio dei nomi locale. L'istruzione consente al codice incapsulato di ricollegare le variabili al di fuori dell'ambito locale oltre all'ambito globale (modulo).

@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

commento per @Kevin Little's answer per includere l'esempio di codice

nonlocal non risolve completamente questo problema su 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

D'altra parte:

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

funziona su python3.1-3.3

La soluzione migliore fino alla 3.0 è quella di includere la variabile come parametro predefinito nella definizione della funzione allegata:

def f()
    x = 5
    def g(y, z, x=x):
        x = x + 1
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top