本文中内存空间等同于命名空间

Python中变量的定义是一种引用,定义变量时(如a = “hello”),实际上是在适当的命名空间中定义了一个变量a,变量a的值是字符串”hello”在内存中的地址,即变量a指向了字符串”hello”,执行语句a = “world”,时,将a的值修改为字符串”world”的内存地址,即将a重新指向了字符串”world”,如果没有其它变量引用字符串”hello”, Python解释器的回收机制会自动将”hello”回收。

如图a = “hello”,,此时命名空间中并没有变量a,所以在该命名空间中新建一个变量a,并指向字符串”hello”,当执行a = [11,22]时,在命名空间中找到变量a,并将其重新指向到列表[11,22]。注意可变变量与不可变变量的区别,字符串是不可变变量,”hello”在内存中创建后,值不能修改,列表是可变变量,通过方法[11,22].append(33)可修改列表的值为[11,22,33],列表修改前后在内存中的地址是不变的,由于变量a的值是列表的地址,指向了该列表,所以a的值也会变为[11,22,33]。

注意:Python中变量a中的值是地址,读变量a时,实际上是读取a的地址指向的内存中的值[11,22],函数id(a)返回变量a指向的地址值,即列表[11,22]在内存中的地址值,变量a自身在内存中的地址是没有办法获取的,也不需要获取。

一:全局变量和局部变量

所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在整段代码的任意位置使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。简单的说局部变量就是在函数内部定义的变量,只在函数内部使用,函数外部定义的变量是全局变量,在整个程序中(本文件模块)使用。

当函数被执行时,Python 会为其分配一块临时的存储空间,所有在函数内部定义的变量,都会存储在这块空间中。而在函数执行完毕后,这块临时存储空间随即会被释放并回收,该空间中存储的变量自然也就无法再被使用。

局部变量的读和写是不同的,读时,程序会在函数所在的内存空间查找该局部变量,如果在该内存空间里没有找到,则到整个程序的内存空间查找,如有同名的全局变量,则取全局变量的值,整个程序找不到则报错。写时,即用 ”=” 赋值,如a = 10 则是在函数内部新定义一个局部变量a,变量a的引用指向了10,并不修改同名的全局变量a。可在函数内部使用global  a语句,明确的指数函数使用的就是全局变量a,内部不创建新的局部变量。

二:Python类变量和实例变量,实例方法、静态方法和类方法

Python类是一个独立的命名空间

Python中类的每个实例化对象,都有各自的命名空间,空间中包含了各自的属性名称以及实例方法的引用,读写时的区别类似局部变量和全局变量。

类体中、所有方法之外:此范围定义的变量,称为类属性或类变量;

类体中,所以方法内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量;

类体中,所有方法内部:以“变量名=变量值”的方式定义的变量,称为局部变量。

 因为类变量为所有实例化对象共有,通过类名修改类变量的值,会影响所有的实例化对象。通过类实例对象也可以读取类变量的值,类变量的查找范围由小到大为:实例对象命名空间---类命名空间-----父类命名空间,但是类实例对象是无法修改类变量的。通过类实例对象对类变量赋值,其本质将不再是修改类变量的值,而是在给该对象定义新的实例变量,类似局部变量读写时的区别。

实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。类中,实例变量和类变量可以同名,但这种情况下使用类实例对象将无法调用类变量,它会首选实例变量,这也是不推荐“类变量使用对象名调用”的原因。

通常情况下,定义局部变量是为了所在类方法功能的实现。需要注意的一点是,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁。

通常情况下,在类中定义的方法默认都是实例方法

@classmethod修饰符进行修饰的方法为类方法,Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是类对象)

静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中。

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法。

静态方法需要使用@staticmethod修饰

模块的导入import

在我们打开一个Python执行环境的时候,Python就将一大批module(内置模块)加载到内存当中,但为了使local名字空间足够干净,Python没有将这些符号暴露在local名字空间中,而是需要我们显式地通过import机制通知Python,我需要将这个符号引入到local名字空间,以便我的程序能使用这个符号背后的对象。自定义模块,是程序在执行imoort 时将模块导入到内存。

对于自定义模块,import实际上做了两件事,第一,将整个模块导入内存执行,第二,将模块的命名空间告知本地程序,让本地程序知道去哪里查找模块中的变量函数等对象。内置模块,启动解释器时,解释器主动做了第一步。

例如,程序A中导入了模块B,实际上是将B中的代码加载到内存中执行,然后告知程序A,B的命名空间在哪里,A中的代码就可以使用B.变量名的方式调用B命名空间中的变量了(此处的变量包括函数对象类等)。A或者B的全局变量都只在各自的命名空间中生效。

A import 导入了B ,B又通过import 导入了C,A代码中不能直接调用C中的变量,也就是说导入不能传递。如图

通过from  XXX  imort A

首先,这种方法也会将XXX模块全部加载到内存中执行,实质上是在本程序内部(本文件)中定义了一个全局变量A,该全局变量指向了XXX模块中的变量A,所以在本程序中可以直接使用变量A,因为没有将XXX模块的命名空间引入到本程序中,所以本程序不能使用XXX模块中的其他变量。如图

当A中执行from B import x时,本地创建一个全局变量x,x的值指向了B中x的值,注意,当给A中的x重新赋值时,实际上是将x值指向了一个新的值,并不修改B中的x值,在多个模块共享某个模块中的数据时,最好不要用from xxx import x 语句,而用import导入共享模块比较靠谱。