关于python:框架和对象之间有什么区别,什么时候应该修改另一个?

What is the difference between a frame and object, and when should I modify one over the other?

我开始阅读python的+=语法,无意中发现了以下文章/答案:关于的交互式代码+=

所以我注意到帧和对象之间似乎有区别。

Frames and Objects

在全局框架中,它们指向同一对象,即使它们是不同的变量;如果

1
l2 += [item]

而是

1
l2 = l2 + [item]

然后"l2"在该行运行时成为单独的对象。我最大的问题是,您希望一个变量何时指向一个单独的对象?另外,为什么和什么时候你想让它们指向同一个物体?

任何解释或用例都将非常感谢!如果您能提到任何与数据科学相关的信息,请特别感谢:)


frameobject不是你所认为的意思。

在编程中,有一个称为堆栈的东西。在Python中,当调用函数时,创建一个称为堆栈帧的东西。这个框架(如您在示例中看到的)基本上只是一个包含函数局部所有变量的表。

注意,定义一个函数并不会创建一个新的堆栈框架,而是调用一个函数。例如:

1
2
3
def say_hello():
    name = input('What is your name?')
    print('Hello, {}'.format(name))

你的全球框架只是一个参考:say_hello。您可以通过检查本地名称空间中的内容(在Python中,名称空间、作用域和堆栈帧之间的关系几乎为1:1)来看到这一点:

1
print(locals())

你会看到这样的东西:

1
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1019bb320>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/private/tmp/fun.py', '__cached__': None, 'say_hello': <function say_hello at 0x101962d90>}

注意dunder(双下划线的缩写)名称-这些名称是自动提供的,为了我们的讨论,您可以忽略它们。这给我们留下了:

1
{'say_hello': <function say_hello at 0x101962d90>}

0x位是函数本身所在的内存地址。所以这里,我们的全局堆栈/帧只包含那个值。如果调用函数,然后再次检查locals(),您将看到name不在那里。这是因为当你调用函数时,你创建了一个新的堆栈框架,变量被分配到那里。您可以通过在函数末尾添加print(locals())来证明这一点。然后你会看到这样的情况:

1
{'name': 'Arthur, King of the Brits'}

这里没有邓德的名字。您还将注意到,这不会显示内存地址。如果你想知道这个值在哪里,它有一个函数。

1
2
3
4
5
6
7
8
def say_hello():
    name = input('What is your name?')
    print('hello {}'.format(name))
    print(locals())
    print(id(name))
    return name

print(id(say_hello()))

这就是这个例子在讨论帧时的含义。

但是物体呢?好吧,在Python中,一切都是一个对象。试试看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> isinstance(3, object)
True
>>> isinstance(None, object)
True
>>> isinstance('hello', object)
True
>>> isinstance(13.2, object)
True
>>> isinstance(3j, object)
True
>>> def fun():
...  print('hello')
...
>>> isinstance(fun, object)
True
>>> class Cool: pass
...
>>> isinstance(Cool, object)
True
>>> isinstance(Cool(), object)
True
>>> isinstance(object, object)
True
>>> isinstance(isinstance, object)
True
>>> isinstance(True, object)
True

它们都是物体。但它们可能是不同的物体。你怎么知道?使用id时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> id(3)
4297619904
>>> id(None)
4297303920
>>> id('hello')
4325843048
>>> id('hello')
4325843048
>>> id(13.2)
4322300216
>>> id(3j)
4325518960
>>> id(13.2)
4322300216
>>> id(fun)
4322635152
>>> id(isinstance)
4298988640
>>> id(True)
4297228640
>>> id(False)
4297228608
>>> id(None)
4297303920
>>> id(Cool)
4302561896

注意,通过使用is还可以比较两个对象是否是同一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
>>> True is False
False
>>> True is True
True
>>> 'hello world' is 'hello world'
True
>>> 'hello world' is ('hello ' + 'world')
False
>>> 512 is (500+12)
False
>>> 23 is (20+3)
True

嗯……?等等,发生了什么事?事实证明,EDOCX1(即cpython)缓存小整数。因此,对象512与添加到对象12中的对象500的结果不同。

需要注意的一点是,赋值操作符=总是为同一对象指定一个新名称。例如:

1
2
3
4
5
6
7
8
9
10
11
>>> x = 592
>>> y = 592
>>> x is y
False
>>> x == y
True
>>> x = y
>>> x is y
True
>>> x == y
True

不管你给一个物体取了多少个别的名字,或者即使你把这个物体传给不同的帧,你仍然拥有相同的物体。

但是当您开始收集时,了解更改对象的操作和生成新对象的操作之间的区别是很重要的。一般来说,在Python中有一些不变的类型,对它们的操作将生成一个新的对象。

至于你的问题,你想什么时候改变对象,什么时候保持它们不变,实际上是看错了方向。当您想更改内容时,您需要使用可变类型;如果不想更改内容,您需要使用不可变类型。

例如,假设您有一个组,并且希望向该组添加成员。您可以使用一个可变类型(如列表)来跟踪组,使用一个不可变类型(如字符串)来表示成员。这样地:

1
2
3
4
5
6
7
8
9
10
>>> group = []
>>> id(group)
4325836488
>>> group.append('Sir Lancelot')
>>> group.append('Sir Gallahad')
>>> group.append('Sir Robin')
>>> group.append("Robin's Minstrels")
>>> group.append('King Arthur')
>>> group
['Sir Lancelot', 'Sir Gallahad', 'Sir Robin',"Robin's Minstrels", 'King Arthur']

当一个小组成员被吃掉时会发生什么?

1
2
3
4
5
>>> del group[-2]  # And there was much rejoicing
>>> id(group)
4325836488
>>> group
['Sir Lancelot', 'Sir Gallahad', 'Sir Robin', 'King Arthur']

你会注意到你仍然有同一个组,只是成员变了。