【问题标题】:Pointers in Python?Python中的指针?
【发布时间】:2011-03-07 14:35:22
【问题描述】:

我知道 Python 没有指针,但是有没有办法让这个 yield 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

ideone


或者在 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;
}

【问题讨论】:

  • 也许我可以问一个类似于 S.Lott 的问题(但更有效率):你能给我们展示一些你想要做的真实代码吗?甚至可能是另一种更符合您口味的语言?您尝试解决的问题很可能会采用更 Pythonic 的解决方案,而专注于“我想要指针”会掩盖真正的答案。
  • 不需要太多想象;我可以想到几十个想要这样做的理由。这不是在 Python 等无指针语言中完成的方式。您需要将其包装在一个不变的容器中,如马特的回答中所述。
  • 您不会在 Python 中编写交换函数。你会写a, b = b, a
  • -1:问题中的结构是 (a) 毫无意义的,并且 (b) 似乎没有人能够提供一个使其合理的示例。说有“几十个原因”和发个例子是不一样的。
  • @Mark:另外,我不是“不同意”。我糊涂了。我在问一个问题,寻找一种方法来理解这是什么以及为什么你认为它如此重要。

标签: python pointers


【解决方案1】:

仅更改该行是无法做到的。你可以这样做:

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

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

【讨论】:

  • 这正是我讨厌 Python 和这些动态语言的那种不一致。 (是的,这并不是真正的“不一致”,因为您更改的是属性而不是引用,但我仍然不喜欢它)
  • @Mark:确实。我认识无数(好吧,少数)人,他们可能花费数小时在他们的代码中搜索“错误”,然后发现它是由未硬拷贝的列表引起的。
  • 没有不一致。它与伟大的静态与动态辩论无关。如果它们是对同一个 Java ArrayList 的两个引用,那么它将是相同的模语法。如果您使用不可变对象(如元组),则不必担心对象会通过另一个引用进行更改。
  • 我已经用过几次了,最常见的是解决 2.x 缺少“非本地”的问题。这不是最漂亮的事情,但在紧要关头它可以正常工作。
  • 这一点也不矛盾,因为您分配给ab 的对象是列表,而不是列表中的值。变量没有改变赋值,对象是同一个对象。如果它改变了,比如改变整数(每个都是不同的对象),a 现在将被分配给另一个对象,并且没有任何提示 b 效仿。在这里,a 没有被重新分配,而是分配给它的对象内部的一个值正在改变。由于b 仍然绑定到该对象,它将反映该对象以及其中值的更改。
【解决方案2】:

我想要form.data['field']form.field.value 始终拥有 相同的值

这是可行的,因为它涉及修饰名称和索引 - 即,完全与您所询问的 barenames ab 的构造不同大约,并且对于您的要求是完全不可能的。为什么要求一些不可能的东西并且与你真正想要的(可能的)东西完全不同?!

也许您没有意识到裸名和修饰名有多么不同。当你引用一个裸名a 时,你得到的正是对象a 最后一次绑定在这个范围内(或者如果它没有绑定在这个范围内,则是一个例外)——这是一个如此深刻和基础Python的一个方面,它不可能被颠覆。当您引用 装饰 名称 x.y 时,您是在要求一个对象(x 所指的对象)请提供“y 属性”——并对此作出回应请求时,对象可以执行完全任意的计算(索引非常相似:它还允许执行任意计算作为响应)。

现在,您的“实际需要”示例很神秘,因为在每种情况下都涉及两个级别的索引或属性获取,因此您渴望的微妙之处可以通过多种方式引入。例如,除了valueform.field 还应该具有哪些其他属性?如果没有进一步的.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])

如果 assignments 这样的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 中与您似乎想要的“指针”的含义大致相同——但至关重要的是要理解这种微妙之处只能与 indexing 和/或 装饰名称从不按照您最初的要求使用裸名!

【讨论】:

  • 我按我的方式问它,因为我希望得到一个通用的解决方案,它适用于所有“barenames”,而我当时并没有想到一个特殊的案例 - - 只是我在这个项目的前一次迭代中遇到了这个问题,我不想再遇到了。无论如何,这是一个很好的答案!然而,这种怀疑却不那么受欢迎。
  • @Mark,看看你 Q 上的 cmets,你会发现“怀疑”是一种普遍的反应——投票最多的 A 告诉你“不要这样做,得到在它上面”,然后是“它就是这样”。虽然您可能不会“欣赏”懂 Python 的人对您的原始规范感到惊讶,但他们本身就非常令人惊讶;-)。
  • 是的,但你似乎对我缺乏 Python 知识感到惊讶……好像我们是外来物种:P
【解决方案3】:

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

当您查看 Python 中的“=”运算符时,不要考虑赋值。你不分配东西,你绑定它们。 = 是一个绑定运算符。

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

来自类 C 语言,这可能会让人感到困惑,但是一旦你习惯了它,你会发现它可以帮助你更清楚地阅读和推理你的代码:名称为“b”的值不会除非你明确地改变它。如果你执行“import this”,你会发现 Python 的禅宗说显式优于隐式。

另外请注意,Haskell 等函数式语言也使用这种范式,在健壮性方面具有很大价值。

【讨论】:

  • 你知道,这样的答案我已经看了几十遍了,一直没看懂。 a = 1; b = a; a = 2; 的行为在 Python、C 和 Java 中完全相同:b 为 1。为什么要关注“= 不是赋值,而是绑定”?
  • 你确实分配了东西。这就是为什么它被称为assignment statement。你所说的区别没有意义。这与编译v.解释或静态v.动态无关。 Java 是一种带有静态类型检查的编译语言,它也没有指针。
  • C++ 呢? “b”可能是对“a”的引用。理解赋值和绑定之间的区别对于充分理解为什么 Mark 不能做他想做的事情以及 Python 等语言是如何设计的至关重要。从概念上讲(不一定在实现中),“a = 1”不会用 1 覆盖名为“a”的内存块;它将名称“a”分配给已经存在的对象“1”,这与 C 中发生的情况根本不同。这就是为什么指针作为一个概念不能在 Python 中存在——它们会在下次原始变量被“赋值”。
  • @d.w.:我喜欢这种思考方式! “绑定”是个好词。 @Ned:输出是相同的,是的,但是在 C 中,“1”的值被复制到 ab 而在 Python 中,它们都 refer相同“1”(我认为)。因此,如果您可以更改 1 的值(与对象一样),情况会有所不同。我听说这会导致一些奇怪的装箱/拆箱问题。
  • Python 和 C 之间的区别并不是“赋值”的意思。这就是“变量”的意思。
【解决方案4】:

是的!有一种方法可以在 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或字符串通过引用传递一个变量?
【解决方案5】:
>> 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)

同样,我们通过两个不同的名称 ab 引用同一个对象(列表)[1]。但是现在我们可以在这个列表保持不变的情况下改变它,ab 都将继续引用它

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

【讨论】:

  • 感谢介绍id功能。这解决了我的许多疑问。
  • 有什么办法可以让 b 总是指向 a 指向的任何东西?也就是说,如果我说a = 2,那么 b 会自动停止指向 1 而是指向 2。
【解决方案6】:

从某种角度来看,everything 在 Python 中是一个指针。您的示例很像 C++ 代码。

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

(更接近的等价物将使用某种类型的shared_ptr&lt;Object&gt; 而不是int*。)

这是一个例子:我想要 form.data['field'] 和 form.field.value 总是有 相同的值。这并不完全 有必要,但我认为这是 不错。

您可以通过在form.data 的类中重载__getitem__ 来做到这一点。

【讨论】:

  • form.data 不是一个类。是否有必要将其设为一个,还是我可以即时覆盖它? (这只是一个 python 字典)此外,数据必须有一个对 form 的引用才能访问这些字段......这使得实现这个丑陋。
【解决方案7】:

这是一个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

【讨论】:

    【解决方案8】:

    我不知道我的评论是否有帮助,但如果你想在 python 中使用pointers,你可以使用dictionaries 而不是变量

    假设在您的示例中将是

    a = {'value': 1}
    
    b = {'value': 2}
    

    然后你将 a 更改为 2

    a['value'] = 2
    
    print(b) #{'value': 2}
    

    【讨论】:

      【解决方案9】:

      我编写了以下简单类,实际上是在 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 notebook 页面):

      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]
      

      当然,也可以很容易地为对象的 dict 项或属性进行此操作。甚至有一种方法可以使用 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++ 编写,我可以用指针解决。

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        • 我认为您只是以一种奇怪的方式将值装箱。
        • 你的意思是:“聪明的方式”还是“不聪明的方式”?
        • Uhhh... 我正在努力寻找由随机数索引的全局存储的有效用例。
        • 使用示例:我是一名算法工程师,我必须和程序员一起工作。我使用 Python,他们使用 C++。有时,他们要求我为他们编写一个算法,为了方便起见,我尽可能将其编写为接近 C++。指针很有用,例如对于二叉树等。
        • 注意:如果全局存储麻烦,你可以将它作为一个全局变量包含在类本身的级别,这样可能更优雅。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-07
        • 1970-01-01
        • 1970-01-01
        • 2021-08-14
        相关资源
        最近更新 更多