python的有趣性和实用性。 第三部分

前面的部分中,我们研究了切片,拆包/打包集合以及布尔运算和类型的某些功能。

评论中提到了将标量乘以标量的可能性:

a = [0] * 3 s = 'a' * 2 print(a, s) # -> [0, 0, 0], 'aa' 

一个或多或少经验丰富的python开发人员都知道在编写时缺少复制机制

 a = [0] b = a b[0] = 1 print(a, b) # -> [1], [1] 

然后将输出以下代码?

 b = a * 2 b[0] = 2 print(a, b) 

在这种情况下,Python的工作原理是最少惊讶:变量a中有一个单位,即可以声明b以及如何

 b = [1] * 2 

在这种情况下的行为将是相同的:

 b = a * 2 b[0] = 2 print(a, b) # -> [1], [2, 1] 

但不是那么简单,python将列表的内容复制了很多倍,并构造了一个新列表。 并且在列表中存储了指向值的链接,并且如果以这种方式复制对不可变类型的引用时,一切都很好,但是由于可变的影响,在写入过程中会出现不复制的情况。

 row = [0] * 2 matrix = [row] * 2 print(matrix) # -> [[0, 0], [0, 0]] matrix[0][0] = 1 print(matrix) # -> [[1, 0], [1, 0]] 

列出生成器和numpy可以在这种情况下为您提供帮助。

可以添加甚至增加列表,任何迭代器都可以在右侧:

 a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a) # -> [0, 1, 2, 'a', 'b', 1] ,     #       

有技巧的问题(供采访):在python中,参数是通过引用还是通过值传递的?

 def inc(a): a += 1 return a a = 5 print(inc(a)) print(a) # -> 5 

如我们所见,函数主体中值的更改并未更改其外部对象的值,由此可以得出结论,参数是通过值传递的,并编写以下代码:

 def appended(a): a += [1] return a a = [5] print(appended(a)) # -> [5, 1] print(a) # -> [5, 1] 


像C ++这样的语言将变量存储在堆栈和动态内存中。 调用该函数时,我们将所有参数放在堆栈上,然后将控制权转移给该函数。 她知道堆栈中变量的大小和偏移量,因此她可以正确解释它们。
同时,我们有两个选择:将变量的内存复制到堆栈上,或将对对象的引用放入动态内存中(或更高级别的堆栈中)。
显然,当更改函数堆栈上的值时,动态内存中的值不会更改,但是当通过引用更改存储区时,我们将修改共享内存,因此指向同一存储区的所有链接都将“看到”新值。

在python中,他们放弃了类似的机制,例如,在创建变量时,用对象代替了变量名称的赋值机制:
 var = "john" 


解释器创建对象“ john”和“ name” var,然后将对象与给定名称关联。
调用函数时,不会创建任何新对象;而是会在其范围内创建与现有对象关联的名称。
但是python具有可变和不可变的类型。 例如,第二个对象包括数字:在算术运算期间,现有对象不会更改,但是会创建一个新对象,然后将其与现有名称相关联。 在此之后,如果没有单个名称与旧对象相关联,则将使用链接计数机制将其删除。
如果名称与变量类型的变量相关联,则在对其进行操作期间,对象的内存将更改;因此,与此存储区相关联的所有名称都会“看到”更改。

您可以在文档中阅读有关此内容的信息此处有更详细的说明。

另一个例子:

 a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l) # -> [1, 2, 3, 4, 5], [1, 2, 3, 4, 5] l = rev(l) print(a, l) # -> [5, 4, 3, 2, 1], [5, 4, 3, 2, 1] 

但是,如果我们决定在函数外部更改变量,该怎么办? 在这种情况下,全局修饰符将帮助我们:

 def change(): global a a += 1 a = 5 change() print(a) 

注意:请勿执行此操作(不严重,请不要在程序中使用全局变量,尤其是不要在您自己的程序中使用)。 最好只从函数中返回一些值:
 def func(a, b): return a + 1, b + 1 


但是,在python中还有另一个作用域和相应的关键字:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget()) # -> 42 vset(0) print(vget()) # -> 0 

在此示例中,我们创建了一个只能通过方法才能更改(并获得其值)的变量,您可以在类中使用类似的机制:

 def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam)) 

但也许最好将自己限制在__getattr__属性__setattr__的定义上。

您甚至可以定义__delattr__

python的另一个功能是存在两种获取属性的方法:__getattr__和__getattribute__

它们之间有什么区别? 仅当找不到类中的属性时才调用第一个,而第二个是无条件的。 如果两者都在类中声明,则仅在__getattribute__中显式调用__getattr__或__getattribute__引发AttributeError时才调用__getattr__。

 class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name) # -> __getattribute__ # -> __getattr__ # -> john 

最后,是python如何自由操作变量和范围的示例:

  e = 42 try: 1 / 0 except Exception as e: pass print(e) # -> NameError: name 'e' is not defined 

顺便说一下,这也许是第二个Python优于第三个python的唯一示例,因为它输出:

  ... print(e) # -> float division by zero 

Source: https://habr.com/ru/post/zh-CN422951/


All Articles