¿Qué tan malo es el sombreado de nombres definidos en ámbitos externos?

208

Acabo de cambiar a Pycharm y estoy muy contento con todas las advertencias y sugerencias que me brinda para mejorar mi código. Excepto por este que no entiendo:

This inspection detects shadowing names defined in outer scopes.

Sé que es una mala práctica acceder a la variable desde el alcance externo, pero ¿cuál es el problema con el sombreado del alcance externo?

Aquí hay un ejemplo, donde Pycharm me da el mensaje de advertencia:

data = [4, 5, 6]

def print_data(data): # <-- Warning: "Shadows 'data' from outer scope
    print data

print_data(data)
Framester
fuente
1
También busqué la cadena "Esta inspección detecta ..." pero no encontré nada en la ayuda en línea de pycharm
Framester
1
Para desactivar este mensaje en PyCharm: <Ctrl> + <Alt> + s (configuración), Editor , Inspecciones , " Sombrear nombres de ámbitos externos ". Desmarcar
ChaimG

Respuestas:

222

No es gran cosa en su fragmento anterior, pero imagine una función con algunos argumentos más y bastantes líneas de código más. Luego decide cambiar el nombre de su dataargumento como yaddapero se pierde uno de los lugares donde se usa en el cuerpo de la función ... Ahoradata refiere a lo global, y comienza a tener un comportamiento extraño, donde tendría un aspecto mucho más obvio NameErrorsi no lo hiciera tener un nombre globaldata .

Recuerde también que en Python todo es un objeto (incluidos módulos, clases y funciones), por lo que no hay espacios de nombres distintos para funciones, módulos o clases. Otro escenario es que importa la funciónfoo en la parte superior de su módulo y la usa en algún lugar de su cuerpo de función. Luego agrega un nuevo argumento a su función y lo nombra - mala suerte -foo .

Finalmente, las funciones y los tipos incorporados también viven en el mismo espacio de nombres y se pueden sombrear de la misma manera.

Nada de esto es un gran problema si tiene funciones cortas, un buen nombre y una cobertura de prueba de unidad decente, pero bueno, a veces tiene que mantener un código menos que perfecto y ser advertido sobre tales posibles problemas podría ayudar.

bruno desthuilliers
fuente
21
Afortunadamente, PyCharm (como lo utiliza el OP) tiene una operación de cambio de nombre muy agradable que cambia el nombre de la variable en todas partes en el mismo ámbito, lo que hace que los errores de cambio de nombre sean menos probables.
wojtow
Además de la operación de cambio de nombre de PyCharm, me encantaría tener resaltados de sintaxis especiales para las variables que se refieren al alcance externo. Estos dos deberían hacer que este juego de resolución de sombras que consume mucho tiempo sea irrelevante.
Leo
Nota al margen: puede usar la nonlocalpalabra clave para hacer que la referencia de puntaje externo (como en los cierres) sea explícita. Tenga en cuenta que esto es diferente del sombreado, ya que explícitamente no sombrea las variables del exterior.
Felix D.
149

La respuesta más votada y aceptada actualmente y la mayoría de las respuestas aquí pierden el punto.

No importa qué tan larga sea su función o cómo nombre su variable descriptivamente (con la esperanza de minimizar la posibilidad de una posible colisión de nombres).

El hecho de que la variable local de su función o su parámetro comparta un nombre en el ámbito global es completamente irrelevante. Y de hecho, no importa cuán cuidadosamente elija su nombre de variable local, su función nunca podrá prever "si mi nombre es genialyadda también se usará como una variable global en el futuro". ¿La solución? ¡Simplemente no te preocupes por eso! La mentalidad correcta es diseñar su función para consumir información de y solo de sus parámetros en la firma , de esa manera no necesita preocuparse por lo que está (o estará) en el ámbito global, y luego el sombreado no se convierte en un problema en absoluto.

En otras palabras, el problema de sombreado solo importa cuando su función necesita usar la misma variable local Y la variable global. Pero debe evitar dicho diseño en primer lugar. El código del OP NO tiene realmente ese problema de diseño. Es solo que PyCharm no es lo suficientemente inteligente y da una advertencia por si acaso. Entonces, solo para hacer feliz a PyCharm y también para limpiar nuestro código, vea esta solución citando la respuesta de silyevsk para eliminar completamente la variable global.

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

Esta es la forma correcta de "resolver" este problema, arreglando / eliminando su cosa global, no ajustando su función local actual.

RayLuo
fuente
11
Bueno, claro, en un mundo perfecto, siempre haces un error tipográfico u olvidas uno de tus reemplazos de búsqueda cuando cambias el parámetro, pero ocurren errores y eso es lo que PyCharm dice: "Advertencia: nada es técnicamente un error, pero esto podría convertirse fácilmente en un problema "
dwanderson
1
@dwanderson La situación que mencionó no es nada nuevo, se describe claramente en la respuesta elegida actualmente. Sin embargo, lo que intento señalar es que debemos evitar la variable global, no evitar el sombreado de la variable global. Este último pierde el punto. ¿Consíguelo? ¿Entendido?
RayLuo
44
Estoy totalmente de acuerdo en el hecho de que las funciones deben ser lo más "puras" posibles, pero se pierden totalmente los dos puntos importantes: no hay forma de restringir que Python busque un nombre en los ámbitos adjuntos si no está definido localmente, y todo (módulos , funciones, clases, etc.) es un objeto y vive en el mismo espacio de nombres que cualquier otra "variable". En su fragmento anterior, print_dataES una variable global. Piénsalo ...
bruno desthuilliers
2
Terminé en este hilo porque estoy usando funciones definidas en funciones, para hacer que la función externa sea más legible sin saturar el espacio de nombres global o usar archivos separados con mucha mano. Este ejemplo aquí no se aplica a ese caso general, de las variables no locales no globales que se sombrean.
micseydel
2
De acuerdo. El problema aquí es el alcance de Python. El acceso no explícito a objetos fuera del alcance actual está pidiendo problemas. ¡Quién querría eso! Una pena porque, de lo contrario, Python es un lenguaje bastante bien pensado (a pesar de una ambigüedad similar en la denominación de módulos).
CodeCabbie
24

Una buena solución en algunos casos puede ser mover el código vars + a otra función:

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()
silyevsk
fuente
Si. Creo que una buena idea es capaz de manejar variables locales y variables globales refactorizando. Su punta realmente ayuda a eliminar estos riesgos potenciales para ide primitiva
stanleyxu2005
5

Depende de cuánto dura la función. Cuanto más larga sea la función, más posibilidades hay de que alguien que la modifique en el futuro escriba datapensando que significa global. De hecho, significa local, pero debido a que la función es tan larga, no les resulta obvio que existe un local con ese nombre.

Para su función de ejemplo, creo que sombrear lo global no está nada mal.

Steve Jessop
fuente
5

Hacer esto:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

fuente
3
data = [4, 5, 6] #your global variable

def print_data(data): # <-- Pass in a parameter called "data"
    print data  # <-- Note: You can access global variable inside your function, BUT for now, which is which? the parameter or the global variable? Confused, huh?

print_data(data)
JoeC
fuente
47
Por mi parte, no estoy confundido. Es bastante obvio el parámetro.
2
@delnan Puede que no se confunda en este ejemplo trivial, pero ¿qué pasa si otras funciones definidas cerca usan el global data, todo dentro de unos cientos de líneas de código?
John Colanduoni
13
@HevyLight No necesito mirar otras funciones cercanas. Solo miro esta función y puedo ver que dataes un nombre local en esta función, por lo que ni siquiera me molesto en verificar / recordar si existe un nombre global del mismo nombre , y mucho menos lo que contiene.
44
No creo que este razonamiento sea válido, únicamente porque para usar un global, necesitaría definir "datos globales" dentro de la función. De lo contrario, lo global no es accesible.
CodyF
1
@CodyF False: si no define, pero solo trata de usar data, busca en los ámbitos hasta que encuentra uno, por lo que encuentra el global data. data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson
3

Me gusta ver una marca verde en la esquina superior derecha en pycharm. Añado los nombres de las variables con un guión bajo solo para borrar esta advertencia y poder concentrarme en las advertencias importantes.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)
Baz
fuente
2

Parece que es un patrón de código 100% pytest

ver:

https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions

Tuve el mismo problema, por eso encontré esta publicación;)

# ./tests/test_twitter1.py
import os
import pytest

from mylib import db
# ...

@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

Y avisará con This inspection detects shadowing names defined in outer scopes.

Para arreglar eso solo mueva su twitterdispositivo a./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

Y quitar el twitteraccesorio como en./tests/test_twitter2.py

# ./tests/test_twitter2.py
import os
import pytest

from mylib import db
# ...

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

Esto hará feliz a QA, Pycharm y todos

Andrei.Danciuc
fuente