¿Usar 'importar módulo' o 'importar módulo'?


411

He tratado de encontrar una guía completa sobre si es mejor usar import moduleo from module import? Acabo de comenzar con Python y estoy tratando de comenzar con las mejores prácticas en mente.

Básicamente, esperaba que alguien pudiera compartir sus experiencias, ¿qué preferencias tienen otros desarrolladores y cuál es la mejor manera de evitar problemas en el futuro ?


55
Solo quería hacerle saber que la respuesta seleccionada es incorrecta. Establece que la diferencia es subjetiva mientras que hay una diferencia. Esto podría conducir a errores difíciles de detectar. Ver la respuesta de Michael Ray Lovetts.
Mayou36


2
Hay un infierno de una diferencia entre la importación de identificadores específicos nombrados 'from module import X,Y,Zvs'from module import * . Este último contamina su espacio de nombres y puede dar resultados impredecibles dependiendo de lo que esté sucediendo en el módulo. Peor aún lo está haciendo from module import *con múltiples módulos.
smci

Respuestas:


474

La diferencia entre import moduley from module import fooes principalmente subjetiva. Elija el que más le guste y sea coherente en su uso. Aquí hay algunos puntos para ayudarlo a decidir.

import module

  • Pros:
    • Menos mantenimiento de sus importdeclaraciones. No es necesario agregar ninguna importación adicional para comenzar a usar otro elemento del módulo
  • Contras:
    • Escribir module.foosu código puede ser tedioso y redundante (el tedio se puede minimizar usando y import module as moluego escribiendo mo.foo)

from module import foo

  • Pros:
    • Menos mecanografía para usar foo
    • Más control sobre a qué elementos de un módulo se puede acceder
  • Contras:
    • Para usar un nuevo elemento del módulo, debe actualizar su importestado de cuenta
    • Pierdes contexto sobre foo. Por ejemplo, no está tan claro qué ceil()hace en comparación conmath.ceil()

Cualquiera de los métodos es aceptable, pero no lo use from module import *.

Para cualquier conjunto grande de código razonable, si es import *probable que lo esté cementando en el módulo, no podrá eliminarlo. Esto se debe a que es difícil determinar qué elementos usados ​​en el código provienen del 'módulo', lo que facilita llegar al punto en el que cree que ya no lo usa, importpero es extremadamente difícil estar seguro.


66
+1 para desalentar el uso de "from module import *", simplemente abarrota el espacio de nombres.
Christian Witts el

22
abarrotar el espacio de nombres no es la parte más problemática de "import *", es la reducción de la legibilidad: cualquier conflicto de nombres se mostrará en las pruebas (de unidad). Pero todos los nombres que use del módulo importado estarán desnudos, sin ninguna pista de dónde provienen. Absolutamente detesto "importar *".
Jürgen A. Erhard

21
¿No dice el Zen de Python que explícito es mejor que implícito?
Antony Koch

8
from module import *puede ser particularmente útil, si se utiliza como: if(windows):\n\t from module_win import * \n else: \n\t from module_lin import *. Entonces, su módulo padre puede contener nombres de funciones independientes del sistema operativo, si los nombres de funciones en module_lin y module_win tienen los mismos nombres. Es como heredar condicionalmente cualquier clase.
anishsane

19
@anishsane. Hay otra forma de hacerlo. importar module_win como algo. Entonces siempre use something.method_name ()
Vinay

163

Hay otro detalle aquí, no mencionado, relacionado con la escritura en un módulo. De acuerdo, esto puede no ser muy común, pero lo he necesitado de vez en cuando.

Debido a la forma en que las referencias y el enlace de nombres funcionan en Python, si desea actualizar algún símbolo en un módulo, diga foo.bar, desde fuera de ese módulo, y haga que otro código de importación "vea" ese cambio, debe importar foo a de cierta manera. Por ejemplo:

módulo foo:

bar = "apples"

módulo a:

import foo
foo.bar = "oranges"   # update bar inside foo module object

módulo b:

import foo           
print foo.bar        # if executed after a's "foo.bar" assignment, will print "oranges"

Sin embargo, si importa nombres de símbolos en lugar de nombres de módulos, esto no funcionará.

Por ejemplo, si hago esto en el módulo a:

from foo import bar
bar = "oranges"

Ningún código fuera de una barra verá "naranjas" porque mi configuración de barra simplemente afectó el nombre "barra" dentro del módulo a, no "alcanzó" el objeto del módulo foo y actualizó su "barra".


Con ese último ejemplo, ¿puedes llamar a 'foo.bar = "orange"' para actualizar 'bar' dentro de 'foo'?
velocirabbit

44
No, en el último ejemplo, el nombre 'foo' es desconocido
Ghislain Leveque

31
Esta respuesta proporciona la "verdadera" respuesta a la pregunta: ¿cuál es la diferencia entre las dos variantes de importación
Mayou36

3
Escribió un fragmento para demostrar que esta respuesta es absolutamente correcta, pero ¿cuál es la razón detrás de esto?
huangbeidu

¿Creo que lo que está diciendo es importar nombres de símbolos para tener variables locales pero importar nombres de módulos para tener variables globales?
WinEunuuchs2Unix

79

Aunque muchas personas ya explicaron sobre importvs import from, quiero tratar de explicar un poco más sobre lo que sucede debajo del capó y dónde están todos los lugares donde cambia.


import foo:

Importa fooy crea una referencia a ese módulo en el espacio de nombres actual. Luego debe definir la ruta completa del módulo para acceder a un atributo o método particular desde el interior del módulo.

Por ejemplo, foo.barpero nobar

from foo import bar:

Importa fooy crea referencias a todos los miembros enumerados ( bar). No establece la variable foo.

Por ejemplo, barpero no bazofoo.baz

from foo import *:

Importa fooy crea referencias a todos los objetos públicos definidos por ese módulo en el espacio de nombres actual (todo lo que aparece en __all__si __all__existe, de lo contrario todo lo que no comienza con _). No establece la variable foo.

Por ejemplo bary bazpero no _quxo foo._qux.


Ahora veamos cuando lo hacemos import X.Y:

>>> import sys
>>> import os.path

Consultar sys.modulescon nombre osy os.path:

>>> sys.modules['os']
<module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
>>> sys.modules['os.path']
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>

De verificación globals()y locals()de espacio de nombres predice con osy os.path:

 >>> globals()['os']
<module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
>>> locals()['os']
<module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
>>> globals()['os.path']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'os.path'
>>>

Del ejemplo anterior encontramos que solo osse inserta en el espacio de nombres local y global. Entonces, deberíamos poder usar:

 >>> os
 <module 'os' from
  '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
 >>> os.path
 <module 'posixpath' from
 '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
 >>>

Pero no path.

>>> path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'path' is not defined
>>>

Una vez que elimine el osespacio de nombres locals (), no podrá acceder ostan bien como os.pathexista en sys.modules:

>>> del locals()['os']
>>> os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> os.path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>>

Ahora hablemos de import from:

from:

>>> import sys
>>> from os import path

Consulte sys.modulescon osy os.path:

>>> sys.modules['os']
<module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>
>>> sys.modules['os.path']
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>

Descubrimos que en sys.modulesencontramos lo mismo que antes usandoimport name

OK, cheque de Let cómo se ve en locals()e globals()espacio de nombres predice:

>>> globals()['path']
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
>>> locals()['path']
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
>>> globals()['os']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'os'
>>>

Puede acceder usando el nombre, pathno por os.path:

>>> path
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
>>> os.path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>>

Eliminemos 'ruta' de locals():

>>> del locals()['path']
>>> path
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'path' is not defined
>>>

Un último ejemplo con un alias:

>>> from os import path as HELL_BOY
>>> locals()['HELL_BOY']
<module 'posixpath' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
>>> globals()['HELL_BOY']
<module 'posixpath' from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.pyc'>
>>>

Y no hay ruta definida:

>>> globals()['path']
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
KeyError: 'path'
>>>

8
Si bien esto es detallado, esta es realmente la mejor respuesta en la lista para una pregunta bastante compleja. Proporciona un código real para ayudar a explicar las sutilezas "ocultas", que son más importantes que el estilo, para este problema en particular. ¡Ojalá pudiera votarlo más de una vez!
Mike Williamson

¿El uso as SYMBOLcambia la forma en que funciona esta respuesta?
Maximilian Burszley

40

Ambas formas son compatibles por una razón: hay momentos en que uno es más apropiado que el otro.

  • import module: agradable cuando está utilizando muchos bits del módulo. El inconveniente es que deberá calificar cada referencia con el nombre del módulo.

  • from module import ...: es bueno que los elementos importados se puedan usar directamente sin el prefijo del nombre del módulo. El inconveniente es que debe enumerar cada cosa que usa, y que no está claro en el código de dónde proviene algo.

Cuál usar depende de qué hace que el código sea claro y legible, y tiene más que poco que ver con las preferencias personales. Me inclino import modulegeneralmente porque en el código está muy claro de dónde proviene un objeto o función. Utilizo from module import ...cuando estoy usando algún objeto / función de un montón en el código.


1
¿Hay alguna manera de usar from M import Xy obtener el beneficio de usar los calificadores de alguna manera? Parece que podrías obtener lo mejor de ambos mundos si aún pudieras hacerlo M.Xdespués de esa importación.
artrópodo

@artgropod: un poco. Puedes hacer class m: from something.too.long import x, y, z. Aunque realmente no lo recomendaría.
Mentira Ryan

35

Yo personalmente siempre uso

from package.subpackage.subsubpackage import module

y luego acceder a todo como

module.function
module.modulevar

La razón es que, al mismo tiempo, tiene una invocación corta y define claramente el espacio de nombres del módulo de cada rutina, algo que es muy útil si tiene que buscar el uso de un módulo determinado en su fuente.

No es necesario decir que no use la importación *, ya que contamina su espacio de nombres y no le dice de dónde proviene una función determinada (de qué módulo)

Por supuesto, puede tener problemas si tiene el mismo nombre de módulo para dos módulos diferentes en dos paquetes diferentes, como

from package1.subpackage import module
from package2.subpackage import module

en este caso, por supuesto, se encuentra con problemas, pero hay una fuerte pista de que el diseño de su paquete es defectuoso y debe repensarlo.


10
En el último caso, siempre puede usar: import pkgN.sub.module como modN que le proporciona nombres distintos para cada módulo. También puede usar el patrón 'import modulename as mod1' para acortar un nombre largo o para cambiar entre implementaciones de la misma API (por ejemplo, módulos de API DB) con un solo cambio de nombre.
Jeff Shannon el

15
import module

Es mejor cuando usará muchas funciones del módulo.

from module import function

Es mejor cuando desea evitar contaminar el espacio de nombres global con todas las funciones y tipos de un módulo cuando solo lo necesita function.


77
¿Seguramente lo único en el espacio de nombres global si hace 'importar módulo' es 'módulo'? Solo contaminará el espacio de nombres si hace 'from ... import *'.
John Fouhy el

10

Acabo de descubrir una sutil diferencia más entre estos dos métodos.

Si el módulo foousa una importación siguiente:

from itertools import count

Entonces, el módulo barpuede usarse por error countcomo si estuviera definido en foo, no en itertools:

import foo
foo.count()

Si fooutiliza:

import itertools

el error aún es posible, pero es menos probable que se cometa. barnecesita:

import foo
foo.itertools.count()

Esto me causó algunos problemas. Tenía un módulo que por error importó una excepción de un módulo que no lo definió, solo lo importó de otro módulo (usando from module import SomeException). Cuando la importación ya no era necesaria y eliminada, el módulo infractor se rompió.


10

Aquí hay otra diferencia no mencionada. Esto se copia literalmente de http://docs.python.org/2/tutorial/modules.html

Tenga en cuenta que al usar

from package import item

el elemento puede ser un submódulo (o subpaquete) del paquete o algún otro nombre definido en el paquete, como una función, clase o variable. La declaración de importación primero prueba si el artículo está definido en el paquete; si no, supone que es un módulo e intenta cargarlo. Si no lo encuentra, se genera una excepción ImportError.

Por el contrario, cuando se usa la sintaxis como

import item.subitem.subsubitem

cada artículo, excepto el último, debe ser un paquete; El último elemento puede ser un módulo o un paquete, pero no puede ser una clase, función o variable definida en el elemento anterior.


Otra cosa que noté fue que si el elemento también es un submódulo dentro del paquete, entonces "desde el paquete de importación del elemento" funciona pero "importar paquete" package.item.subitem = ... no funciona con un init .py vacío del paquete, a menos que tener "elemento de importación" en el archivo de inicio del paquete.
Amitoz Dandiana

6

Como también soy un principiante, trataré de explicar esto de una manera simple: en Python, tenemos tres tipos de importdeclaraciones que son:

1. Importaciones genéricas:

import math

este tipo de importación es mi favorito personal, el único inconveniente de esta técnica de importación es que si necesita usar la función de cualquier módulo, debe usar la siguiente sintaxis:

math.sqrt(4)

por supuesto, aumenta el esfuerzo de escritura, pero como principiante, le ayudará a realizar un seguimiento del módulo y la función asociada con él (se recomienda un buen editor de texto que reduzca el esfuerzo de escritura de manera significativa).

El esfuerzo de escritura se puede reducir aún más mediante el uso de esta declaración de importación:

import math as m

ahora, en lugar de usar math.sqrt()puedes usar m.sqrt().

2. Importaciones de funciones:

from math import sqrt

este tipo de importación es más adecuado si su código solo necesita acceder a funciones únicas o pocas desde el módulo, pero para usar cualquier elemento nuevo del módulo debe actualizar la declaración de importación.

3. Importaciones universales:

from math import * 

Aunque reduce significativamente el esfuerzo de escritura, pero no se recomienda porque llenará su código con varias funciones del módulo y su nombre podría entrar en conflicto con el nombre de las funciones definidas por el usuario. ejemplo:

Si tiene una función de su propio sqrt con nombre e importa matemáticas, su función es segura: está su sqrt y hay math.sqrt. Sin embargo, si lo hace desde la importación matemática *, tiene un problema: dos funciones diferentes con exactamente el mismo nombre. Fuente: Codecademy


5
import package
import module

Con import, el token debe ser un módulo (un archivo que contiene comandos de Python) o un paquete (una carpeta que sys.pathcontiene un archivo __init__.py).

Cuando hay subpaquetes:

import package1.package2.package
import package1.package2.module

los requisitos para la carpeta (paquete) o archivo (módulo) son los mismos, pero la carpeta o archivo debe estar dentro de package2los cuales deben estar dentro package1, y ambos package1y package2deben contener __init__.pyarchivos. https://docs.python.org/2/tutorial/modules.html

Con el fromestilo de importación:

from package1.package2 import package
from package1.package2 import module

el paquete o módulo ingresa el espacio de nombres del archivo que contiene la importdeclaración como module(o package) en lugar de package1.package2.module. Siempre puedes unirte a un nombre más conveniente:

a = big_package_name.subpackage.even_longer_subpackage_name.function

Solo el fromestilo de importación le permite nombrar una función o variable particular:

from package3.module import some_function

está permitido, pero

import package3.module.some_function 

No se permite.


4

Para agregar a lo que la gente ha dicho from x import *: además de hacer que sea más difícil saber de dónde provienen los nombres, esto elimina los correctores de código como Pylint. Informarán esos nombres como variables indefinidas.


3

Mi propia respuesta a esto depende principalmente de cuántos módulos diferentes usaré. Si solo voy a usar uno o dos, a menudo lo usaré from... importya que hace menos pulsaciones de teclas en el resto del archivo, pero si voy a usar muchos módulos diferentes, prefiero solo importporque eso significa que cada referencia de módulo es autodocumentada. Puedo ver de dónde viene cada símbolo sin tener que buscar.

Por lo general, prefiero el estilo autodocumentado de importación simple y solo cambio de ... importación cuando el número de veces que tengo que escribir el nombre del módulo crece por encima de 10 a 20, incluso si solo se importa un módulo.


1

Una de las diferencias significativas que descubrí de las que sorprendentemente nadie ha hablado es que al usar la importación simple puedes acceder private variabley private functionsdesde el módulo importado, lo que no es posible con la declaración from-import .

ingrese la descripción de la imagen aquí

Código en la imagen:

setting.py

public_variable = 42
_private_variable = 141
def public_function():
    print("I'm a public function! yay!")
def _private_function():
    print("Ain't nobody accessing me from another module...usually")

plain_importer.py

import settings
print (settings._private_variable)
print (settings.public_variable)
settings.public_function()
settings._private_function()

# Prints:
# 141
# 42
# I'm a public function! yay!
# Ain't nobody accessing me from another module...usually

from_importer.py

from settings import *
#print (_private_variable) #doesn't work
print (public_variable)
public_function()
#_private_function()   #doesn't work

0

Importar módulo: no necesita esfuerzos adicionales para recuperar otra cosa del módulo. Tiene desventajas como la escritura redundante

Importar módulo desde: menos escritura y más control sobre a qué elementos de un módulo se puede acceder. Para usar un nuevo elemento desde el módulo, debe actualizar su declaración de importación.


0

Hay algunos módulos incorporados que contienen funciones en su mayoría desnudos ( base 64 , matemáticas , OS , shutil , sys , tiempo , ...) y es definitivamente una buena práctica tener estas funciones desnudos con destino a algún espacio de nombres y así mejorar la legibilidad de su código. Considere cuán difícil es comprender el significado de estas funciones sin su espacio de nombres:

copysign(foo, bar)
monotonic()
copystat(foo, bar)

que cuando están vinculados a algún módulo:

math.copysign(foo, bar)
time.monotonic()
shutil.copystat(foo, bar)

A veces, incluso necesita el espacio de nombres para evitar conflictos entre diferentes módulos ( json.load vs. pickle.load )


Por otro lado, hay algunos módulos que contienen principalmente clases ( configparser , datetime , tempfile , zipfile , ...) y muchos de ellos hacen que sus nombres de clase se expliquen por sí solos :

configparser.RawConfigParser()
datetime.DateTime()
email.message.EmailMessage()
tempfile.NamedTemporaryFile()
zipfile.ZipFile()

por lo que puede haber un debate sobre si el uso de estas clases con el espacio de nombres del módulo adicional en su código agrega información nueva o simplemente alarga el código.


0

Me gustaría agregar a esto, hay algunas cosas a tener en cuenta durante las llamadas de importación:

Tengo la siguiente estructura:

mod/
    __init__.py
    main.py
    a.py
    b.py
    c.py
    d.py

main.py:

import mod.a
import mod.b as b
from mod import c
import d

dis.dis muestra la diferencia:

  1           0 LOAD_CONST               0 (-1)
              3 LOAD_CONST               1 (None)
              6 IMPORT_NAME              0 (mod.a)
              9 STORE_NAME               1 (mod)

  2          12 LOAD_CONST               0 (-1)
             15 LOAD_CONST               1 (None)
             18 IMPORT_NAME              2 (b)
             21 STORE_NAME               2 (b)

  3          24 LOAD_CONST               0 (-1)
             27 LOAD_CONST               2 (('c',))
             30 IMPORT_NAME              1 (mod)
             33 IMPORT_FROM              3 (c)
             36 STORE_NAME               3 (c)
             39 POP_TOP

  4          40 LOAD_CONST               0 (-1)
             43 LOAD_CONST               1 (None)
             46 IMPORT_NAME              4 (mod.d)
             49 LOAD_ATTR                5 (d)
             52 STORE_NAME               5 (d)
             55 LOAD_CONST               1 (None)

Al final se ven iguales (STORE_NAME es el resultado en cada ejemplo), pero vale la pena tener en cuenta si necesita considerar las siguientes cuatro importaciones circulares:

Ejemplo 1

foo/
   __init__.py
   a.py
   b.py
a.py:
import foo.b 
b.py:
import foo.a
>>> import foo.a
>>>

Esto funciona

ejemplo2

bar/
   __init__.py
   a.py
   b.py
a.py:
import bar.b as b
b.py:
import bar.a as a
>>> import bar.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "bar\a.py", line 1, in <module>
    import bar.b as b
  File "bar\b.py", line 1, in <module>
    import bar.a as a
AttributeError: 'module' object has no attribute 'a'

No dados

ejemplo3

baz/
   __init__.py
   a.py
   b.py
a.py:
from baz import b
b.py:
from baz import a
>>> import baz.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "baz\a.py", line 1, in <module>
    from baz import b
  File "baz\b.py", line 1, in <module>
    from baz import a
ImportError: cannot import name a

Problema similar ... pero claramente desde x import y no es lo mismo que import import xy como y

ejemplo4

qux/
   __init__.py
   a.py
   b.py
a.py:
import b 
b.py:
import a
>>> import qux.a
>>>

Este también funciona


0

Esta es la estructura de mi directorio de mi directorio actual:

.  
└─a  
   └─b  
     └─c
  1. La importdeclaración recuerda todos los nombres intermedios .
    Estos nombres tienen que ser calificados:

    In[1]: import a.b.c
    
    In[2]: a
    Out[2]: <module 'a' (namespace)>
    
    In[3]: a.b
    Out[3]: <module 'a.b' (namespace)>
    
    In[4]: a.b.c
    Out[4]: <module 'a.b.c' (namespace)>
  2. La from ... import ...declaración solo recuerda el nombre importado .
    Este nombre no debe ser calificado:

    In[1]: from a.b import c
    
    In[2]: a
    NameError: name 'a' is not defined
    
    In[2]: a.b
    NameError: name 'a' is not defined
    
    In[3]: a.b.c
    NameError: name 'a' is not defined
    
    In[4]: c
    Out[4]: <module 'a.b.c' (namespace)>

  • Nota: Por supuesto, reinicié mi consola Python entre los pasos 1 y 2.

0

Como menciona Jan Wrobel , un aspecto de las diferentes importaciones es la forma en que se divulgan las importaciones.

Módulo mymath

from math import gcd
...

Uso de mymath :

import mymath
mymath.gcd(30, 42)  # will work though maybe not expected

Si importé gcdsolo para uso interno, no para revelarlo a los usuarios mymath, esto puede ser un inconveniente. Tengo esto con bastante frecuencia, y en la mayoría de los casos quiero "mantener limpios mis módulos".

Aparte de la propuesta de Jan Wrobel de ocultar esto un poco más usando en su import mathlugar, he comenzado a ocultar las importaciones de la divulgación utilizando un guión bajo:

# for instance...
from math import gcd as _gcd
# or...
import math as _math

En proyectos más grandes, esta "mejor práctica" me permite controlar exactamente lo que se revela a las importaciones posteriores y lo que no. Esto mantiene mis módulos limpios y paga en un cierto tamaño de proyecto.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.