Python中的指针 | 为什么Python不支持指针

Python中的指针 | 为什么Python不支持指针

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

我们还将了解如何模拟Python中的指针。以下是对指针的介绍,供那些对它一无所知的人参考。

我们还将了解如何模拟Python中的指针。以下是对指针的简介,供那些对它一无所知的人参考。

什么是指针

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

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

让我们看一个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代表那个值。

如果我们表示内存的视图 –

Python中的指针 | 为什么Python不支持指针

正如我们所见,x有一个内存位置,用于存储值286。现在,我们将为x分配新的值。

x = 250

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

x的值位置保持不变,但值发生了变化。这是一个重要的点,表明x是内存位置,而不仅仅是它的名称。

现在,我们引入了一个新的变量,它使用x,然后y创建了一个新的内存盒子。

int y = x;

变量y创建了一个叫做y的新框,将x的值复制到这个框中。

Python中的指针 | 为什么Python不支持指针

Python中的变量名

正如我们之前讨论的,Python没有变量,而是使用名称来表示变量。但是变量和名称之间存在区别。我们来看下面的示例。

x = 289

上述代码在执行过程中出现了错误。

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

它的样子会像下面这样。

Python中的指针 | 为什么Python不支持指针

我们可以理解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中模拟指针的优秀方法。

Camera课程

Python教程

Java教程

Web教程

数据库教程

图形图像教程

办公软件教程

Linux教程

计算机教程

大数据教程

开发工具教程