Python中的指针 | 为什么Python不支持指针
在本教程中,我们将学习Python中的指针并了解为什么Python不支持指针概念。
我们还将了解如何模拟Python中的指针。以下是对指针的介绍,供那些对它一无所知的人参考。
我们还将了解如何模拟Python中的指针。以下是对指针的简介,供那些对它一无所知的人参考。
什么是指针
指针是一种非常流行和有用的工具,用于存储变量的地址。如果有人曾经使用过低级语言,例如C、C++,他/她可能对指针很熟悉。它可以高效地管理代码。对于初学者来说可能会有些困难,但它是程序的重要概念之一。然而,它可能导致各种内存管理错误。因此,指针的定义是:
“指针是保存另一个变量的内存地址的变量。指针变量用星号(*)表示。”
让我们看一个C编程语言中使用指针的示例。
示例如何在C中使用指针
#include
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. 可变对象
不可变对象不能被修改,而可变对象可以被修改。让我们来看一下常见类型的以下表格,以及它们是否可变。
Objects | Type |
---|---|
Int | Immutable |
Float | Immutable |
Bool | Immutable |
List | Mutable |
Set | Mutable |
Complex | Mutable |
Tuple | Immutable |
Frozenset | Immutable |
Dict | Mutable |
我们可以使用 id() 方法来检查上面对象的类型。这个方法返回对象的内存地址。
我们在一个REPL环境中输入以下行。
x = 5
id(x)
输出:
140720979625920
在上面的代码中,我们将值10赋给了x。如果我们使用替代修改这个值,我们将获得新的对象。
x-=1
id(x)
输出:
140720979625888
正如我们所看到的,我们修改了上述代码并获得了新的对象作为响应。让我们看另一个 str 的示例。
s = "java"
print(id(s))
s += "Tpoint"
print(s)
id(s)
输出:
2315970974512
JavaTpoint
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代表那个值。
如果我们表示内存的视图 –
正如我们所见,x有一个内存位置,用于存储值286。现在,我们将为x分配新的值。
x = 250
这个新值覆盖了之前的值。这意味着变量x是可变的。
x的值位置保持不变,但值发生了变化。这是一个重要的点,表明x是内存位置,而不仅仅是它的名称。
现在,我们引入了一个新的变量,它使用x,然后y创建了一个新的内存盒子。
int y = x;
变量y创建了一个叫做y的新框,将x的值复制到这个框中。
Python中的变量名
正如我们之前讨论的,Python没有变量,而是使用名称来表示变量。但是变量和名称之间存在区别。我们来看下面的示例。
x = 289
上述代码在执行过程中出现了错误。
- 创建一个PyObject对象
- 为PyObject对象设置类型码为整数
- 将PyObject对象的值设为289
- 创建一个名为x的变量
- 将x指向新的PyObject对象
- 将PyObject对象的引用计数增加1
它的样子会像下面这样。
我们可以理解Python中变量的内部工作原理。变量x指向对象的引用,它不再具有以前的内存空间。它还表明x = 289将名称x绑定到一个引用。
现在,我们引入新的变量并将x赋给它。
y = x
在Python中,变量y不会创建新的对象;它只是一个指向同一对象的新名称。对象的 引用计数(refcount) 也增加了一个。我们可以通过以下方式进行确认。
y is x
输出:
True
如果我们将y的值增加一,它将不再引用相同的对象。
y + =1
y is x
这意味着,在Python中,我们不分配变量,而是绑定名字到引用。
在Python中模拟指针
正如我们所讨论的,Python不支持指针,但我们可以获得使用指针的好处。Python提供了使用指针的替代方法。以下是这两种方法。
- 使用可变类型作为指针
- 使用自定义的Python对象
让我们了解给定的要点。
使用可变类型作为指针
在前面的部分中,我们已经定义了可变类型对象;我们可以把它们当作指针来处理,以模拟指针的行为。让我们了解以下示例。
C
void add_one(int *a) {
*a += 1;
}
在上面的代码中,我们定义了指针*a,然后我们将值增加了一。现在,我们将使用main()函数来实现它。
#include
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]
以上函数访问列表的第一个元素并将其值加一。当我们执行上述程序时,它会打印出修改后的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中,我们也可以使用字典来模拟指针。
让我们了解下面的示例,我们将计算程序中发生的每个操作。我们可以使用字典来实现这一点。
示例
count = {"funcCalls": 0}
def car():
count["funcCalls"] += 1
def foo():
count["funCcalls"] += 1
car()
foo()
count["funcCalls"]
输出:
2
解释 –
在上面的示例中,我们使用了 count 字典,它会跟踪函数调用的次数。当调用 foo() 函数时,计数器增加2,因为dict是可变的。
使用Python对象
在前面的示例中,我们使用了dict来模拟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装饰器将访问funCalls和catPicture_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()。我们在 matrices 字典中使用这些函数来修改值。在这里,我们可以像修改指针一样修改类。
pt = Pointer()
pt.increment()
pt.increment()
pt.funCalls()
Python ctypes模块
Python ctypes模块允许我们在Python中创建C类型的指针。如果我们想要调用一个需要指针的C库函数时,这个模块很有帮助。让我们了解以下示例。
示例C语言
void incr_one(int *x) {
*x += 1;
}
在上面的函数中,我们将x的值增加了一。假设我们将上述文件保存为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中模拟指针的优秀方法。