python面向对象
# 一、封装
# 1、类定义
class ClassName:# __name__ 定义类之后,会得到一个类对象
“”“类的帮助信息”“” # 类文本字符串
# statement... # 类体
age=13; # 类属性或叫成员
def __init__(self):
print("我是大雁类")
def show(self): # Person.show.__qualname__ 限定名访问到方法名; 类属性,放在类字典中,本质是一个函数 bound method self->instance
#静态方法,不可以写self
@staticmethod
def method():
print("我使用了staticmethod进行修饰,所以我是静态方法")
#内建函数,类方法,写cls
@classmethod
def cm(cls):
print("我是类方法,因为我使用了classmethod进行修饰 ")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- ClassName:用于指定类名,一般使用大写字母开头,如果类名中包括个词,第二个单词的首字母也要大写,这种命名方法也称为“驼峰式命名法”,这是惯例。当然,也可以根据自己的习惯命名,但是一般推荐按照惯例来命名。
- “类的帮助信息”:用于指定类的文档字符串。定义该字符串后,在创建类的对象时,输入类名和左侧的括号“( ” 后,将显示信息。
- statement:类体,主要由类变量(或类成员)、方法和属性等定义语句组成。如果在定义类时,没有想好类的具体功能,也可以在类体中直接使用Pass语句代替。
# 01、类的属性
类的特殊属性: __name__
类的名称; __doc__
类文档; __class__
类型,相当于type(XXX); __qualname__
限定名;__dict__
对象的属性的字典
类的属性,实例可以共享,是类的,就是实例大家的; 实例变量是每一个实例自己的变量
类方法
print(Person.show) # 普通的函数方法 <function Person.show at 0x000001E972AB0360>
print(p1.show) # show是类属性,实例调用,会注入当前实例 <bound method Person.show of <__main__.Person object at 0x000001E972A55E10>>
2
# _slots_
问题的引出:都是字典惹的祸,字典为了提升查询效率,必须用空间换时间。一般来说一个实例,属性多一点,都存储在字典中便于查询,问题不大。但是如果数百万个实例,那么字典占的总空间就有点大了。这个时候,能不能把属性字典__dict__
省了? Python提供了__slots__
属性,阻止了字典的生成,没有__dict__
了
__slots__
是一个可迭代对象(元组、列表等),包括了当前能访问到的属性。
当定义了slots后,slots中定义的变量变成了类的描述符,相当于java,c++中的成员变量声明,
类的实例只能拥有slots中定义的变量,不能再增加新的变量。注意:定义了slots后,就不再有dict
子类不受父类__slots__
的影响,不继承
# 应用场景
使用需要构建在数百万以上众多对象,且内存容量较为紧张,实例的属性简单、固定目不用动态增加的场景。 可以使用tracemalloc看看内存使用的差异。建议使用 stats = snapshot.statistics('filename') 查看总内存使用。
import tracemalloc
tracemalloc.start() #开始跟踪内存分配
count = 10000
d = [dict( zip('xy',(5,6)) ) for i in range(count)] #列表里面放了若干个字典,
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('filename') #按行号统计 lineno, filename traceback
for stat in top_stats:
print(stat)
2
3
4
5
6
7
8
9
10
11
12
# 02、属性装饰器和析构
#age= property(lambda self: self.__age);
@property # getter
def age(self):
return self.__age; # private 相当于返回 self._Person__age
@age.setter
def age(self, age):
self.__age = age;
@age.deleter
def age(self, age):
print("del~~"); # del tom.__age
2
3
4
5
6
7
8
9
10
11
12
13
# property装饰器
- 后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性
- setter装饰器
- 与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写
- deleter装饰器
- 可以控制是否删除属性。很少用
- property装饰器必须在前,setter、deleter装饰器在后
- property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果
- property()函数写法:
property(fget=None, fset=None, fdel=None, doc=None)
# 析构__del__函数
__init__
中开启资源,释放资源,python解释器会自动调用; 引用计数归0后垃圾回收时或者程序退出时,需要析构
类中可以定义__del__
方法,称为析构函数 (方法)
作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接.
注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它使用__del__
语句删除实例,引用计数减1。当引用计数为0时,会自动调用__del__
方法。
由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。
# 03、“魔术”方法
命名规则是前后各有两个下划线, 魔法方法是python当中,预设好的,具有特定功能的方法, 一般这种方法,不需要手动调用,会自动调用执行
魔术方法里面要小心出现死递归
# 01、创建、初始化与销毁
# _new_()
__new__()
是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先__init__()
初始化方法被调用。
一般情况下,覆写 __new__()
的实现将会使用合适的参数调用其超类的 super().__new__()
,并在返回之前修改实例
def __new__(cls, *args, **kwargs): #魔术方法, 返回一个实例,实例创建之前被调用的,用于创建实例,然后返回该实例对象,是个静态方法。
print(" new ~~", args, kwargs) # 修改args、kwargs没有用
# return super().__new__(cls) # 或者下面这种形式,两种都可, @staticmethod
return object.__new__(cls);
2
3
4
5
# _ init _()
在创建类后,类通常会自动创建一个__init__()
方法。该方法是一个特殊的方法,类似JAVA 语言中的构造方法。每当创建一个类的新实例时,Python都会自动执行它。__init()__
方法必须包含一个参数,并且必须是第一参数。self参数是一个指向实例本身的引用,用于访问类中的属性和方法。
在方法调用时会自动传递实际参数self。因此,当__init()__
方法只有一个参数时,在创建类的实例时,就不需要指定参数了。
在__init()__
方法中,除了self参数外,还可以自定义一些参数,参数间使用逗号“,”进行分隔。
# _del_()方法
该方法是用来在销毁对象时,回收释放资源使用的方法,该方法也是自动调用,当在使用 del 对象时,会调用方法
# 02、可视化
# _str_()
格式化对象:该方法返回一个字符串值,当使用 str() 做类型转换时,会自动调用 该方法
当一个自定义类,没有实现该方法时,默认打印格式是 <模块名.类名 object at 地址>
如:<main.Cat object at 0x02176610>
# _repr_()
# _bytes_()
# 03、hash哈希散列
# _hash_()
内建函数 hash()
调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。
- 默认情况下,
__hash__
方法返回对象的 ID,__eq__
方法使用 is 操作符进行比较 - 如果实现了
__eq__
方法,Python 会将__hash__
方法设置为 None,除非实现了自定义的__hash__
方法。 - 不可hash对象
isinstance(p1, typing.Hashable)
一定为False
set去重原理: 第一步求hash, hash值相同,即hash冲突了,第二步再比较内容__eq__
# _eq_()
对应==操作符,判断2个对象内容是否相等,返回bool值定义了这个方法,如果不提供 hash 方法,那么实例将不可hash了
# 04、bool
# _bool_()
内建函数
__bool__()
,或者对象放在逻辑表达式的位置,调用这个函数返回布尔值, 0、1都不可以。没有定义
__bool__()
,就找__len__()
返回长度,非0为真。如果__len__()
也没有定义,那么所有实例都返回真
list\set\tuple\dict 空容器,bool都为False,主要看
# 05、运算符重载
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point。提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。int类,几乎实现了所有操作符,可以作为参考。
注意__sub__
和 __isub__
的区别:
__isub__
方法定义,一般会in-place就地来修改自身,如果没有定义 __isub__
方法,则会调用 __sub__
# @functools.total_ordering
__le__
、__lt__
、__eq__
、__gt__
、__ge__
是比较大小必须实现的方法,但是全部写完太麻烦,使用@functools.total_ordering
装饰器就可以大大简化代码。
但是要求__eq__
必须实现,其它方法__le__
、__lt__
、__gt__
、__ge__
实现其一
这个装饰器只是看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器
# 06、容器和大小
class Cart:
def __init__(self):
self.__item = [];
def __str__(self):
return str(self.__item);
def addItem(self, item):
self.__item.append(item);
return self;
def __add__(self, other):
self.__item.append(other);
return self;
def __iter__(self): # 返回可迭代对象
#return iter(self.__item)
#yield from self.__item
for item in self.__item:
yield item
def __setitem__(self, key, value):
self.__item[key] = value;
return self;
def __getitem__(self, key):
return self.__item[key]
def __len__(self):
return len(self.__item)
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
30
31
# 07、可调用对象
# _call_()
__call__()
, 类中定义一个该方法,实例就可以像函数一样调用
可调用对象: 定义一个类,并实例化得到其实例,将实例像函数一样调用
可调用对象,都有该方法(可调用对象:函数、类、类实例)
class Fib:
def __init__(self):
self.items=[0,1,1]; # 斐波拉契数列 fib(0) fib(1) fib(2)
def __call__(self, index):
if index<0:
raise Exception("索引需大于0");
for i in range(len(self), index+1): # fib.__len__
self.items.append(self.items[i-1] + self.items[i-2]) #生成斐波拉契数列, index小于3的话,不会进来这个语句
return self.items[index];
__getitem__ = __call__
def __str__(self):
return str(self.items);
__repr__ = __str__;
def __len__(self):
return len(self.items);
def __iter__(self):
yield self.items
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
# 08、上下文管理with
文件IO操作可以对文件对象使用上下文管理,使用with...as
语法
with 操作会调用实例的__enter__
,as子句后的标识符会得到这个返回值
c= Cart();
with c as p: # 相当于 p= c.__enter__()
pass;
2
3
# _enter_
# _exit_
__exit__(self, exc_type, exc_val, exc_tb)
__exit__
的返回值等效为True,则压制异常,否则往外抛出异常
# 上下文应用场景
1.增强功能 在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
2、资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
3、权限验证
在执行代码之前,做权限的验证,在__enter__
中处理
# contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__
和__exit__
方法对下面的函数有要求: 必须有yield
,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。
@contextmanager
def timeit(fn):
start = datetime.datetime.now();
try:
yield fn; # yield值只能有一个,相当于__enter__
finally: # 相当于__exit__
delta = (datetime.datetime.now() - start).total_seconds()
print("contextmanager方式","{} tooks {}秒".format(fn.__name__, delta))
with timeit(add) as f: # 这里f=yield后的值
f(3,5);
2
3
4
5
6
7
8
9
10
11
# 09、反射
运行时,runtime,区别于编译时,指的是程序被加载到内存中执行的时候.
反射,reflection,指的是运行时获取类型定义信息。一个对象能够在运行时,像照镜子一样,反射出其类型信息。简单说,在Python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或者自省。 具有反射能力的函数有 type()、isinstance()、callable()dir0)、getattr()等
动态增加类属性,使用.运算符或者getattr访问,都可以有绑定实例self参数的效果; 动态增加实例的属性,通过实例属性调用时,没有self绑定效果
# _getattr_
通过实例访问引发AttributeError
时,如果有此魔术方法,则调用。类属性访问时,不会调用该魔术方法
# _setattr_
只要是实例的属性赋值就调用该方法
def __getattr__(self, propName): # 找不到实例的属性,即会报AttributeError时,会进入该方法
print("miss prop={}".format(propName))
def __setattr__(self, key, value): #给实例的属性赋值时,会进入该方法
#setattr(self, key, value) #死递归 等价 self.key = value
#super().__setattr__(key,value)
self.__dict__[key] = value;
2
3
4
5
6
7
实例属性会按照继承关系找,如果找不到,就会执行__getattr__()
方法,如果没有这个方法,就会抛出AttributeError
异常表示找不到属性。
查找属性顺序为:
instance.__dict__
->instance.__class: __dict__
-> 继承的祖先类(直到object)的 __dict__
--找不到--> 调用__getattr__
# _delattr_
通过实例删除属性,就会尝试调用该魔术方法。可以阻止通过实例来删除属性的操作。但是通过类依然可以删除类属性。
# _getattribute_
只要通过实例访问属性,都会经过此方法, 包括__dict__
等属性, 因此这个魔术方法一般不用, 如果写了,一定是通过实例访问属性的第一站, 若raise AttributeError
,会调用__getattr__
def __getattribute__(self, item):
#raise AttributeError("没找到") # 抛这个异常,会进入__getattr__函数
#return self.__dict__[item]; # 死递归,访问实例的__dict__,也会进入该魔术方法
#return object.__getattribute__(self, item);
return super().__getattribute__(item);
2
3
4
5
# 10、描述器
Python中,一个类实现了__set__
__delete__
__get__
三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
- 仅实现了
__get__
,就是非数据描述符 non-data descriptor - 实现了
__get__
__set__
,就是数据描述符 data descriptor - 如果一个类的类属性设置为描述器实例,那么它被称为
owner
属主。当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法。
描述器在Python中应用非常广泛
Python的方法(包括@staticmethod
和@classmethod
) 都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。
@property
函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
# _get_
因为定义了__get__
方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用___get__
_方法,即访问类属性转变成了函数调用[属性增强]
# _set_、 set_name
实例的属性如果是数据描述器,那么实例属性赋值就成了调用A().__set__
, 优先级: 数据描述器>__dict__
> 非数据描述器
# _delete_
class A:
def __init__(self):
print("A init~~")
self.name="jim"
def __str__(self):
return "<A {}>".format(id(self))
def __get__(self, instance, owner):
"""
:param instance: 属主实例
:param owner: 属主类
:return:
"""
print("A get {} {} {}".format(self, instance, owner));
return self;
def __set__(self, instance, value): # 有__set__则为数据描述器, 没有则为非数据描述器
print("A set {} {} {}".format(self, instance, value));
pass;
class B:
"""
属主类
"""
x = A(); # A()为描述器实例, 不能用在实例属性上,需放在属主类的类属性上,访问类属性转变成了函数调用[属性增强]
def __init__(self):
print("B init~~")
# 因为x为数据描述器类属性,数据描述器的优先级比__dict__高,因此会调用数据描述器的__set__,如果没有__set__,则为非数据描述器,dict优先级高于非数据描述器
self.x = "b.x";
#self.y = A(); 描述器不能用在实例属性上, __get__不会生效
def __str__(self):
return "<B {}>".format(id(self))
print("-"*30);
b = B();
print(B.x, b.x, id(b)); # => A().__get__()
print(B.x.name);
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
30
31
32
33
34
35
36
37
38
39
40
# 11、运算符重载
# _radd_ 和 _add_
def __add__(self, other): # a+b
return self.x + other.x
def __iadd__(self, other): # a += b
return A(self.x + other.x)
def __radd__(self, other): # 1+a 不同类型相加
pass
2
3
4
5
6
7
8
9
10
a+b => a.__add__(b)
如果a实例对应的类没有__add__
方法或者返回return NotImplemented
,则就到b对应类的__radd__
方法
1+a => int.__add__(1,a)
先进行内部类型检查,不是我支持的类型,return NotImplemented
,转而尝试后者的A.__radd__(a,1)
# 12、可迭代对象
# _iter_
列表、元组、set、dict、 range、生成器、迭代器、自定义类实例(实现__iter__(self)
,返回迭代器)
可迭代对象一定在类上实现了__iter__
- 返回return iter(self.items); 或者 yield from self.items
# _reversed_
逆向迭代对象,对应 reversed(xxx);
# 迭代器__next__方法
实现了__next__
方法的对象,就是一个迭代器,但不一定可以迭代,即for in, 只有实现了__iter__
方法,才可以迭代
# for in 可迭代对象
- 可迭代对象
__iter__
拿到一个迭代器 - 对
迭代器.__next__
逐个元素迭代,直到出现StopIteration异常
# 03、类方法
所谓实例方法是指在类中定义函数。该函数是一种在类的实例上操作的函数。同__init()__
方法一样,实例方法的第一参数必须是self(名字可以换),并且必须包含一个self参数, self指代的是实例本身
# 04、@staticmethod
使用@staticmethod修饰的类方法也被称为静态方法,此方法不传入代表实例对象的self参数,并且不强制要求传递任何参数,可以被类直接调用,当然实例化的对象也可以调用
# 05、@classmethod
类静态方法, classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等
from functools import partial
class StaticMethod:
def __init__(self, fn):
self.__fn = fn;
def __get__(self, instance, owner): # 非数据描述器
return self.__fn
class ClassMethod: # 非数据描述器
def __init__(self, fn):
self.__fn = fn;
def __get__(self, instance, owner): # 非数据描述器
print(2,instance, owner.__dict__);
return partial(self.__fn, owner) # 需要固定owner, 返回一个函数,(偏函数)
class A:
#@staticmethod
@StaticMethod # fn1 = StaticMethod(fn1) A为宿主, StaticMethod为非数据描述器, A.fn1会走描述器的__get__方法
def fn1():
print("A fn1...");
#@classmethod
@ClassMethod # fn2 = ClassMethod(fn2) A为宿主, ClassMethod为非数据描述器, A.fn2会走描述器的__get__方法
def fn2(cls):
print("A fn2...");
@property # 只读属性age @property 数据描述器,会走描述器的__get__ __set__方法
def age(self):
return 23;
pass
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
30
31
32
33
# 2、实例化
instance = ClassName(parameterlist) #创建一个实例,
- ClassName:是必选参数,用于指定类。
- parameterlist:可以选参数,当创建一个类时,没有创建__init__()方法,或者当__init__()方法只有一个self参数时, parameterlist可以省略。
参考:Python 面向对象 (opens new window)
# 3、访问控制
在Python中,权限管理通常是通过访问控制机制来实现的。Python内置了一些访问控制机制,包括以下几种:
1.公共访问:public,默认情况下,所有的对象和方法都是公共的,可以被程序中的任何其他对象和方法访问。这是Python中最常见的访问控制方式。
2.私有成员:private,可以通过在变量名或方法名前加上两个下划线__
来将其声明为私有成员。私有成员只能在类的内部访问,而无法从类的外部直接访问。
3.受保护的成员:protected,可以通过在变量名或方法名前加上一个下划线_来将其声明为受保护的成员。受保护的成员可以在类的内部和其子类中访问,但是不能从类的外部直接访问。
Python对象模型使用了字典, 私有成员,在类中定义,都会被解释器改名为:
_类名__私有属性
,使用上面的改名方案之后,造成一种在类外无法使用限定名.__成员名
访问到属性的现象(隐藏)。 注意赋值即定义,在使用时与私有属性的干扰。python的类字典、实例字典是公开的,实例字典没找到,再从类字典里面找, 所以我们看到真实的情况, 但是其他高级语言,没有这个特性。 所以Python的私有属性可以绕过,可以访问,少用这种方式, 这是python隐藏数据的方式
# 4、未实现和未实现异常
Notlmplemented是个值,单值,是NotlmplementedType的实例
NotlmplementedError 是类型,是异常类,返回type
# 二、继承
object是所有类的根基类;
python可以多继承, 优点:子类可以同时遗传多个父类的属性,最大限度的重用代码;缺点:违反人的思维习惯,一个人有两个爹,代码的可读性会变差,不建议使用多继承,如果不可避免多个父类的继承,应该使用 Mixins机制
# 重写override
在开发过程中父类实现的方法与子类现完全不同,可以使用覆盖的形式,在子类中重新编写父类的方法具体的实现形式就是在子类中定义一个与父类同名的方法,重写父类之后,只会调用子类中重写后的方法,不会调用父类中封装的方法
super() -> same as super(__class__, <first argument>) #需要有第一参数, 如self; 会不断往上找父类的方法 super().__init__(args1, args2);
安全实现:先调用父类的方法,再执行自己的代码 super().XXX()
# 多继承
多继承的实现会导致编译器设计的复杂度增加(c++支持多继承),所以有些高级编程语言舍弃了类的多继承,如java。不管是否支持多继承,都应该避免使用多继承
多继承采用路径选择问题,使用mro解决基类搜索顺序问题
# Mixin
Mixin 直译为‘混合’的意思,它其实是 python 中的一种设计模式,是一种将多个类中的功能单元进行组合利用的方式。
Mixin 类通常是实现了某种功能单元的类,利用 python 的多重继承,子类为了实现某些功能,组合继承不同功能的 Mixin 类即可, 也可以使用装饰器@
实现。
在使用 Mixin 类应该遵循的几个原则
1.尽量以 Mixin 作为类名后缀,在 Mixin 类中也不要使用 super 这种用法
2.Mixin 类实现的功能是单一通用的,多个功能就写多个 Mixin,子类可以按需求组合继承
3.绝对独立,子类不继承 Mixin 也能初始化成功,Mixin 只是拓展了子类的功能,而不能影响子类的原有功能
4、mixin类尽量写在多继承列表前面
# 三、多态
c2:Cat = KadiCat(3,"凯迪小猫");
# 四、抽象类、接口
# 五、python对象模型
type用来干什么的?
- 内建函数,类,元类;判断一个实例的类型
- type(A()) => A类,说明A的实例由A构造实例化
- type(A) => type,即A是type的实例,由type造实例化成的类对象
- type(type) => type ,type类对象,由type构造的实例; 类对象的类型是type,说明他们都是type的实例
- type(object) => type,object类也是由type构造的类对象
- type的父类 object,从object派生的
继承都来自object,类型都看type。type也是对象继承自object,object也有类型是type. 这俩又特殊,type类型是它自己,object没有基类。
# 七、获取对象信息
# type()函数
返回Class类型
# isinstance()函数
判断一个对象的变量类型, isinstance()判断的是一个对象是否为该类型本身,或者是否为该类型父类的类型
# dir()
python是动态语言,不清楚对象有哪些属性,因此dir()大有作用
返回模块的属性列表, dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表,包括继承过来的;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
dir(obj)对于不同类型的对象obi具有不同的行为:
- 如果对象是模块对象,返回的列表包含模块的属性名和变量名, 相当于
globals()
- 如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名
- 如果是类的实例
。有
_dir_
方法,返回可迭代对象的返回值 。没有_dir_
方法,则尽可能收集实例的属性名、类的属性和祖先类的属性名 - 如果obi不写,返回列表包含内容不同
- 在模块中,返回模块的属性和变量名0
- 在函数中,返回本地作用域的变量名
- 在方法中,返回本地作用域的变量名
locals()
返回当前作用域中的变量字典
globals()
当前模块全局变量的字典