Todo es un objeto en Python: identidad, tipo y valor

Cuando decimos “todo es un objeto en Python” no es marketing ni filosofía — es una afirmación técnica precisa con consecuencias directas en cómo funciona el lenguaje. Vamos a ver qué significa exactamente.

Un objeto en Python es una región de memoria con tres atributos fundamentales: identidad (una dirección única, accesible con id()), tipo (la clase a la que pertenece, accesible con type()), y valor (el contenido que representa). Cada valor que manipulas, sin excepción, tiene estos tres atributos. Esto incluye 42, "hola", None, una función que acabas de definir, y la propia clase int.

¿Por qué diseñar el lenguaje así? Porque la alternativa — tener valores “primitivos” sin tipo ni identidad, como en Java con int vs Integer — genera una fractura en el modelo mental. Python eligió la uniformidad: una sola abstracción para todo. El precio es algo más de overhead de memoria (cada objeto lleva metadatos). El beneficio es que cualquier cosa puede ser inspeccionada, pasada como argumento, almacenada en una colección, o decorada — sin casos especiales.

Cuándo importa esto en la práctica: cuando haces reflexión (isinstance, type()), cuando escribes decoradores o metaclases, cuando necesitas entender por qué is y == se comportan diferente, o cuando depuras comportamiento inesperado con objetos mutables como argumentos por defecto.

Lo que se rompe si no internalizas este modelo: usar is para comparar valores en lugar de identidades, asumir que dos objetos con el mismo valor son el mismo objeto, o confundirte cuando type(type) devuelve type — que parece un error pero es el diseño intencional de la metaclase raíz del sistema.

# Todas las importaciones que necesitamos para inspeccionar el modelo de objetos
import types
import inspect


def describe_object(obj: object) -> dict:
    """Devuelve las tres propiedades fundamentales de cualquier objeto."""
    return {
        "identity": id(obj),
        "type": type(obj),
        "type_name": type(obj).__name__,
        "value_repr": repr(obj),
    }


# ── 1. Valores literales ─────────────────────────────────────────────────────

integer_obj = 42
string_obj = "hola"
list_obj = [1, 2, 3]

for obj in (integer_obj, string_obj, list_obj):
    info = describe_object(obj)
    print(f"{info['value_repr']:>12}  →  type={info['type_name']}, id={info['identity']}")


# ── 2. None es exactamente una instancia — no un valor especial ──────────────

print("\n--- None ---")
print(f"type(None)  = {type(None)}")        # <class 'NoneType'>
print(f"id(None)    = {id(None)}")          # siempre el mismo: singleton

# None es singleton; por eso el idioma correcto es `is None`, no `== None`
x = None
print(f"x is None   = {x is None}")        # True — misma identidad
print(f"x == None   = {x == None}")        # True — mismo valor (aquí coincide)


# ── 3. Las funciones son objetos de primera clase ────────────────────────────

def greet(name: str) -> str:
    return f"Hola, {name}"

print("\n--- función como objeto ---")
print(f"type(greet)       = {type(greet)}")          # <class 'function'>
print(f"isinstance check  = {isinstance(greet, types.FunctionType)}")

# Puedes almacenarla, pasarla, inspeccionarla igual que cualquier otro objeto
funcs = [greet, str.upper, len]
print(f"lista de funciones: {[f.__name__ for f in funcs]}")


# ── 4. Las clases son objetos — instancias de `type` ─────────────────────────

print("\n--- clases como objetos ---")

class Animal:
    pass

print(f"type(Animal)      = {type(Animal)}")         # <class 'type'>
print(f"type(int)         = {type(int)}")            # <class 'type'>
print(f"type(str)         = {type(str)}")            # <class 'type'>

# type es su propia metaclase: la raíz del sistema de tipos
print(f"type(type)        = {type(type)}")           # <class 'type'>  ← no es un error
print(f"isinstance(int, type)  = {isinstance(int, type)}")   # True
print(f"isinstance(type, type) = {isinstance(type, type)}")  # True


# ── 5. La reflexión es natural porque todo tiene tipo e identidad ─────────────

print("\n--- reflexión uniforme ---")

things = [42, "texto", [1, 2], greet, int, type, None]

for thing in things:
    # Podemos aplicar exactamente la misma operación a *cualquier* cosa
    print(f"{repr(thing):>20}  is-a  {type(thing).__name__}")

Lo que revela el código

La función describe_object funciona idéntica para 42, para greet, y para la clase int — sin ninguna ramificación. Eso es posible precisamente porque los tres tienen identidad, tipo y valor. No hay un camino especial para “primitivos”.

None como singleton merece atención. NoneType solo tiene una instancia en todo el intérprete, siempre en la misma dirección de memoria. Por eso la comparación canónica es x is None: no estás comparando valores, estás verificando que x apunta al único objeto None que existe. Usar == None funciona pero abre la puerta a falsos positivos si alguien implementa __eq__ de forma rara en su clase.

El bloque de funciones muestra que greet vive en funcs junto a str.upper y len sin ningún boxing ni conversión. No hay diferencia conceptual entre guardar 42 y guardar una función en una lista — ambos son referencias a objetos.

La parte más densa es la de metaclases. Cuando declaras class Animal: pass, Python ejecuta internamente Animal = type("Animal", (), {}). La clase Animal es un objeto de tipo type. Y type mismo — la metaclase raíz — tiene type(type) == type. Es una auto-referencia intencional: type es instancia de sí misma. Esto cierra el sistema de tipos sin necesidad de una cadena infinita.

El bucle final del bloque de reflexión es la demostración más directa del modelo uniforme: la misma expresión type(thing).__name__ funciona sin modificación para un entero, una cadena, una función, una clase, y None. En Java necesitarías ramas para primitivos vs. objetos. Aquí no hay distinción.

Errores que debes conocer

Error: usar is para comparar enteros o strings pequeños y que “funcione” en desarrollo pero falle en producción.

# ❌ Wrong
a = 256
b = 256
print(a is b)   # True — CPython cachea enteros de -5 a 256, parece correcto

a = 1000
b = 1000
print(a is b)   # False — objetos distintos con el mismo valor
# ✅ Right
a = 1000
b = 1000
print(a == b)   # True siempre — compara valores, no identidades

is compara identidades (id()), no valores. El caché de enteros de CPython hace que is funcione accidentalmente para números pequeños, pero es un detalle de implementación, no parte del modelo del lenguaje.


Error: confundir type() con isinstance() al verificar tipos, perdiendo la jerarquía de herencia.

# ❌ Wrong
def process(value):
    if type(value) == int:      # falla con subclases de int
        return value * 2

class MyInt(int): pass
print(process(MyInt(5)))        # None — MyInt no es exactamente int
# ✅ Right
def process(value):
    if isinstance(value, int):  # True para int y cualquier subclase
        return value * 2

print(process(MyInt(5)))        # 10

type(x) == SomeClass exige coincidencia exacta en el grafo de tipos. isinstance respeta la jerarquía completa, que es lo que casi siempre quieres.


Error: asumir que type(None) es None o que None no tiene tipo.

# ❌ Wrong
def check(x):
    if type(x) == None:         # siempre False; None no es un tipo
        print("es None")
# ✅ Right
def check(x):
    if x is None:               # idioma correcto: identidad de singleton
        print("es None")
    # o si necesitas el tipo explícito:
    if type(x) is type(None):   # type(None) devuelve NoneType
        print("es None")

None es un objeto de tipo NoneType, no de tipo None. El patrón correcto para detectarlo es is None — directo, claro, y correcto para cualquier implementación de Python.


Control Block:

86

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio