Python中的指针?


124

我知道Python没有指针,但是有办法提高产量 2,而不是

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


这是一个例子:我想要form.data['field']form.field.value始终具有相同的值。并非完全必要,但我认为这会很好。


例如,在PHP中,我可以这样做:

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

输出:

GeorgeGeorgeRalphRalph

异丁酮


或在C ++中这样(我认为这是正确的,但我的C ++生锈了):

#include <iostream>
using namespace std;

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

    return 0;
}

28
也许我可以问一个类似于S.Lott的问题(但效率更高):您可以向我们展示一些实际的代码吗?也许即使是另一种更适合您的语言?您尝试解决的问题很可能会使其更适合Pythonic的解决方案,而专注于“我想要指针”会掩盖真正的答案。
Ned Batchelder

8
不需要太多的想象力;我可以想到许多这样做的理由。这不是在像Python这样的无指针语言中完成的;您需要将其包装在一个不变的容器中,如马特的答案。
Glenn Maynard 2010年

14
您不会在Python中编写交换函数。你会写a, b = b, a
dan04 2010年

3
-1:问题中的构造是(a)毫无意义,并且(b)似乎没有人能够提供使它变得明智的示例。说有“数十个原因”与发布示例不同。
S.Lott

1
@Mark:另外,我不是“不同意”。我很困惑。我在问一个问题,寻找一种了解这是什么以及为什么您认为它如此重要的方法。
S.Lott

Answers:


48

我希望form.data['field']form.field.value始终拥有相同的价值

这是可行的,因为它涉及修饰的名称和索引-即,与完全不同的结构以及您所要询问的内容,对于您的请求来说是完全不可能的。为什么要索要不可能的东西而这又与您实际想要的(可能的)东西完全不同? ab

也许您不知道裸名和修饰名有多么大的不同。当您引用裸名时a,您将确切地知道该对象a在该范围内最后绑定到该对象(如果未在该范围内绑定则为例外),这是Python如此深入和基本的方面,它可以不可能被颠覆。当您引用修饰的名称时x.y,您正在要求一个对象(该对象所x引用的)请提供“ y属性”-响应该请求,该对象可以执行完全任意的计算(并且索引非常相似:它还允许作为响应执行任意计算)。

现在,您的“实际需求”示例很神秘,因为在每种情况下都涉及两个级别的索引编制或属性获取,因此可以通过多种方式引入您渴望的精妙之处。form.field例如,假设还具有其他哪些属性value?如果没有进一步的.value计算,可能性将包括:

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

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

的存在.value表明采摘第一种形式,加上一种-的无用的包装:

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

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

如果还应该将这样的分配form.field.value = 23设置为中的条目form.data,则包装器的确必须变得更加复杂,并且不是所有的没用的:

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)

后面的示例在Python中与您似乎想要的“指针”意义大致相近-但至关重要的是要了解这样的微妙只能与索引和/或修饰的名称一起使用决不能像您最初要求的那样使用裸名!


23
我这样问,是因为我希望得到一个通用的解决方案,该解决方案适用于从“ barenames”开始的所有事情,而且当时我没有特别的想法-只是我曾经遇到过这种情况该项目的上一个迭代存在问题,我不想再次遇到。无论如何,这是一个很好的答案!然而,这种怀疑却鲜为人知。
mpen 2010年

2
@Mark,看看您Q上的评论,您会发现“怀疑”是一种普遍的反应-投票最多的A告诉您“不要这样做,克服它”,然后是一个去“就是这样”。尽管您可能不“欣赏” Python知识渊博的人,他们对您的原始规格感到惊讶,但他们自己相当惊讶;-)。
Alex Martelli 2010年

30
是的,但是您似乎对我缺乏Python知识感到惊讶……好像我们是外来物种:P
mpen

51

您无法更改那条线。你可以做:

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

这将创建一个列表,将引用分配给a,然后再将b分配给a,使用a引用将第一个元素设置为2,然后使用b引用变量进行访问。


17
这正是我讨厌Python和这些动态语言的那种不一致。(是的,并不是真的“不一致”,因为您正在更改属性而不是引用,但我仍然不喜欢它)
mpen 2010年

10
@Mark:的确如此。我知道无数(好几个)人花了几个小时在他们的代码中搜索“错误”,然后发现它是由于列表没有被复制而造成的。
houbysoft 2010年

14
没有矛盾。它与巨大的静态诉动态辩论无关。如果它们是对同一Java ArrayList的两个引用,则将是相同的模语法。如果您使用不可变的对象(例如元组),则不必担心对象会通过另一个引用进行更改。
马修·弗拉申

我已经使用过几次,最常用的方法是解决2.x缺少“非本地”的问题。这不是最漂亮的事情,但在紧急情况下效果很好。
Glenn Maynard 2010年

1
这是不是在所有不一致的,因为你要分配对象a,并b在列表,不在列表中的值。变量未更改分配,对象是同一对象。如果更改,则就像更改整数(每个整数都是不同的对象)的情况一样,a现在将其分配给另一个对象,并且没有任何提示b可仿效。在这里,a并没有被重新分配,而是分配给它的对象内部的值正在改变。由于b仍绑定到该对象,它将反映该对象以及其中值的更改。
arkigos

34

这不是一个错误,这是一个功能 :-)

当您在Python中查看'='运算符时,不要以赋值的方式思考。您不分配东西,而是绑定它们。=是绑定运算符。

因此,在代码中,您给值1命名:然后,为“ a”中的值命名:b。然后,将值2绑定到名称'a'。绑定到b的值在此操作中不会更改。

来自类似C的语言,这可能会造成混淆,但是一旦您习惯了它,就会发现它可以帮助您更清晰地阅读和推理代码:除非您明确地更改它。而且,如果您执行“导入此操作”,您会发现Python的Zen指出显式要好于隐式。

还要注意的是,诸如Haskell之类的功能语言也使用此范例,就健壮性而言具有极大的价值。


39
您知道,我已经读过数十遍这样的答案,但我从未理解过。a = 1; b = a; a = 2;在Python,C和Java中,行为完全相同:b为1。为什么要关注“ =不是赋值,而是绑定”?
Ned Batchelder

4
您确实要分配东西。这就是为什么它被称为赋值语句。您要说明的区别没有意义。这与编译的v。解释的或静态v。动态的无关。Java是具有静态类型检查的编译语言,并且它也没有指针。
马修·弗拉申

3
C ++呢?“ b”可能是对“ a”的引用。理解赋值和绑定之间的区别对于全面理解Mark为什么不能做自己想做的事情以及Python等语言的设计方式至关重要。从概念上(不一定在实现中),“ a = 1”不会用1覆盖名为“ a”的内存块。它将名称“ a”分配给已存在的对象“ 1”,这与C中发生的情况根本不同。这就是为什么指针作为概念在Python中不存在的原因-下次它们将变得陈旧原始变量已“分配”。
格伦·梅纳德

1
@dw:我喜欢这种思考方式!“绑定”是一个好词。@Ned:输出是相同的,是的,但在C的“1”的值被复制到两个ab在Python而,它们都同一 “1”(我想)。因此,如果您可以更改值1(与对象一样),则该值将有所不同。我听说这导致一些奇怪的装箱/拆箱问题。
mpen

4
Python和C之间的区别不是“赋值”的含义。这就是“变量”的意思。
dan04 2010年

28

是! 有一种方法可以将变量用作python中的指针!

我很遗憾地说,许多答案部分不正确。原则上,每个equal(=)分配都共享内存地址(检查id(obj)函数),但实际上并非如此。有一些变量的equal(“ =”)行为在上学期可以用作存储空间的副本,主要在简单对象(例如“ int”对象)中起作用,而其他变量在其中不起作用(例如“ list”,“ dict”对象) 。

这是指针分配的示例

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

这是副本分配的示例

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

指针分配是一种非常有用的工具,它可以在某些情况下使用别名来执行别名,而不会浪费额外的内存,

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

但是为了防止代码错误,必须注意这种用法。

总而言之,默认情况下,某些变量为准名称(简单对象,如int,float,str,...),而某些变量在它们之间分配时为指针(例如dict1 = dict2)。如何识别它们?只需与他们一起尝试此实验。在具有可变资源管理器面板的IDE中,指针机制对象的定义中通常显示为内存地址(“ @axbbbbbb ...”)。

我建议对该主题进行调查。肯定有很多人对此主题有更多的了解。(请参见“ ctypes”模块)。希望对您有所帮助。享受物品的良好使用!问候,何塞·克雷斯波


所以我必须使用字典通过引用将变量传递给函数,而不能使用int或字符串通过引用传递变量?
山姆

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

如您所见ab只是引用同一不变对象(int)的两个不同名称1。如果稍后编写a = 2,则将名称重新分配a另一个对象(int)2,但b继续引用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)

如果您有一个可变对象,例如列表,将会发生什么[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)

同样,我们[1]通过两个不同的名称a和引用同一对象(列表)b。然而,现在虽然仍然是相同的对象,我们可以变异这个名单,和ab都将继续引用它

>> a[0] = 2
>> a
[2]
>> b
[2]
>> id(a)
328817800  # same as before
>> id(b)
328817800  # same as before

1
感谢您介绍id函数。这解决了我的许多疑问。
haudoing

12

从一个角度来看,一切都是Python中的指针。您的示例的工作原理与C ++代码非常相似。

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

(更接近的等效项将使用某种类型的shared_ptr<Object>代替int*。)

这是一个示例:我希望form.data ['field']和form.field.value始终具有相同的值。并非完全必要,但我认为这会很好。

您可以通过__getitem__form.data类中重载来实现。


form.data不是上课。是否有必要将其设置为一个,还是可以即时覆盖?(这只是一个python字典)此外,数据还必须返回引用form以访问字段...这使得实现起来很丑。
mpen 2010年

1

这是python指针(与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

0

我写了以下简单的类作为有效地在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)

这是一个使用示例(来自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]

当然,对对象的字典项或属性进行这项工作也很容易。甚至可以使用globals()来完成OP所要求的操作:

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)

这将打印2,然后打印3。

以这种方式来处理全局名称空间显然是一个可怕的想法,但它表明可以(如果不建议这样做)执行OP所要求的操作。

这个例子当然是毫无意义的。但是我发现该类在我为其开发的应用程序中很有用:一种数学模型,其行为由众多用户可设置的,各种类型的数学参数(由于它们取决于命令行参数而未知)控制在编译时)。并且一旦将访问权限封装在Parameter对象中,就可以用统一的方式操纵所有此类对象。

尽管它看起来不太像C或C ++指针,但这正在解决一个问题,如果我用C ++编写的话,我将使用指针解决该问题。


0

以下代码完全模拟了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    

以下是使用示例:

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)

但是现在是时候提供更专业的代码了,包括删除指针的选项,我刚刚在我的个人库中找到了它:

# 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

我认为您刚刚以一种奇怪的方式将这些值装箱了。
mpen

您的意思是:“聪明的方式”还是“不聪明的方式”?
MikeTeX

呃...我正在努力寻找一个由随机数索引的全局存储的有效用例。
mpen

使用示例:我是一名算法工程师,必须与程序员合作。我使用Python,并且它们使用C ++。有时,他们要求我为他们编写一个算法,为了方便起见,我将其写得尽可能接近C ++。指针非常有用,例如对于二叉树等
。– MikeTeX

注意:如果全局存储很麻烦,则可以在类本身的级别将其作为全局变量包括在内,这可能更美观。
MikeTeX
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.