Python的一些特殊用法

1. 私有变量的定义

​ Python中,有以下几种方式定义变量:

  • xx: 共有变量。
  • _xx: 单前置下划线,私有化属性或方法,类对象和子类可以访问,from somemodule import *禁止导入。
  • __xx: 双前置下划线,私有化属性或方法,无法在外部直接访问,名字重整所以无法访问。(见下文)
  • __xx__: 双前后下划线,系统定义名字。(不要自己定义)
  • xx_: 单后置下划线,用于避免与Python关键词的冲突。

2. 名字重整

​ 实质是用一个名称对不同数据类型的处理的技术。例如在C++中,会将重载函数

1
2
3
4
print(int i)
print(char c)
print(float f)
print(char* s)

分别编译为

1
2
3
4
_print_int
_print_char
_print_float
_print_string

​ Python中与之类似,对于私有变量,如__name

1
2
3
4
5
6
7
class hehe:
def __init__(self):
self.__name = 1

a = hehe()
print(a.__dict__) #报错
print(a._hehe__name) #正常打印

​ 里面的__name作为私有变量,不能直接在外部访问,就是因为名称被重组为了_hehe_name

​ 不过虽然可以这样进行访问,但强烈不建议使用。

3. 命名空间和作用域

1. 介绍

​ A namespace is a mapping from names to objects. Most namespaces are currently implemented as Python dictionaries.

​ 命名空间是一个存放变量的字典。键就是变量名,值就是变量的值。命名空间的目的是提供了在项目钟避免名字冲突的方法。各个命名空间是独立的,一个命名空间中不可以重名,但是不同的命名空间中就可以重名了。

​ 在一个Python程序的任何一个地方,都有几个可用的命名空间:

  1. 全局命名空间:每个模块拥有自己的命名空间,叫全局命名空间,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  2. 局部命名空间:每个函数都有自己的命名空间,叫做局部命名空间,它记录了函数的变量,包括函数的参数和局部定义的变量。
  3. 内置命名空间:任何模块均可访问它,它存放着内置的函数和异常。
2. 查找顺序:
image-20200711093123996
image-20200711093123996

​ Python会按照由小到大的顺序查找:局部的命名空间->全局命名空间->内置命名空间。如果找不到,放弃查找并给出一个NameError异常。

3. 作用域

​ Python的作用域一共有4种,分别是:

  • L(Local):最内层,包含局部变量,比如一个函数/方法内部。
  • E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
  • G(Global):当前脚本的最外层,比如当前模块的全局变量。
  • B(Built-in): 包含了内建的变量/关键字等,最后被搜索。

规则顺序: L –> E –> G –>B

​ 内置作用域通过名为builtin的标准模块实现,但这个变量名自身没有放入内置作用域,使用方法如下:

1
2
import builtins
dir(builtins)

​ Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问。

4. global和nonlocal关键字

​ 当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。

5. 例子
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
28
29
# 命名空间(namespace)
# 命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中
# 每一个作用域都会有一个它对应的命名空间
# 全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量
# 命名空间实际上就是一个字典,是一个专门用来存储变量的字典

# locals()用来获取当前作用域的命名空间
# 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
# 返回的是一个字典
scope = locals() # 当前命名空间
print(type(scope))
# print(a)
# print(scope['a'])
# 向scope中添加一个key-value
scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建了一个变量(一般不建议这么做)
# print(c)

def fn4():
a = 10
# scope = locals() # 在函数内部调用locals()会获取到函数的命名空间
# scope['b'] = 20 # 可以通过scope来操作函数的命名空间,但是也是不建议这么做

# globals() 函数可以用来在任意位置获取全局命名空间
global_scope = globals()
# print(global_scope['a'])
global_scope['a'] = 30
# print(scope)

fn4()

3. 包, 模块

1. 模块

​ 简单说一个.py文件就是一个模块(Module)。逻辑上来说模块就是一组功能的组合;实质上一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。

​ 想要使用模块,必须先加载进来,可以通过关键字import或from进行加载;需要注意的是模块和当前文件在不同的命名空间中。

​ import可以在任意位置使用,且不会重复导入,因为第二次、三次等导入只是增加了一次引用,不会重新执行模块内语句。

​ 导入的模块会重新开辟独立的名称空间。且可以为模块起别名。

2. 包

​ 包就是一个目录,里面存放了.py文件,外加一个__init__.py。包就是用来管理和分类模块的。

4. 使用不同方法导入模块,模块中私有变量的使用区别

​ 使用from somemodule import *导入模块,不能导入或使用私有属性和方法。

5. 对类中的私有属性进行操作的三种方法

1. getter和setter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class test(object):
def __init__(self):
self.__num = 10

def getNum(self):
return self.__num

def setNum(self, value):
self.__num = value

t = test()
print(t.getNum()) # 10
t.setNum(20)
print(t.getNum()) # 20
2. property方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class test(object):
def __init__(self):
self.__num = 10

def getNum(self):
return self.__num

def setNum(self, value):
self.__num = value

num = property(getNum,setNum)

t = test()
print(t.num) # 10
t.num = 20
print(t.num) # 20
  • property方法包含四个参数,分别为fget,fset,fdel,doc,分别对应getter方法,setter方法,deleter方法和方法说明
  • property()方法返回一个property属性,如果c是C的实例,那么c.x会调用getter方法,c.x = value会调用setter方法,而del c.x会调用deleter方法。
3. @property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class test(object):
def __init__(self):
self.__num = 10

@property
def num(self):
return self.__num

@num.setter
def num(self, value):
self.__num = value

t = test()
print(t.num) # 10
t.num = 20
print(t.num) # 20
  • 我们可以把property()方法当成一个装饰器来使用,使用@property对方法进行装饰
  • 装饰器@property把方法x()转换成了与方法名同名的getter方法,”I’m the ‘x’ property.”是property的doc参数
  • 调用方法和property()方法一样

6. ->怎么用?

1
2
3
@property
def attrs(self) -> _Attrs:
pass

​ 只是一种标注返回值的方式,无实际意义。

7. 参考资料

  1. Python命名空间的本质
  2. Python3 命名空间和作用域
  3. python命名空间(namespace)
  4. 我的Python学习笔记(三):私有变量
  5. 在def定义函数的时候, @和-> 代表什么?