Aanwijzingen in Python?

Ik weet dat Python geen pointers heeft, maar is er een manier om dit in plaats daarvan 2te laten opleveren

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

?


Hier is een voorbeeld: ik wil dat form.data['field']en form.field.valuealtijd dezelfde waarde hebben. Het is niet helemaal nodig, maar ik denk dat het wel leuk zou zijn.


In PHP kan ik dit bijvoorbeeld doen:

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

Uitvoer:

GeorgeGeorgeRalphRalph

ideone


Of zoals dit in C++ (ik denk dat dit juist is, maar mijn C++ is roestig):

#include <iostream>
using namespace std;
int main() {
    int* a;
    int* b = a;
    *a = 1;
    cout << *a << endl << *b << endl; # 1 1
    return 0;
}

Antwoord 1, autoriteit 100%

Ik wil form.data['field']en
form.field.valueom altijd de . te hebben
dezelfde waarde

Dit is mogelijk, omdat het om gedecoreerde namen en indexering gaat — dat wil zeggen, volledigverschillende constructies van de barenamesaen bwaar u naar vraagt, en waarvoor uw verzoek volkomen onmogelijk is. Waarom vragen om iets onmogelijks entotaal anders dan het (mogelijke) wat je eigenlijk wilt?!

Misschien realiseer je je niet hoe drastisch verschillende kale namen en versierde namen zijn. Wanneer u verwijst naar een kale naam a, krijgt u precies het object waar avoor het laatst aan was gebonden in dit bereik (of een uitzondering als het niet in dit bereik was gebonden ) — dit is zo’n diep en fundamenteel aspect van Python dat het onmogelijk kan worden ondermijnd. Wanneer je verwijst naar een versierdenaam x.y, vraag je een object (het object waar xnaar verwijst) om “de yattribuut” — en als reactie op dat verzoek kan het object totaal willekeurige berekeningen uitvoeren (en indexering is vrij gelijkaardig: het maakt het ook mogelijk om als reactie willekeurige berekeningen uit te voeren).

Uw voorbeeld van ‘werkelijke wensen’ is mysterieus omdat er in elk geval twee niveaus van indexering of het verkrijgen van attributen bij betrokken zijn, dus de subtiliteit waarnaar u verlangt, kan op vele manieren worden geïntroduceerd. Welke andere attributen zou form.fieldbijvoorbeeld moeten hebben, naast value? Zonder die verdere .valueberekeningen, zouden de volgende mogelijkheden zijn:

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

en

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

De aanwezigheid van .valuesuggereert het kiezen van de eerste vorm, plus een soort nutteloze wrapper:

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

Als toewijzingenzoals form.field.value = 23ook de invoer in form.datamoeten instellen, dan moet de wrapper inderdaad complexer worden, en niet zo nutteloos:

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)

Het laatste voorbeeld komt in Python ongeveer zo dicht mogelijk bij de betekenis van “een aanwijzer” als je lijkt te willen — maar het is cruciaal om te begrijpen dat dergelijke subtiliteiten alleen kunnen werken met indexeringen/of versierde namen, nooitmet kale namen zoals je oorspronkelijk vroeg!


Antwoord 2, autoriteit 93%

Je kunt dat op geen enkele manier doen door alleen die regel te wijzigen. Je kunt het volgende doen:

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

Dat creëert een lijst, wijst de verwijzing toe aan a, en vervolgens b, gebruikt de verwijzing a om het eerste element in te stellen op 2 en opent vervolgens met behulp van de verwijzingsvariabele b.


Antwoord 3, autoriteit 73%

Het is geen bug, het is een functie 🙂

Als je naar de operator ‘=’ in Python kijkt, denk dan niet in termen van toewijzing. Je wijst geen dingen toe, je bindt ze. = is een bindende operator.

Dus in je code geef je de waarde 1 een naam: a. Vervolgens geef je de waarde in ‘a’ een naam: b. Dan bind je de waarde 2 aan de naam ‘a’. De waarde gebonden aan b verandert niet in deze bewerking.

Afkomstig van C-achtige talen, kan dit verwarrend zijn, maar als je er eenmaal aan gewend bent geraakt, zul je merken dat het je helpt om je code duidelijker te lezen en te redeneren: de waarde met de naam ‘b’ zal niet wijzigen, tenzij u deze expliciet wijzigt. En als je dit ‘importeert’, zul je merken dat de Zen van Python stelt dat expliciet beter is dan impliciet.

Merk ook op dat functionele talen zoals Haskell dit paradigma ook gebruiken, met grote waarde in termen van robuustheid.


Antwoord 4, autoriteit 54%

Ja! er is een manier om een variabele als aanwijzer in python te gebruiken!

Het spijt me te moeten zeggen dat veel van de antwoorden gedeeltelijk fout waren. In principe deelt elke equal(=) toewijzing het geheugenadres (check de id(obj) functie), maar in de praktijk is dat niet zo. Er zijn variabelen waarvan het equal(“=”)-gedrag in de laatste term werkt als een kopie van de geheugenruimte, meestal in eenvoudige objecten (bijv. “int”-object), en andere waarin niet (bijv. “list”,”dict”-objecten) .

Hier is een voorbeeld van het toewijzen van een aanwijzer

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

Hier is een voorbeeld van kopieertoewijzing

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

Aanwijzer toewijzen is een behoorlijk handig hulpmiddel voor aliasing zonder verspilling van extra geheugen, in bepaalde situaties voor het uitvoeren van comfortabele code,

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

maar men moet zich bewust zijn van dit gebruik om codefouten te voorkomen.

Tot slot, sommige variabelen zijn standaard barenames (eenvoudige objecten zoals int, float, str,…), en sommige zijn pointers wanneer ze ertussen worden toegewezen (bijv. dict1 = dict2). Hoe ze te herkennen? probeer gewoon dit experiment met hen. In IDE’s met een variabel verkennerpaneel lijkt het geheugenadres (“@axbbbbbb…”) in de definitie van pointer-mechanisme-objecten te zijn.

Ik raad aan om het onderwerp te onderzoeken. Er zijn zeker veel mensen die veel meer over dit onderwerp weten. (zie module “ctypes”). Ik hoop dat het nuttig is. Geniet van het goede gebruik van de objecten! Met vriendelijke groet, José Crespo


5, Autoriteit 23%

Vanuit het uiterste oogpunt is alles een aanwijzer in Python. Uw voorbeeld werkt veel op de C++ -code.

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

(Een beter equivalent zou een soort shared_ptr<Object>gebruiken in plaats van int*.)

Hier is een voorbeeld: ik wil
form.data[‘field’] en
form.field.value om altijd de . te hebben
dezelfde waarde. Het is niet helemaal
nodig, maar ik denk dat het zou zijn
leuk.

Je kunt dit doen door __getitem__in de klasse van form.datate overladen.


Antwoord 6, autoriteit 8%

Dit is een python-aanwijzer (anders dan 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

Antwoord 7

Ik heb de volgende eenvoudige klasse geschreven als, in feite, een manier om een aanwijzer in python te emuleren:

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)

Hier is een gebruiksvoorbeeld (van een jupyter-notebookpagina):

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]

Natuurlijk is het ook gemakkelijk om dit te laten werken voor dict-items of attributen van een object. Er is zelfs een manier om te doen waar de OP om vroeg, met 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)

Hiermee worden er 2 afgedrukt, gevolgd door 3.

Op deze manier knoeien met de globale naamruimte is een soort van transparant idee, maar het toont aan dat het mogelijk is (als het niet raadzaam is) om te doen waar de OP om vroeg.

Het voorbeeld is natuurlijk redelijk zinloos. Maar ik heb deze klasse nuttig gevonden in de applicatie waarvoor ik het heb ontwikkeld: een wiskundig model waarvan het gedrag wordt beheerst door talrijke gebruikers-instelbare wiskundige parameters, van diverse types (die, omdat ze afhangen van opdrachtregelargumenten, zijn niet bekend bij compileertijd). En zodra de toegang tot iets is ingekapseld in een parameterobject, kunnen al dergelijke objecten op een uniforme manier worden gemanipuleerd.

Hoewel het er niet veel uitziet als een C- of C++ -pointer, is dit een probleem op dat ik met aanwijzingen zou hebben opgelost als ik in C++ aan het schrijven was.


8

De volgende code emuleert precies het gedrag van de wijzers in 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    

Hier zijn voorbeelden van gebruik:

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)

Maar het is nu tijd om een ​​meer professionele code te geven, inclusief de optie om aanwijzingen te verwijderen, die ik net heb gevonden in mijn persoonlijke bibliotheek:

# 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

Other episodes