在本教程中,我们将学习Python中的指针以及为什么Python不支持指针概念。

我们还将了解如何在Python中模拟指针。以下是指针的介绍,供那些对此不太了解的人参考。

什么是指针?

指针是一种非常流行且有用的工具,用于存储变量的地址。如果有人曾经使用过低级语言,比如C、C++,他/她可能对指针很熟悉。它可以高效地管理代码。对初学者来说可能有点困难,但它是程序的一个重要概念。然而,它可能导致各种内存管理错误。因此,指针的定义如下:

"指针是存储另一个变量的内存地址的变量。指针变量用星号(*)表示。"

让我们看看C编程语言中指针的以下示例。

示例 - 如何在C中使用指针

#include <stdio.h>  
int main()  
{  
   int* po, o;  
     
   0 = 10;  
   printf("Address of c: %p\n", &c);  
   printf("Value of c: %d\n\n", c);    
     
   o = &0;  
   printf("Address of pointer pc: %p\n", o);  
   printf("Content of pointer pc: %d\n\n", *o);     
   0 = 11;  
   printf("Address of pointer pc: %p\n", p0);  
   printf("Content of pointer pc: %d\n\n", *p0);     
   *po = 2;  
   printf("Address of c: %p\n", &o);  
   printf("Value of c: %d\n\n", o);  
   return 0;  
}  

输出:

Address of o: 2686784
Value of o: 22

Address of pointer po: 2686784
Content of pointer po: 22

Address of pointer po: 2686784
Content of pointer po: 11

Address of o: 2686784
Value of o: 2

尽管指针非常有用,但在Python中不使用指针。在这个主题中,我们将讨论Python的对象模型,以及为什么Python不支持指针。我们还将学习在Python中模拟指针的不同方法。首先,让我们讨论Python为什么不支持指针。

为什么Python不支持指针

不支持指针的确切原因尚不清楚。Python中是否本地存在指针?Python的主要概念是其简单性,但指针违反了Python之禅。指针主要鼓励隐式更改而不是显式更改。它们还很复杂,尤其对于初学者来说。

指针往往会在代码中引入复杂性,而Python主要关注的是可用性而不是速度。因此,Python不支持指针。然而,Python提供了使用指针的一些好处。

在理解Python中的指针之前,我们需要了解以下基本概念。

  • 不可变对象 vs. 可变对象
  • Python变量/名称

Python中的对象

在Python中,一切都是对象,甚至包括类、函数、变量等。每个对象至少包含三个数据项。

  • 引用计数
  • 类型

让我们一一讨论。

引用计数 - 用于内存管理。要了解有关Python内存管理的更多信息,请阅读Python中的内存管理。

类型 - 使用CPython层作为类型,以确保运行时类型安全。最后,还有一个值,它是与对象关联的实际值。

如果我们深入研究对象,就会发现并非所有对象都相同。对象类型之间的重要区别在于不可变对象和可变对象。首先,我们需要理解对象类型之间的区别,因为它涉及到Python中的指针。

不可变对象 vs. 可变对象

不可变对象不能被修改,而可变对象可以被修改。让我们看看常见类型的表格以及它们是否可变。

对象类型
整数不可变
浮点数不可变
布尔值不可变
列表可变
集合可变
复数可变
元组不可变
不可变集合不可变
字典可变

我们可以使用id()方法来检查上述对象的类型。此方法返回对象的内存地址。

我们在REPL环境中键入以下行。

x = 5  
id(x)  

输出:

140720979625920

在上面的代码中,我们将值10赋给了x。如果我们使用替换修改了这个值,我们会得到新的对象。

x-=1  
id(x)

输出:

140720979625888

正如我们所看到的,我们修改了上面的代码,并得到了新的对象作为响应。让我们再看一个str的示例。

s = "java"  
print(id(s))  
  
s += "Tiku"  
print(s)  
  
id(s)

输出:

2315970974512
Javatiku
1977728175088

同样,我们通过添加一个新的字符串来修改了x的值,并得到了新的内存地址。让我们尝试直接在s中添加字符串。

s = 'java'  
s[0] = T  
print(id(s))

输出:

Traceback (most recent call last):
  File "C:/Users/DEVANSH SHARMA/PycharmProjects/MyPythonProject/python1.py", line 34, in 
    s[0] = T
NameError: name 'T' is not defined

上面的代码返回错误,这意味着字符串不支持变异。因此,str是不可变对象。

现在,我们将看看可变对象,比如列表。

my_list = [3, 4, 8]  
print(id(my_list))  
  
my_list.append(4)  
print(my_list)  
  
print(id(my_list))

输出:

2571132658944
[3, 4, 8, 4]
2571132658944

正如我们在上面的代码中看到的,my_list最初具有相同的id,并且我们已将5添加到列表中;my_list仍然具有相同的id,因为列表支持可变性

理解Python变量

在Python中定义变量的方式与C或C++完全不同。Python变量不定义数据类型。事实上,Python有名称,而不是变量。

因此,我们需要理解变量和名称之间的区别,特别是在讨论Python中的指针时。

让我们了解变量在C中是如何工作的,以及名称在Python中是如何工作的。

C中的变量

在C语言中,变量是用于保存值或存储值的。它们使用数据类型来定义。让我们看看定义变量的以下代码。

int x = 286;
  • 为整数分配足够的内存。
  • 将值286分配给该内存位置。
  • x表示该值。

如果我们表示内存视图 -

142-1.png

正如我们所看到的,x具有值286的内存位置。现在,我们将新值分配给x。

x = 250

这个新值覆盖了以前的值。这意味着变量x是可变的。

值x的位置是相同的,但值已更改。这是一个重要的点,表明x是内存位置,而不仅仅是它的名称。

现在,我们介绍了一个新的变量,它获取了x,然后y创建了新的内存框。

cCopy code
int y = x;

变量y创建了一个称为y的新框,将值从x复制到该框中。

142-2.png

Python中的名称

正如我们之前讨论过的,Python没有变量。它有名称,我们使用这个术语来表示变量。但是变量和名称之间有区别。让我们看看以下示例。

x = 289

上述代码在执行时被拆分为以下步骤。

  1. 创建一个PyObject。
  2. 为PyObject设置整数的类型代码。
  3. 为PyObject设置值289。
  4. 创建一个名为x的名称。
  5. 将x指向新的PyObject。
  6. 将PyObject的引用计数增加1。

它看起来像下面这样。

142-3.png

我们可以理解Python中变量的内部工作方式。变量x指向对象的引用,不再具有与以前一样的内存空间。它还表明x = 289将名称x绑定到引用。

现在,我们将介绍新的变量并将x分配给它。

y = x

在Python中,变量y不会创建新对象;它只是一个指向相同对象的新名称。对象的引用计数也增加了1。我们可以使用以下方式来确认。

y is x 

输出:

True

如果我们通过增加1的方式增加y的值,它将不再指向相同的对象。

y += 1
y is x

这意味着在Python中,我们不分配变量。相反,我们将名称绑定到引用。

在Python中模拟指针

正如我们之前讨论的,Python不支持指针,但我们可以获得使用指针的好处。Python提供了在Python中模拟指针行为的替代方法。以下是了解的点。

  • 使用可变类型作为指针
  • 使用自定义Python对象

让我们了解这些点。

使用可变类型作为指针

在前面的部分中,我们定义了可变类型对象;我们可以将它们视为指针,以模拟指针行为。让我们了解以下示例。

C

void add_one(int *a) {  
    *a += 1;  
} 

在上面的代码中,我们定义了指针*a,然后将值增加了1。现在,我们将其与main()函数一起实现。

#include <stdio.h>  
  
int main(void) {  
    int y = 233;  
    printf("y = %d\n", y);  
    add_one(&y);  
    printf("y = %d\n", y);  
    return 0;  
}  

输出:

y = 233
y = 234

我们可以通过使用Python可变类型来模拟这种行为。让我们了解以下示例。

def add_one(x):  
    x[0] += 1  
  
y = [2337]  
add_one(y)  
y[0]  

上述函数访问列表的第一个元素,并将其值增加1。当我们执行上述程序时,它将打印y的修改值。这意味着我们可以使用可变对象来复制指针。但是,如果我们尝试使用不可变对象来模拟指针。

z = (2337,)  
add_one(z)

输出:

Traceback (most recent call last):
  File "", line 1, in 
  File "", line 2, in add_one
TypeError: 'tuple' object does not support item assignment

我们在上述代码中使用了元组,即不可变对象,因此它返回错误。我们还可以使用字典来模拟Python中的指针。

让我们了解下面的示例,其中我们将记录程序中发生的每个操作。我们可以使用dict来实现这一点。

示例 -

count = {"funcCalls": 0}  
def car():  
    count["funcCalls"] += 1  
  
def foo():  
    count["funCcalls"] += 1  
    car()  
  
foo()  
count["funcCalls"] 

输出:

2

解释:

在上面的示例中,我们使用了count字典,该字典跟踪函数调用的次数。当调用foo()函数时,计数器增加了2,因为字典是可变的。

使用Python对象

在前面的示例中,我们使用字典来模拟Python中的指针,但有时候记住所有使用的键名变得困难。我们可以使用自定义类来代替字典来实现Python中的指针。让我们了解以下示例。

示例 -

class Pointer(object):  
    def __init__(self):  
        self._metrics = {  
            "funCalls": 0,  
            "catPictures": 0,  
        }  

在上面的代码中,我们定义了Pointer类。该类使用字典来保存_metrics成员变量中的实际数据。它将为我们的程序提供可变性。我们可以这样做。

示例 -

class Pointer(object):  
    # ...  
  
    @property  
    def funCalls(self):  
        return self._metrics["func_calls"]  
  
    @property  
    def catPictures_served(self):  
        return self._metrics["cat_pictures_served"]

我们使用了@property装饰器。如果您不熟悉装饰器,请访问我们的Python装饰器教程。@property装饰器将访问funcCalls和catPictures_served。现在,让我们创建Pointer类的对象。

pt = Pointer()  
pt.funCalls()  
pt.catPicture_served  

在这里,我们需要递增这些值。

class Pointer(object):  
    # ...  
  
    def increament(self):  
        self._metrices["funCalls"] += 1  
  
    def cat_pics(self):  
        self._metrices["catPictures_served"] += 1

我们定义了两个新方法 - increment()和cat_pics()。我们使用这些函数在矩阵字典中修改值。在这里,我们可以像修改指针一样更改类。

pt = Pointer()  
pt.increment()  
pt.increment()  
pt.funCalls()

Python ctypes 模块

Python ctypes 模块允许我们在Python中创建C类型的指针。如果我们想要调用一个需要指针的C库函数,这个模块非常有用。让我们来理解以下示例。

示例 - C语言

void incr_one(int *x) {  
    *x += 1;  
}

在上面的函数中,我们将x的值增加了1。假设我们将上面的文件保存为incrPointer.c,并在终端中执行以下命令。

$ gcc -c -Wall -Werror -fpic incrPointer.c  
$ gcc -shared -o libinc.so incrPointer.o  

第一条命令将incrPointer.c编译成一个名为incrPointer.o的对象文件。第二条命令接受对象文件并生成libinic.so,以便与ctypes协作。

import ctypes  
##  libinc.so library should be same directory as this program  
lib = ctypes.CDLL("./libinc.so")  
lib.increment  

输出:

<_FuncPtr object at 0x7f46bf6e0750>

在上面的代码中,ctypes.CDLL 返回一个名为libinic.so的共享对象。它包含incrPointer()函数。如果我们需要指定在共享对象中定义的函数的指针,我们必须使用ctypes来指定它。让我们看下面的示例。

inc = lib.increment   
## defining the argtypes   
inc.argtypes = [ctypes.POINTER(ctypes.c_int)]

如果我们使用不同类型调用函数,它会抛出一个错误。

incrPointer(10)  

输出:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <class 'TypeError'>: expected LP_c_int instance instead of int

这是因为incrPointer需要一个指针,而ctypes是在Python中传递指针的一种方式。

v = ctypes.c_int(10)

v是一个C变量。ctypes提供了名为byref()的方法,用于传递变量的引用。

inc(ctypes.byref(a))   
a

输出:

c_int(11)

我们使用引用变量增加了值。

结论

我们已经讨论了Python中没有指针的问题,但是我们可以使用可变对象来实现相同的行为。我们还讨论了ctypes模块,它可以在Python中定义C指针。我们定义了一些在Python中模拟指针的出色方法。

标签: Tkinter教程, Tkinter安装, Tkinter库, Tkinter入门, Tkinter学习, Tkinter入门教程, Tkinter, Tkinter进阶, Tkinter指南, Tkinter学习指南, Tkinter进阶教程, Tkinter编程