¿Punteros en Python?

124

Sé que Python no tiene punteros, pero ¿hay alguna manera de tener este rendimiento en su 2lugar?

>>> a = 1
>>> b = a # modify this line somehow so that b "points to" a
>>> a = 2
>>> b
1

?


He aquí un ejemplo: quiero form.data['field']y form.field.valuetener siempre el mismo valor. No es completamente necesario, pero creo que estaría bien.


En PHP, por ejemplo, puedo hacer esto:

<?php

class Form {
    public $data = [];
    public $fields;

    function __construct($fields) {
        $this->fields = $fields;
        foreach($this->fields as &$field) {
            $this->data[$field['id']] = &$field['value'];
        }
    }
}

$f = new Form([
    [
        'id' => 'fname',
        'value' => 'George'
    ],
    [
        'id' => 'lname',
        'value' => 'Lucas'
    ]
]);

echo $f->data['fname'], $f->fields[0]['value']; # George George
$f->data['fname'] = 'Ralph';
echo $f->data['fname'], $f->fields[0]['value']; # Ralph Ralph

Salida:

GeorgeGeorgeRalphRalph

ideone


O así en C ++ (creo que esto es correcto, pero mi C ++ está oxidado):

#include <iostream>
using namespace std;

int main() {
    int* a;
    int* b = a;
    *a = 1;
    cout << *a << endl << *b << endl; # 1 1

    return 0;
}
mpen
fuente
28
Quizás pueda hacer una pregunta similar a la de S.Lott (pero más productiva): ¿puede mostrarnos algún código real donde quisiera hacer esto? ¿Quizás incluso en otro idioma que sea más de tu gusto? Es probable que el problema que está intentando resolver se preste a una solución más pitónica, y centrarse en "Quiero punteros" está oscureciendo la respuesta real.
Ned Batchelder
8
No se necesita mucha imaginación; Puedo pensar en docenas de razones para querer hacer esto. Simplemente no es como se hace en lenguajes sin punteros como Python; debe envolverlo en un recipiente que no sea invariante, como en la respuesta de Matt.
Glenn Maynard
14
No escribirías una función de intercambio en Python. Escribirías a, b = b, a.
dan04
3
-1: El constructo en la pregunta es (a) sin sentido y (b) nadie parece ser capaz de proporcionar un ejemplo que lo haga sensato. Decir que hay "decenas de razones" no es lo mismo que publicar un ejemplo.
S.Lott
1
@Mark: Además, no estoy "en desacuerdo". Estoy confundido. Estoy haciendo una pregunta, buscando una manera de entender qué es esto y por qué crees que es tan importante.
S.Lott

Respuestas:

48

Quiero form.data['field']y form.field.valuetener siempre el mismo valor

Esto es factible, porque implica nombres decorados e indexación, es decir, construcciones completamente diferentes de las barenames a y sobre las bque estás preguntando, y porque con tu solicitud es completamente imposible. ¿Por qué pedir algo imposible y totalmente diferente de lo (posible) que realmente quieres ?

Tal vez no se dé cuenta de lo drásticamente diferentes que son las barenames y los nombres decorados. Cuando se refiere a un nombre desnudo a, obtiene exactamente el objeto al que ase vinculó por última vez en este alcance (o una excepción si no estaba vinculado en este alcance); este es un aspecto tan profundo y fundamental de Python que puede Posiblemente no sea subvertido. Cuando te refieres a un nombre decoradox.y , le estás pidiendo a un objeto (al que se xrefiere el objeto ) que proporcione "ely atributo", y en respuesta a esa solicitud, el objeto puede realizar cálculos totalmente arbitrarios (y la indexación es bastante similar: también permite realizar cálculos arbitrarios en respuesta).

Ahora, su ejemplo de "desiderata real" es misterioso porque en cada caso están involucrados dos niveles de indexación o obtención de atributos, por lo que la sutileza que anhela podría introducirse de muchas maneras. ¿Qué otros atributos se form.fieldsupone que tiene, por ejemplo, además value? Sin esos .valuecálculos adicionales , las posibilidades incluirían:

class Form(object):
   ...
   def __getattr__(self, name):
       return self.data[name]

y

class Form(object):
   ...
   @property
   def data(self):
       return self.__dict__

La presencia de .valuesugiere elegir la primera forma, además de una especie de envoltorio inútil:

class KouWrap(object):
   def __init__(self, value):
       self.value = value

class Form(object):
   ...
   def __getattr__(self, name):
       return KouWrap(self.data[name])

Si se supone que estas asignacionesform.field.value = 23 también establecen la entrada form.data, entonces el contenedor debe volverse más complejo, y no tan inútil:

class MciWrap(object):
   def __init__(self, data, k):
       self._data = data
       self._k = k
   @property
   def value(self):
       return self._data[self._k]
   @value.setter
   def value(self, v)
       self._data[self._k] = v

class Form(object):
   ...
   def __getattr__(self, name):
       return MciWrap(self.data, name)

El último ejemplo es más o menos lo más parecido, en Python, al sentido de "un puntero" que parece desear, pero es crucial comprender que tales sutilezas solo pueden funcionar con indexación y / o nombres decorados , nunca con barenames como pediste originalmente!

Alex Martelli
fuente
23
Lo pregunté de la forma en que lo hice porque esperaba obtener una solución general que funcionara para todo, hasta "barenames" y no tenía un caso particular en mente en ese momento, solo que me había topado con esto. problema con la iteración anterior de este proyecto y no quería volver a encontrarme. De todos modos, ¡esta es una gran respuesta! La incredulidad, sin embargo, es menos apreciada.
mpen
2
@Mark, mira los comentarios en tu Q y verás que la "incredulidad" es una reacción generalizada, y la A más votada te está diciendo "no lo hagas, supéralo", seguido de uno que dice "así es como es". Si bien es posible que no "aprecie" que las personas con conocimientos de Python reaccionen con asombro a sus especificaciones originales, son bastante asombrosas en sí mismas ;-).
Alex Martelli
30
Sí, pero parece asombrado por mi falta de conocimiento de Python ... como si fuéramos una especie extranjera: P
mpen
51

No hay forma de que puedas hacer eso cambiando solo esa línea. Tu puedes hacer:

a = [1]
b = a
a[0] = 2
b[0]

Eso crea una lista, asigna la referencia a a, luego b también, usa la referencia a para establecer el primer elemento en 2, luego accede usando la variable de referencia b.

Matthew Flaschen
fuente
17
Ese es exactamente el tipo de inconsistencia que odio de Python y estos lenguajes dinámicos. (Sí, sí, no es realmente "inconsistente" porque estás cambiando un atributo en lugar de la referencia, pero todavía no me gusta)
mpen
10
@Mark: de hecho. Conozco a innumerables (bueno, algunas) personas que pasaron posiblemente horas buscando un "error" en su código y luego descubrir que fue causado por una lista que no se copió en papel.
houbysoft
14
No hay inconsistencia. Y no tiene nada que ver con el gran debate estático versus dinámico. Si fueran dos referencias a la misma Java ArrayList, sería la misma sintaxis de módulo. Si usa objetos inmutables (como tuplas), no tiene que preocuparse de que el objeto cambie a través de otra referencia.
Matthew Flaschen
Lo he usado varias veces, más comúnmente para solucionar la falta de "no local" de 2.x. No es lo más bonito que se puede hacer, pero funciona bien en caso de apuro.
Glenn Maynard
1
Esto no es inconsistente en absoluto porque el objeto al que está asignando ay bes la lista, no el valor en la lista. Las variables no cambian de asignación, el objeto es el mismo objeto. Si cambiara, como en el caso de cambiar el número entero (cada uno de los cuales son objetos diferentes), aahora se asignaría a otro objeto y no hay nada que indique lo bsiguiente. Aquí, ano se está reasignando, sino que está cambiando un valor dentro del objeto al que está asignado. Dado bque todavía está vinculado a ese objeto, reflejará ese objeto y los cambios en los valores dentro de él.
Arkigos
34

No es un error, es una característica :-)

Cuando observe el operador '=' en Python, no piense en términos de asignación. No asignas cosas, las atas. = es un operador vinculante.

Entonces, en su código, le está dando al valor 1 un nombre: a. Entonces, le está dando al valor en 'a' un nombre: b. Entonces estás vinculando el valor 2 al nombre 'a'. El valor ligado a b no cambia en esta operación.

Viniendo de lenguajes similares a C, esto puede ser confuso, pero una vez que se acostumbre a él, encontrará que le ayuda a leer y razonar sobre su código más claramente: el valor que tiene el nombre 'b' no cambiará a menos que usted cambiarlo explícitamente. Y si hace una 'importación de esto', encontrará que el Zen de Python establece que Explícito es mejor que implícito.

Tenga en cuenta también que los lenguajes funcionales como Haskell también utilizan este paradigma, con un gran valor en términos de robustez.

David Harks
fuente
39
Sabes, he leído respuestas como esta docenas de veces y nunca las he entendido. El comportamiento de a = 1; b = a; a = 2;es exactamente el mismo en Python, C y Java: b es 1. ¿Por qué este enfoque en "= no es una asignación, es vinculante"?
Ned Batchelder
4
Asignas cosas. Por eso se llama declaración de asignación . La distinción que está afirmando no tiene sentido. Y esto no tiene nada que ver con compilado v. Interpretado o estático v. Dinámico. Java es un lenguaje compilado con verificación de tipo estática y tampoco tiene punteros.
Matthew Flaschen
3
¿Y C ++? "b" podría ser una referencia a "a". Comprender la diferencia entre asignación y enlace es fundamental para comprender completamente por qué Mark no puede hacer lo que le gustaría hacer y cómo se diseñan lenguajes como Python. Conceptualmente (no necesariamente en la implementación), "a = 1" no sobrescribe el bloque de memoria llamado "a" con 1; asigna un nombre "a" al objeto "1" ya existente, que es fundamentalmente diferente de lo que sucede en C. Es por eso que los punteros como concepto no pueden existir en Python: se volverían obsoletos la próxima vez que el la variable original fue "asignada sobre".
Glenn Maynard
1
@dw: ¡Me gusta esta forma de pensar! "Atar" es una buena palabra. @Ned: La salida es la misma, sí, pero en C el valor de "1" se copia a ambos ay bmientras que en Python, ambos se refieren al mismo "1" (creo). Por lo tanto, si pudiera cambiar el valor de 1 (como con los objetos), sería diferente. Lo que lleva a algunos problemas extraños de boxeo / unboxing, escuché.
mpen
4
La diferencia entre Python y C no es lo que significa "asignación". Es lo que significa "variable".
dan04
28

¡Si! ¡Hay una forma de usar una variable como puntero en Python!

Lamento decir que muchas de las respuestas fueron parcialmente incorrectas. En principio, cada asignación igual (=) comparte la dirección de memoria (verifique la función id (obj)), pero en la práctica no es así. Hay variables cuyo comportamiento igual ("=") funciona en último término como una copia del espacio de memoria, principalmente en objetos simples (por ejemplo, objeto "int"), y otras en las que no (por ejemplo, objetos "lista", "dict") .

Aquí hay un ejemplo de asignación de puntero

dict1 = {'first':'hello', 'second':'world'}
dict2 = dict1 # pointer assignation mechanism
dict2['first'] = 'bye'
dict1
>>> {'first':'bye', 'second':'world'}

Aquí hay un ejemplo de asignación de copia

a = 1
b = a # copy of memory mechanism. up to here id(a) == id(b)
b = 2 # new address generation. therefore without pointer behaviour
a
>>> 1

La asignación de puntero es una herramienta bastante útil para crear un alias sin desperdiciar memoria extra, en ciertas situaciones para realizar un código cómodo,

class cls_X():
   ...
   def method_1():
      pd1 = self.obj_clsY.dict_vars_for_clsX['meth1'] # pointer dict 1: aliasing
      pd1['var4'] = self.method2(pd1['var1'], pd1['var2'], pd1['var3'])
   #enddef method_1
   ...
#endclass cls_X

pero hay que ser consciente de este uso para evitar errores de código.

Para concluir, por defecto algunas variables son barenames (objetos simples como int, float, str, ...), y algunas son punteros cuando se asignan entre ellas (por ejemplo, dict1 = dict2). ¿Cómo reconocerlos? prueba este experimento con ellos. En IDE con panel de explorador de variables, generalmente aparece la dirección de memoria ("@axbbbbbb ...") en la definición de objetos de mecanismo de puntero.

Sugiero investigar en el tema. Seguro que hay mucha gente que sabe mucho más sobre este tema. (ver módulo "ctypes"). Espero que sea útil. ¡Disfruta del buen uso de los objetos! Saludos, José Crespo

José Crespo Barrios
fuente
¿Entonces tengo que usar un diccionario para pasar una variable por referencia a una función, y no puedo pasar una variable por referencia usando un int o una cadena?
Sam
13
>> id(1)
1923344848  # identity of the location in memory where 1 is stored
>> id(1)
1923344848  # always the same
>> a = 1
>> b = a  # or equivalently b = 1, because 1 is immutable
>> id(a)
1923344848
>> id(b)  # equal to id(a)
1923344848

Como se puede ver a, y bson sólo dos nombres diferentes que hacen referencia a la misma objeto inmutable (int) 1. Si luego escribe a = 2, reasigna el nombre aa un objeto diferente (int) 2, pero la breferencia continúa a 1:

>> id(2)
1923344880
>> a = 2
>> id(a)
1923344880  # equal to id(2)
>> b
1           # b hasn't changed
>> id(b)
1923344848  # equal to id(1)

¿Qué pasaría si tuviera un objeto mutable en su lugar, como una lista [1]?

>> id([1])
328817608
>> id([1])
328664968  # different from the previous id, because each time a new list is created
>> a = [1]
>> id(a)
328817800
>> id(a)
328817800 # now same as before
>> b = a
>> id(b)
328817800  # same as id(a)

Nuevamente, estamos haciendo referencia al mismo objeto (lista) [1]con dos nombres diferentes ay b. Sin embargo ahora podemos mutar esta lista mientras que sigue siendo el mismo objeto, y a, ba los dos para continuar haciendo referencia a ella

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before
Andreas K.
fuente
1
Gracias por presentar la función de identificación. Esto resuelve muchas de mis dudas.
Haudoing
12

Desde un punto de vista, todo es un puntero en Python. Su ejemplo funciona de manera muy similar al código C ++.

int* a = new int(1);
int* b = a;
a = new int(2);
cout << *b << endl;   // prints 1

(Un equivalente más cercano usaría algún tipo de en shared_ptr<Object>lugar de int*).

Aquí hay un ejemplo: quiero que form.data ['campo'] y form.field.value siempre tengan el mismo valor. No es completamente necesario, pero creo que estaría bien.

Puede hacer esto sobrecargando la clase de __getitem__in form.data.

dan04
fuente
form.datano es una clase. ¿Es necesario convertirlo en uno o puedo anularlo sobre la marcha? (Es solo un dictado de Python) Además, los datos tendrían que tener una referencia formpara acceder a los campos ... lo que hace que implementar esto sea feo.
mpen
1

Este es un puntero de Python (diferente de c / c ++)

>>> a = lambda : print('Hello')
>>> a
<function <lambda> at 0x0000018D192B9DC0>
>>> id(a) == int(0x0000018D192B9DC0)
True
>>> from ctypes import cast, py_object
>>> cast(id(a), py_object).value == cast(int(0x0000018D192B9DC0), py_object).value
True
>>> cast(id(a), py_object).value
<function <lambda> at 0x0000018D192B9DC0>
>>> cast(id(a), py_object).value()
Hello
Enrique
fuente
0

Escribí la siguiente clase simple como, efectivamente, una forma de emular un puntero en Python:

class Parameter:
    """Syntactic sugar for getter/setter pair
    Usage:

    p = Parameter(getter, setter)

    Set parameter value:
    p(value)
    p.val = value
    p.set(value)

    Retrieve parameter value:
    p()
    p.val
    p.get()
    """
    def __init__(self, getter, setter):
        """Create parameter

        Required positional parameters:
        getter: called with no arguments, retrieves the parameter value.
        setter: called with value, sets the parameter.
        """
        self._get = getter
        self._set = setter

    def __call__(self, val=None):
        if val is not None:
            self._set(val)
        return self._get()

    def get(self):
        return self._get()

    def set(self, val):
        self._set(val)

    @property
    def val(self):
        return self._get()

    @val.setter
    def val(self, val):
        self._set(val)

Aquí hay un ejemplo de uso (de una página de cuaderno de jupyter):

l1 = list(range(10))
def l1_5_getter(lst=l1, number=5):
    return lst[number]

def l1_5_setter(val, lst=l1, number=5):
    lst[number] = val

[
    l1_5_getter(),
    l1_5_setter(12),
    l1,
    l1_5_getter()
]

Out = [5, None, [0, 1, 2, 3, 4, 12, 6, 7, 8, 9], 12]

p = Parameter(l1_5_getter, l1_5_setter)

print([
    p(),
    p.get(),
    p.val,
    p(13),
    p(),
    p.set(14),
    p.get()
])
p.val = 15
print(p.val, l1)

[12, 12, 12, 13, 13, None, 14]
15 [0, 1, 2, 3, 4, 15, 6, 7, 8, 9]

Por supuesto, también es fácil hacer que esto funcione para dict elementos o atributos de un objeto. Incluso hay una forma de hacer lo que pidió el OP, usando globals ():

def setter(val, dict=globals(), key='a'):
    dict[key] = val

def getter(dict=globals(), key='a'):
    return dict[key]

pa = Parameter(getter, setter)
pa(2)
print(a)
pa(3)
print(a)

Esto imprimirá 2, seguido de 3.

Jugar con el espacio de nombres global de esta manera es, de manera transparente, una idea terrible, pero muestra que es posible (si no es aconsejable) hacer lo que pidió el OP.

El ejemplo es, por supuesto, bastante inútil. Pero he encontrado que esta clase es útil en la aplicación para la que la desarrollé: un modelo matemático cuyo comportamiento se rige por numerosos parámetros matemáticos configurables por el usuario, de diversos tipos (que, debido a que dependen de argumentos de línea de comando, no se conocen en tiempo de compilación). Y una vez que el acceso a algo se ha encapsulado en un objeto de parámetro, todos esos objetos se pueden manipular de manera uniforme.

Aunque no se parece mucho a un puntero C o C ++, esto está resolviendo un problema que habría resuelto con punteros si estuviera escribiendo en C ++.

Leon Avery
fuente
0

El siguiente código emula exactamente el comportamiento de los punteros en C:

from collections import deque # more efficient than list for appending things
pointer_storage = deque()
pointer_address = 0

class new:    
    def __init__(self):
        global pointer_storage    
        global pointer_address

        self.address = pointer_address
        self.val = None        
        pointer_storage.append(self)
        pointer_address += 1


def get_pointer(address):
    return pointer_storage[address]

def get_address(p):
    return p.address

null = new() # create a null pointer, whose address is 0    

A continuación se muestran ejemplos de uso:

p = new()
p.val = 'hello'
q = new()
q.val = p
r = new()
r.val = 33

p = get_pointer(3)
print(p.val, flush = True)
p.val = 43
print(get_pointer(3).val, flush = True)

Pero ahora es el momento de dar un código más profesional, incluida la opción de eliminar punteros, que acabo de encontrar en mi biblioteca personal:

# C pointer emulation:

from collections import deque # more efficient than list for appending things
from sortedcontainers import SortedList #perform add and discard in log(n) times


class new:      
    # C pointer emulation:
    # use as : p = new()
    #          p.val             
    #          p.val = something
    #          p.address
    #          get_address(p) 
    #          del_pointer(p) 
    #          null (a null pointer)

    __pointer_storage__ = SortedList(key = lambda p: p.address)
    __to_delete_pointers__ = deque()
    __pointer_address__ = 0 

    def __init__(self):      

        self.val = None 

        if new.__to_delete_pointers__:
            p = new.__to_delete_pointers__.pop()
            self.address = p.address
            new.__pointer_storage__.discard(p) # performed in log(n) time thanks to sortedcontainers
            new.__pointer_storage__.add(self)  # idem

        else:
            self.address = new.__pointer_address__
            new.__pointer_storage__.add(self)
            new.__pointer_address__ += 1


def get_pointer(address):
    return new.__pointer_storage__[address]


def get_address(p):
    return p.address


def del_pointer(p):
    new.__to_delete_pointers__.append(p)

null = new() # create a null pointer, whose address is 0
MikeTeX
fuente
Creo que acaba de encasillar los valores de una manera extraña.
mpen
¿Quiere decir: una "forma inteligente" o una "forma no inteligente"?
MikeTeX
Uhhh ... Estoy luchando por ver un caso de uso válido para un almacenamiento global indexado por un número aleatorio.
mpen
Ejemplo de uso: soy ingeniero de algoritmos y tengo que trabajar con programadores. Trabajo con Python y ellos trabajan con C ++. A veces, me piden que les escriba un algoritmo, y lo escribo lo más cerca posible de C ++ para su conveniencia. Los punteros son útiles, por ejemplo, para árboles binarios, etc.
MikeTeX
Nota: si el almacenamiento global te molesta, puedes incluirlo como variable global al nivel de la propia clase, que probablemente sea más elegante.
MikeTeX