Ponteiros em Python?

124

Eu sei Python não tem ponteiros, mas existe uma maneira de ter esse rendimento 2em vez

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

?


Aqui está um exemplo: eu quero form.data['field']e form.field.valuesempre tenho o mesmo valor. Não é completamente necessário, mas acho que seria legal.


No PHP, por exemplo, eu posso fazer isso:

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

Resultado:

GeorgeGeorgeRalphRalph

ideona


Ou assim em C ++ (acho que está certo, mas meu C ++ está enferrujado):

#include <iostream>
using namespace std;

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

    return 0;
}
mpen
fonte
28
Talvez eu possa fazer uma pergunta semelhante à de S.Lott (mas mais produtiva): você pode nos mostrar algum código real onde você queria fazer isso? Talvez até em outro idioma que seja mais do seu gosto? Provavelmente, o problema que você está tentando resolver se presta a uma solução mais pitônica, e o foco em "Quero indicações" está obscurecendo a resposta real.
precisa
8
Não é preciso muita imaginação; Eu posso pensar em dezenas de razões para querer fazer isso. Não é apenas como é feito em linguagens sem ponteiro, como Python; você precisa envolvê-lo em um recipiente que não seja invariável, como na resposta de Matt.
Glenn Maynard
14
Você não escreveria uma função de troca no Python. Você escreveria a, b = b, a.
dan04
3
-1: O construto da questão é (a) insensato e (b) ninguém parece capaz de fornecer um exemplo que o torne sensato. Dizer que existem "dezenas de razões" não é o mesmo que postar um exemplo.
S.Lott
1
@ Mark: Além disso, não estou "discordando". Estou confuso. Estou fazendo uma pergunta, procurando uma maneira de entender o que é isso e por que você acha que é tão importante.
S.Lott

Respostas:

48

Quero form.data['field']e form.field.valuesempre ter o mesmo valor

Isso é possível, porque envolve nomes e indexação decorados - ou seja, construções completamente diferentes dos nomes de barras a e sobre as bquais você está perguntando, e com o seu pedido é totalmente impossível. Por que pedir algo impossível e totalmente diferente da (possível) coisa que você realmente quer ?!

Talvez você não perceba como nomes de barreiras e nomes decorados são drasticamente diferentes. Quando você se refere a um nome de barra a, está obtendo exatamente o aúltimo objeto vinculado a este escopo (ou uma exceção se não estiver vinculado a esse escopo) - esse é um aspecto tão profundo e fundamental do Python que pode possivelmente seja subvertido. Quando você se refere a um nome decoradox.y , solicita a um objeto (o objeto xse refere) que forneça "o yatributo" - e em resposta a essa solicitação, o objeto pode executar cálculos totalmente arbitrários (e a indexação é bastante semelhante: também permite que cálculos arbitrários sejam executados em resposta).

Agora, seu exemplo de "desiderata real" é misterioso porque, em cada caso, dois níveis de indexação ou obtenção de atributos estão envolvidos, portanto a sutileza que você deseja pode ser introduzida de várias maneiras. Que outros atributos form.fielddevem ter, por exemplo, além disso value? Sem esses .valuecálculos adicionais , as possibilidades incluiriam:

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

e

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

A presença de .valuesugestões para escolher a primeira forma, além de um tipo de invólucro inútil:

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

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

Se atribuições como form.field.value = 23também devem definir a entrada form.data, o wrapper deve se tornar realmente mais complexo e não tão 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)

O último exemplo é o mais aproximado possível, em Python, do sentido de "um ponteiro" como você deseja - mas é crucial entender que essas sutilezas podem funcionar apenas com indexação e / ou nomes decorados , nunca com nomes de barras como você pediu originalmente!

Alex Martelli
fonte
23
Eu perguntei da maneira que eu fiz, porque esperava encontrar uma solução geral que funcionasse para tudo, até "nomes de barreiras" e eu não tinha um caso específico em mente na época - apenas que eu tinha encontrado esse problema. problema com a iteração anterior deste projeto e eu não queria encontrar novamente. Enfim, esta é uma ótima resposta! A incredulidade, no entanto, é menos apreciada.
MPEN
2
@ Mark, olhe para os comentários no seu Q e você verá que "incredulidade" é uma reação generalizada - e o A mais votado está dizendo para você "não faça, supere", seguido por um que vai "é assim que é". Embora você possa não "apreciar" as pessoas conhecedoras de Python que reagem com espanto às suas especificações originais, elas são surpreendentes por si mesmas ;-).
precisa
30
Sim, mas você parece surpreso com a minha falta de conhecimento em Python ... como se fossemos uma espécie estrangeira: P
mpen
51

Não há como você fazer isso mudando apenas essa linha. Você pode fazer:

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

Isso cria uma lista, atribui a referência a e, em seguida, b também usa a referência a para definir o primeiro elemento para 2 e acessa usando a variável de referência b.

Matthew Flaschen
fonte
17
Esse é exatamente o tipo de inconsistência que eu odeio no Python e nessas linguagens dinâmicas. (Sim, sim, não é realmente "inconsistente" porque você está mudando um atributo em vez da referência, mas eu ainda não gosto)
MPEN
10
@ Mark: de fato. Conheço inúmeras (bem, algumas) pessoas que passaram possivelmente horas procurando um "bug" em seu código e depois descobriram que foi causado por uma lista que não foi copiada.
houbysoft
14
Não há inconsistência. E isso não tem nada a ver com o grande debate estático versus dinâmico. Se fossem duas referências ao mesmo Java ArrayList, seria a mesma sintaxe do módulo. Se você usa objetos imutáveis ​​(como tuplas), não precisa se preocupar com a alteração do objeto por outra referência.
Matthew Flaschen
Eu usei isso várias vezes, mais comumente para solucionar a falta de "não-local" do 2.x. Não é a coisa mais bonita a se fazer, mas funciona muito bem.
Glenn Maynard
1
Isso não é inconsistente porque o objeto ao qual você está atribuindo ae bé a lista, não o valor na lista. As variáveis ​​não estão mudando a atribuição, o objeto é o mesmo objeto. Se ele fosse alterado, como no caso de alterar o número inteiro (cada um dos quais são objetos diferentes), aagora seria atribuído a outro objeto e não há nada a ser solicitado b. Aqui, anão está sendo reatribuído, mas um valor dentro do objeto ao qual está atribuído está sendo alterado. Como bainda está vinculado a esse objeto, ele refletirá esse objeto e as alterações nos valores dentro dele.
Arkigos
34

Isto não é um erro, é um recurso :-)

Quando você olha para o operador '=' no Python, não pense em termos de atribuição. Você não atribui coisas, vincula-as. = é um operador de ligação.

Portanto, no seu código, você está dando um nome ao valor 1: a. Então, você está dando o valor em 'a' a nome: b. Então você está vinculando o valor 2 ao nome 'a'. O valor vinculado a b não muda nesta operação.

Vindo de idiomas do tipo C, isso pode ser confuso, mas depois que você se acostuma, descobre que ajuda a ler e raciocinar sobre seu código com mais clareza: o valor que tem o nome 'b' não será alterado, a menos que você alterá-lo explicitamente. E se você 'importar isso', verá que o Zen do Python afirma que o Explícito é melhor do que implícito.

Observe também que linguagens funcionais como Haskell também usam esse paradigma, com grande valor em termos de robustez.

David Harks
fonte
39
Sabe, eu li respostas como essa dezenas de vezes e nunca entendi. O comportamento de a = 1; b = a; a = 2;é exatamente o mesmo em Python, C e Java: b é 1. Por que esse foco em "= não é atribuição, é obrigatório"?
precisa
4
Você atribui coisas. É por isso que é chamado de declaração de atribuição . A distinção que você afirma não faz sentido. E isso não tem nada a ver com compilado versus interpretado ou estático versus dinâmico. Java é uma linguagem compilada com verificação de tipo estática e também não possui ponteiros.
Matthew Flaschen
3
E o C ++? "b" pode ser uma referência a "a". Compreender a diferença entre atribuição e encadernação é fundamental para entender completamente por que Mark não pode fazer o que gostaria de fazer e como linguagens como Python são projetadas. Conceitualmente (não necessariamente na implementação), "a = 1" não substitui o bloco de memória chamado "a" por 1; ele atribui um nome "a" ao objeto já existente "1", que é fundamentalmente diferente do que acontece em C. É por isso que ponteiros como conceito não podem existir no Python - eles ficam obsoletos na próxima vez que o A variável original foi "atribuída sobre".
Glenn Maynard
1
@dw: Eu gosto dessa maneira de pensar sobre isso! "Encadernação" é uma boa palavra. @ Ned: A saída é a mesma, sim, mas em C o valor de "1" é copiado para ambos ae, benquanto em Python, eles se referem ao mesmo "1" (eu acho). Portanto, se você pudesse alterar o valor de 1 (como nos objetos), seria diferente. O que leva a alguns problemas estranhos de boxe / unboxing, pelo que ouvi.
MPEN
4
A diferença entre Python e C não é o que "atribuição" significa. É o que "variável" significa.
dan04
28

Sim! existe uma maneira de usar uma variável como um ponteiro em python!

Lamento dizer que muitas respostas estavam parcialmente erradas. Em princípio, toda atribuição igual (=) compartilha o endereço de memória (verifique a função id (obj)), mas, na prática, não é assim. Existem variáveis ​​cujo comportamento igual ("=") funciona no último termo como uma cópia do espaço da memória, principalmente em objetos simples (por exemplo, objeto "int") e outras em que não (por exemplo, objetos "list", "dict") .

Aqui está um exemplo de atribuição de ponteiro

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

Aqui está um exemplo de atribuição de cópia

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

A atribuição de ponteiro é uma ferramenta bastante útil para criar aliases sem o desperdício de memória extra, em certas situações para a execução de código confortável,

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

mas é preciso estar ciente desse uso para evitar erros de código.

Para concluir, por padrão, algumas variáveis ​​são nomes de barras (objetos simples como int, float, str, ...) e algumas são ponteiros quando atribuídas entre elas (por exemplo, dict1 = dict2). Como reconhecê-los? tente este experimento com eles. Nos IDEs com painel explorador variável, geralmente parece ser o endereço de memória ("@axbbbbbb ...") na definição de objetos de mecanismo de ponteiro.

Eu sugiro investigar no tópico. Há muitas pessoas que sabem muito mais sobre esse tópico com certeza. (consulte o módulo "ctypes"). Espero que seja útil. Aproveite o bom uso dos objetos! Atenciosamente, José Crespo

José Crespo Barrios
fonte
Então eu tenho que usar um dicionário para passar uma variável por referência a uma função e não posso passar uma variável por referência usando um int ou uma string?
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 você pode ver ae bsão apenas dois nomes diferentes que se referem ao mesmo objeto imutável (int) 1. Se você escrever mais tarde a = 2, redesigne o nome apara um objeto diferente (int) 2, mas a breferência continua 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)

O que aconteceria se você tivesse um objeto mutável, como uma 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)

Novamente, estamos nos referindo ao mesmo objeto (lista) [1]por dois nomes diferentes ae b. No entanto, agora podemos transformar esta lista enquanto ele permanece o mesmo objeto, e a, bambos continuarão fazendo referência a ele

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before
Andreas K.
fonte
1
Obrigado por introduzir a função de identificação. Isso resolve muitas das minhas dúvidas.
haudoing 8/01/19
12

De um ponto de vista, tudo é um ponteiro no Python. Seu exemplo funciona muito como o código C ++.

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

(Um equivalente mais próximo usaria algum tipo de em shared_ptr<Object>vez de int*.)

Aqui está um exemplo: Quero que form.data ['field'] e form.field.value tenham sempre o mesmo valor. Não é completamente necessário, mas acho que seria legal.

Você pode fazer isso por sobrecarga __getitem__na form.dataclasse 's.

dan04
fonte
form.datanão é uma aula. É necessário torná-lo um ou posso substituí-lo em tempo real? (É apenas um ditado em python) Além disso, os dados precisariam ter uma referência formpara acessar os campos ... o que torna a implementação desse processo feio.
MPEN
1

Este é um ponteiro 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
Henry
fonte
0

Eu escrevi a seguinte classe simples como, efetivamente, uma maneira de emular um ponteiro em 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)

Aqui está um exemplo de uso (de uma página de caderno 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]

Obviamente, também é fácil fazer isso funcionar para itens de ditado ou atributos de um objeto. Existe até uma maneira de fazer o que o OP pediu, 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)

Isso imprimirá 2, seguidos por 3.

Mexer com o espaço para nome global dessa maneira é uma idéia terrível e transparente, mas mostra que é possível (se desaconselhável) fazer o que o OP solicitou.

O exemplo é, obviamente, bastante inútil. Mas achei essa classe útil no aplicativo para o qual a desenvolvi: um modelo matemático cujo comportamento é governado por vários parâmetros matemáticos configuráveis ​​pelo usuário, de diversos tipos (que, porque dependem de argumentos da linha de comando, não são conhecidos). em tempo de compilação). E uma vez que o acesso a algo tenha sido encapsulado em um objeto Parameter, todos esses objetos podem ser manipulados de maneira uniforme.

Embora não pareça muito com um ponteiro C ou C ++, isso está resolvendo um problema que eu teria resolvido com ponteiros se estivesse escrevendo em C ++.

Leon Avery
fonte
0

O código a seguir emula exatamente o comportamento dos ponteiros em 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    

Aqui estão alguns exemplos 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)

Mas agora é hora de fornecer um código mais profissional, incluindo a opção de excluir ponteiros, que acabei de encontrar na minha biblioteca pessoal:

# 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
fonte
Eu acho que você acabou de encaixar os valores de uma maneira estranha.
mpen 26/08/19
Você quer dizer: um "caminho inteligente" ou um "caminho não inteligente"?
MikeTeX 26/08/19
Estou lutando para ver um caso de uso válido para um armazenamento global indexado por um número aleatório.
MPEN
Exemplo de uso: sou engenheiro de algoritmos e tenho que trabalhar com programadores. Eu trabalho com Python e eles trabalham com C ++. Às vezes, eles me pedem para escrever um algoritmo para eles, e eu o escrevo o mais próximo possível do C ++, para sua conveniência. Os ponteiros são úteis, por exemplo, para árvores binárias etc.
#
Nota: se o armazenamento global incomodar, você poderá incluí-lo como uma variável global no nível da própria classe, o que provavelmente é mais elegante.
MikeTeX 26/08/19