本文由 大侠(AhcaoZhu)原创,转载请声明。
链接: https://blog.csdn.net/Ahcao2008
关于类的探索(2)
- 前言
- 前提
- 两种学习方法
- 先从一个例子开始
- 稍微优化一下输出
- 模块
- 数据
- 函数
- 类
- 类的继承关系
- 类的内部解析
- 收尾事项
前言
很久就想写一个关于python类的一系列文章了。尽管我接触 python 只有不到三个月时间,但我以前有其它语言的很好的基础。可以说,当初吸引我学习 python 的一个很重要的原因,就是它的类。
为什么从(2)开始写起呢?因为(1)要留给基础的知识。基础知识太多了,需要整理。但是我会尽快吧。
前提
前面发了一系列的一图看懂
的图和文系列,但效果不太好。
大家的疑问可能在于:python的类清清楚楚、明明白白地在那里,需要去探索吗?或者,这些图到底有什么用处呢?
一个有基础的熟练编程者似乎用不着这些,而一个新手或刚接触python不久的人,还在懵懵懂懂的阶段,看了这些图似乎也没啥用处。
这要从一个学习方法谈起。
两种学习方法
我的认知里,学习方法可以划分为两类:一类是自底向上
学习方法,一类是自顶向下
学习方法。(此方法论划分为大侠原创)
所谓,自底向上
学习方法,就拿学习 python 来说,就是先学习最基础的东西,比如关键字、程序结构、数据类型、常用内置函数,熟悉安装、IDE交互界面、查找资源和帮助等等。我们将从Hello world
等一行行的基础代码练习起。我们将经历阵痛:我们看起网上大咖的视频课件讲解津津有味,我们看起别人的示例代码如行云流水,可是我们只要开始敲入一行代码,哪怕是一个最简单的,比如带格式输出的print语句,就错误百出。尴尬了。
而自顶向下
的学习方法,就是先掌握python的全局、全貌,不求细节和精细,而求系统和全面,纲要式地掌握。不需要你会,只需要你了解,只需要知道个名字就行,记不住没关系,大概知道有几个、在何处等就行。就是走马观花、提纲挈领、居高临下。等到差不多有全貌了解之后,再深入到一个选定的方向,或结合你的兴趣、或结合你的目标,比如研究课题、任务等,来进行细致的了解。
两种方法,各有其适用对象。
自底向上,适用于儿童、青少年,或毫无计算机、操作系统、常识等知识的小白。因为那是没有办法的事。你连程序的顺序、选择、循环结构不了解,你连计算机语言有何特点不知道,你连类是个啥玩意更是无从谈起,所以,只能从底向上打基础。
自顶向下,适用于最好是有一门、或几门高级语言基础的,转行、跨界而来的,有较强逻辑思维能力的,有较好计算机基础知识的,其它方面成功人士或知识丰富、阅历深厚、年龄较大等人士。
所以,今天这篇文章,还没写就跑题了:到底是结合类的讲解来谈学习,还是推荐自顶向下学习方法,以类来作举例?或许兼而有之吧。
先从一个例子开始
我们知道,python类,很多无需实例化;而一个类,有一个隐含属性__dict__
,就从它讲起。
说明:
1)不仅仅是类,具有__dict__
属性。
2)之所以叫属性,是一种专业(zhuang B)的叫法,模仿国外大师的说法,连类的method都只能叫属性。
例子:
import inspect
print(inspect.__dict__)
为什么是 inspect
呢?只是用它来举例,任何模块举例都可以,只是即将用到的很多知识,正好是这个模块里面的。关于 inspect 模块的图文,我已经发过博文了,见:
一图看懂 inspect 资料整理+笔记(大全)
这里,一不小心,又落入全网风行的机构培训广告俗套:“四行代码爬取某视频网站VIP视频”。
的确,不算模块引入import
语句,以上代码只用了一行,打印了inspect
模块的字典__dict__
内容。
这里还是啰嗦两句:
假设1:你已经知道 dict 类型及它的特点。
假设2:你已经知道,这里的 inspect 是一个模块,不是一个变量名这么简单。
以上这两点假设,细究一下,真的很有意思。
print(type(inspect))
# <class 'module'>
print(type(inspect).__name__)
# module
可见, inspect 是一个模块吧?
print(inspect.ArgInfo.__dict__)
# {'__doc__': 'ArgInfo(args, varargs, keywords, locals)', ...}
print(type(inspect.ArgInfo).__name__)
# type
不仅是模块,ArgInfo
是inspect模块的一个类,它也有__dict__
吧?
回到本例开头:我们打印了一个模块的字典。
由于篇幅,我没有给出输出。
请问,你看出什么了没有?
假如你是刚入门的新手,如果你照着这一行代码敲一下,发现它输出了一大堆的内容。
假如你已入门了,你可能会说,嗯,它是一个模块的字典。仅此而已。
仅此而已吗?
稍微优化一下输出
import inspect
_dict = inspect.__dict__
i = 1
for key in _dict:print(f'{i}\t{type(_dict[key]).__name__}\t{key}:{_dict[key]}')i += 1
看起来没有改变什么,我们加了一个序号,输出了字典中的每个键值对的键、对象、以及对象的类型。
输出的结果,这里不想重复,还是参看一图看懂 inspect 资料整理+笔记(大全)
因为,本文的任务,并不是介绍 inspect ,是介绍探究 对象的方法。
这里,我们分析结果,可以看到,类别中,有各种数据类型: int, bool,float,str,tuple,dict ,set,list,还有各种对象类型,有 fucntion,type,module,builtin_function_or_method 等等。
[注:]以上数据类型,不一定全部会出现,视不同的模块而言。
看到这些结果,这正是我们希望看到的:一个模块,它里面有什么?有数据(各种数据)、有类定义、函数(方法)定义、还有模块。
以下,我们将沿着两条路线,探索模块里面的内容:
- 一条是模块。各个模块之间的关系是怎样的?
- 一条是类:类与其它的类是什么关系(继承)?类里面包含了什么内容?
模块
首先,我们将一个模块字典里,有关的module的对象筛选出来。方法不止一种:
import inspect
_dict = inspect.__dict__
i = 1
for key in _dict:obj = _dict[key]if type(obj).__name__ == 'module':print(f'{i}\t{type(obj).__name__}\t{key}:{obj}')i += 1
另一种方法:利用
ismodule
函数。
import inspect
_dict = inspect.__dict__
list_mod = [en for en in _dict.keys() if inspect.ismodule(_dict[en])]
print(list_mod)
方法三:利用
filter
过滤器
import inspect
_dict = inspect.__dict__
list_mod = list(filter(lambda x: type(_dict[x]).__name__ == 'module', _dict.keys()))
print(list_mod)
第一种方法,得到的结果:
1 module abc:<module 'abc' from '.\\lib\\abc.py'>
2 module dis:<module 'dis' from '.\\lib\\dis.py'>
3 module collections:<module 'collections' from '.\\lib\\collections\\__init__.py'>
4 module enum:<module 'enum' from '.\\lib\\enum.py'>
5 module importlib:<module 'importlib' from '.\\lib\\importlib\\__init__.py'>
6 module itertools:<module 'itertools' (built-in)>
7 module linecache:<module 'linecache' from '.\\lib\\linecache.py'>
8 module os:<module 'os' from '.\\lib\\os.py'>
9 module re:<module 're' from '.\\lib\\re.py'>
10 module sys:<module 'sys' (built-in)>
11 module tokenize:<module 'tokenize' from '.\\lib\\tokenize.py'>
12 module token:<module 'token' from '.\\lib\\token.py'>
13 module types:<module 'types' from '.\\lib\\types.py'>
14 module warnings:<module 'warnings' from '.\\lib\\warnings.py'>
15 module functools:<module 'functools' from '.\\lib\\functools.py'>
16 module builtins:<module 'builtins' (built-in)>
可以看到,模块的名称,以及定义模块的文件。
我们可以看到,里面包含了非常多基础的模块,例如,os、sys、builtins、re、types、enum 等等。
那么,对于初学者,对这些基础的模块,一一去探究、去学习,用后面介绍的探究类结构的方法。这样岂不是事半功倍?
假如,我们用自底向上的方法,每碰到一个函数,不懂,去查资料、搜网络、找用例,这样没什么不妥,只是进度会相当的慢。
越是基础的东西,例如这些基础的模块,里面包含的基础的类、函数等,我们今后大多数要用到。所以,既然迟早要学到,倒不如系统地横扫过去,这样会精进快速。
在inspect模块里为什么会出现其它模块的名字呢?是因为,在inspect模块的某一处,用到了其它模块的类、函数(方法)或其它数据。
每个模块得到的结果是不一样的。较为复杂的模块(包,package),模块是一个复杂的族群,自身用到的、直接引用的、间接引用的,不知凡几。
笔者后面附加给出了递归调用的函数,但不准备展开了。因为据笔者研究,这个模块展开研究下去,意义不大。理由有二:
一是,虽然可以探究不同模块、特别是一些专业模块,它们的模块族群,还可以研究模块的版本、起源等等。但对我们学习编程和语言本身来说,上面所说两条路径,这一条其实就到此为止。大概了解一下即可以了。
二是,模块和库、包之间的定义并非是一个很严格的层次逻辑(Hierachical)。也就是说,它不是树类型。
附录1:递归地找出模块勾连关系的函数,仅供参考。
def getmodules(my_mod, modlist: list = [], deep: int = 1):""" 递归获取。deep深度: 1-只到本模块,及包含的模块;2-包含的模块再展开1级;3-展开直至穷尽。"""mod_dict = getattr(my_mod, '__dict__', dict())if len(mod_dict) == 0:if my_mod.__name__ not in modlist:modlist.append(my_mod.__name__)print(f'{my_mod.__name__}')returnelse:if my_mod.__name__ not in modlist:modlist.append(my_mod.__name__)_li = getmodule(my_mod)if len(_li) == 0:returnelse:print(f'{my_mod.__name__}')for _en in _li:if deep == 2:if _en not in modlist:modlist.append(_en)print(f'\t{_en}')if deep == 1:returnfor _en in _li:getmodules(mod_dict[_en], modlist, deep)
附录2:递归研究的例子。
用以上递归函数,我们对 urllib3 模块只展到2级,就已很复杂了。(urllib3 的例子,我在后期,也有一图看懂
介绍。)
类似的,例如,numpy、tkinter等很多第三方模块,越是大而复杂,展开的层次就越多。
urllib3loggingwarningsexceptions_versionrequestpackagessixfunctoolsitertoolsoperatorsystypescontrib_appengine_environosutilwaiterrnoselectsysconnectionsocket_appengine_environsixresponsehttplibretryemailloggingretimewarningssixurlresixssltransportiosocketsslsixssl_hmacossyswarningssixssltimeouttimessl_match_hostnameresysipaddressqueuecollectionssixqueuerequestproxy_collectionssixconnectiondatetimeloggingosresocketwarningssixsslconnectionfieldsemailmimetypesresixfilepostbinasciicodecsossixresponseiologgingsyswarningszlibutilsixconnectionpoolerrnologgingresocketsyswarningssixqueuepoolmanagercollectionsfunctoolsloggingsix
http.clientemailhttpioresocketcollectionsssl
数据
上面说到,探索一个模块分为两条路径;上面也介绍了模块,可以说是浅尝则止了,理由前面也介绍过了。
在介绍最重要的类、函数的剥茧抽丝的过程之前,先让我们扫清数据
吧。
这时所说的数据,就是指类、方法、函数、模块等之外定义的一切。
包括两大部分:一类,我们叫静态数据(请注意,叫静态数据,其实是不规范的叫法,它其实也是动态
的。)就是前面提到的 int, bool,float,str,tuple,dict ,set,list 等等类型的数据。另一类,是对我们编程来说不太重要的一些系统类等。
对于这两类,我们仅列示清单,一带而过。
例如,对于 所有 int
型变量(全局或局部,下同),仅用一句就够了:
import inspect
_dict = inspect.__dict__
list_int = list(filter(lambda x: type(_dict[x]).__name__ == 'int', _dict.keys()))
print(list_int)
可是,对于以上至少八种类型,都要重复这样的代码吗?
看以下代码,我们有变通的法则:
import inspectdef printli():i = 1for key in list_obj:print(f'{i}\t{type(_dict[key]).__name__}\t{key}:{_dict[key]}')i += 1_dict = inspect.__dict__
_typelist = ['int', 'bool', 'float', 'str', 'tuple', 'list', 'dict', 'set']
list_obj = list(en for en in _dict.keys() if type(_dict[en]).__name__ in _typelist)
list_obj.sort(key=lambda x: type(_dict[x]).__name__)
printli()
如果是略有强迫症的我,一定要按照 _typelist 的顺序来显示呢?
import inspectdef printli():i = 1for _type in _typelist:for key in list_obj:if type(_dict[key]).__name__ == _type:print(f'{i}\t{type(_dict[key]).__name__}\t{key}:{_dict[key]}')i += 1_dict = inspect.__dict__
_typelist = ['int', 'bool', 'float', 'str', 'tuple', 'list', 'dict', 'set']
list_obj = list(en for en in _dict.keys() if type(_dict[en]).__name__ in _typelist)
# list_obj.sort(key=lambda x: type(_dict[x]).__name__)
# 这里的排序就没有必要了。
printli()
于是我们得到以下完美的输出:
(为节省篇幅,省略了部分长内容)
1 int k:512
2 int CO_OPTIMIZED:1
3 int CO_NEWLOCALS:2
4 int CO_VARARGS:4
5 int CO_VARKEYWORDS:8
6 int CO_NESTED:16
7 int CO_GENERATOR:32
8 int CO_NOFREE:64
9 int CO_COROUTINE:128
10 int CO_ITERABLE_COROUTINE:256
11 int CO_ASYNC_GENERATOR:512
12 int TPFLAGS_IS_ABSTRACT:1048576
13 str __name__:inspect
14 str __doc__:Get useful information from live Python objects....
15 str __package__:
16 str __file__:D:\ProgramFiles\Python\Python37\lib\inspect.py
17 str __cached__:...\lib\__pycache__\inspect.cpython-37.pyc
18 str v:ASYNC_GENERATOR
19 str GEN_CREATED:GEN_CREATED
20 str GEN_RUNNING:GEN_RUNNING
21 str GEN_SUSPENDED:GEN_SUSPENDED
22 str GEN_CLOSED:GEN_CLOSED
23 str CORO_CREATED:CORO_CREATED
24 str CORO_RUNNING:CORO_RUNNING
25 str CORO_SUSPENDED:CORO_SUSPENDED
26 str CORO_CLOSED:CORO_CLOSED
27 tuple __author__:('Ka-Ping Yee <ping@lfw.org>', 'Yury Selivanov <yselivanov@sprymix.com>')
28 tuple _NonUserDefinedCallables:(<class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'classmethod_descriptor'>, <class 'builtin_function_or_method'>)
29 dict __builtins__:{...}
30 dict mod_dict:{...}
31 dict modulesbyfile:{}
32 dict _filesbymodname:{}
33 dict _PARAM_NAME_MAPPING:{...}
函数
参见以上模块的代码,我们只需将类名称改为 funcion
即可得到输出。
import inspect
_dict = inspect.__dict__
i = 1
for key in _dict:obj = _dict[key]if type(obj).__name__ == 'function':print(f'{i}\t{type(obj).__name__}\t{key}:{obj}')i += 1
以下这个输出不太完美,不是我们想要的。
1 function namedtuple:<function namedtuple at 0x0000000002885AF8>
2 function ismodule:<function ismodule at 0x000000000291F5E8>
...
86 function _main:<function _main at 0x0000000002981DC8>
我们需要将函数的定义显示出来。函数,以及下一节的类,将是我们探究的重点。
所以,如有必要,我们要将函数定义的文件找出来,函数定义所处的行号(这个可以为以后更精美、复杂的系统开发作准备),还有文档__doct__
找出来以及做到机器自动翻译。
import inspect
from inspect import *
_dict = inspect.__dict__
s_fun = [en for en in _dict.keys() if isfunction(_dict[en])]
i = 1
for en in s_fun:obj = _dict[en]_name = getattr(obj, '__name__', '') + str(signature(obj))_code = getattr(obj, '__code__', '')_file = getattr(_code, 'co_filename', '')_lineno = getattr(_code, 'co_firstlineno', '')_doc = getattr(obj, '__doc__', '') # getdoc(obj)print(f'{i}\t{_name}\tline:{_lineno} file:{_file}')# print(_doc)# print(EnToChinese(_doc))i += 1
稍解释一下:
- 请注意,导入稍有变化,
from inspect import *
,是因为我们用了isfunction
和signature
函数。当然,你不导入时,可以加前缀,效果是一样的,例如:inspect.isfunction
- 获取属性,有多种方法,例如,要想获取
__doc__
属性,用getdoc(obj)
也是可以的,但如果要获取文档的对象不具有这一属性时,程序会报错中止。所以,用getattr(obj, '__doc__', '')
,即使对象没有__doc__
属性,但它会返回空字串。 - 限于篇幅,我这里没有打印
_doc
,同样,给出了翻译的伪函数,EnToChinese
,它将用到网络爬取技术,这里也不准备展开它。
输出如下:
1 namedtuple(typename, field_names, *, rename=False, defaults=None, module=None) line:316 file:...\lib\collections\__init__.py
2 ismodule(object) line:63 file:...\lib\inspect.py
3 isclass(object) line:72 file:...\lib\inspect.py
4 ismethod(object) line:80 file:...\lib\inspect.py
5 ismethoddescriptor(object) line:90 file:...\lib\inspect.py
6 isdatadescriptor(object) line:110 file:...\lib\inspect.py
7 ismemberdescriptor(object) line:126 file:...\lib\inspect.py
8 isgetsetdescriptor(object) line:143 file:...\lib\inspect.py
9 isfunction(object) line:158 file:...\lib\inspect.py
10 isgeneratorfunction(object) line:171 file:...\lib\inspect.py
11 iscoroutinefunction(object) line:179 file:...\lib\inspect.py
12 isasyncgenfunction(object) line:187 file:...\lib\inspect.py
13 isasyncgen(object) line:196 file:...\lib\inspect.py
14 isgenerator(object) line:200 file:...\lib\inspect.py
15 iscoroutine(object) line:217 file:...\lib\inspect.py
16 isawaitable(object) line:221 file:...\lib\inspect.py
17 istraceback(object) line:228 file:...\lib\inspect.py
18 isframe(object) line:238 file:...\lib\inspect.py
19 iscode(object) line:252 file:...\lib\inspect.py
20 isbuiltin(object) line:276 file:...\lib\inspect.py
21 isroutine(object) line:285 file:...\lib\inspect.py
22 isabstract(object) line:292 file:...\lib\inspect.py
23 getmembers(object, predicate=None) line:316 file:...\lib\inspect.py
24 classify_class_attrs(cls) line:362 file:...\lib\inspect.py
25 getmro(cls) line:478 file:...\lib\inspect.py
26 unwrap(func, *, stop=None) line:484 file:...\lib\inspect.py
27 indentsize(line) line:520 file:...\lib\inspect.py
28 _findclass(func) line:525 file:...\lib\inspect.py
29 _finddoc(obj) line:535 file:...\lib\inspect.py
30 getdoc(object) line:594 file:...\lib\inspect.py
31 cleandoc(doc) line:613 file:...\lib\inspect.py
32 getfile(object) line:642 file:...\lib\inspect.py
33 getmodulename(path) line:668 file:...\lib\inspect.py
34 getsourcefile(object) line:680 file:...\lib\inspect.py
35 getabsfile(object, _filename=None) line:702 file:...\lib\inspect.py
36 getmodule(object, _filename=None) line:714 file:...\lib\inspect.py
37 findsource(object) line:760 file:...\lib\inspect.py
38 getcomments(object) line:833 file:...\lib\inspect.py
39 getblock(lines) line:935 file:...\lib\inspect.py
40 getsourcelines(object) line:946 file:...\lib\inspect.py
41 getsource(object) line:967 file:...\lib\inspect.py
42 walktree(classes, children, parent) line:977 file:...\lib\inspect.py
43 getclasstree(classes, unique=False) line:987 file:...\lib\inspect.py
44 getargs(co) line:1016 file:...\lib\inspect.py
45 _getfullargs(co) line:1026 file:...\lib\inspect.py
46 getargspec(func) line:1056 file:...\lib\inspect.py
47 getfullargspec(func) line:1089 file:...\lib\inspect.py
48 getargvalues(frame) line:1182 file:...\lib\inspect.py
49 formatannotation(annotation, base_module=None) line:1192 file:...\lib\inspect.py
50 formatannotationrelativeto(object) line:1201 file:...\lib\inspect.py
51 formatargspec(args, varargs=None, varkw=None, defaults=None, kwonlyargs=(), kwonlydefaults={}, annotations={}, formatarg=<class 'str'>, formatvarargs=<function <lambda> at 0x0000000002109168>, formatvarkw=<function <lambda> at 0x00000000021091F8>, formatvalue=<function <lambda> at 0x0000000002109288>, formatreturns=<function <lambda> at 0x0000000002109318>, formatannotation=<function formatannotation at 0x0000000002109048>) line:1207 file:...\lib\inspect.py
52 formatargvalues(args, varargs, varkw, locals, formatarg=<class 'str'>, formatvarargs=<function <lambda> at 0x00000000021094C8>, formatvarkw=<function <lambda> at 0x0000000002109558>, formatvalue=<function <lambda> at 0x00000000021095E8>) line:1265 file:...\lib\inspect.py
53 _missing_arguments(f_name, argnames, pos, values) line:1288 file:...\lib\inspect.py
54 _too_many(f_name, args, kwonly, varargs, defcount, given, values) line:1304 file:...\lib\inspect.py
55 getcallargs(*func_and_positional, **named) line:1325 file:...\lib\inspect.py
56 getclosurevars(func) line:1389 file:...\lib\inspect.py
57 getframeinfo(frame, context=1) line:1444 file:...\lib\inspect.py
58 getlineno(frame) line:1476 file:...\lib\inspect.py
59 getouterframes(frame, context=1) line:1483 file:...\lib\inspect.py
60 getinnerframes(tb, context=1) line:1495 file:...\lib\inspect.py
61 currentframe() line:1507 file:...\lib\inspect.py
62 stack(context=1) line:1511 file:...\lib\inspect.py
63 trace(context=1) line:1515 file:...\lib\inspect.py
64 _static_getmro(klass) line:1524 file:...\lib\inspect.py
65 _check_instance(obj, attr) line:1527 file:...\lib\inspect.py
66 _check_class(klass, attr) line:1536 file:...\lib\inspect.py
67 _is_type(obj) line:1545 file:...\lib\inspect.py
68 _shadowed_dict(klass) line:1552 file:...\lib\inspect.py
69 getattr_static(obj, attr, default=<object object at 0x0000000000507DC0>) line:1566 file:...\lib\inspect.py
70 getgeneratorstate(generator) line:1619 file:...\lib\inspect.py
71 getgeneratorlocals(generator) line:1637 file:...\lib\inspect.py
72 getcoroutinestate(coroutine) line:1661 file:...\lib\inspect.py
73 getcoroutinelocals(coroutine) line:1679 file:...\lib\inspect.py
74 _signature_get_user_defined_method(cls, method_name) line:1707 file:...\lib\inspect.py
75 _signature_get_partial(wrapped_sig, partial, extra_args=()) line:1723 file:...\lib\inspect.py
76 _signature_bound_method(sig) line:1799 file:...\lib\inspect.py
77 _signature_is_builtin(obj) line:1825 file:...\lib\inspect.py
78 _signature_is_functionlike(obj) line:1837 file:...\lib\inspect.py
79 _signature_get_bound_param(spec) line:1862 file:...\lib\inspect.py
80 _signature_strip_non_python_syntax(signature) line:1885 file:...\lib\inspect.py
81 _signature_fromstr(cls, obj, s, skip_bound_arg=True) line:1957 file:...\lib\inspect.py
82 _signature_from_builtin(cls, func, skip_bound_arg=True) line:2101 file:...\lib\inspect.py
83 _signature_from_function(cls, func) line:2117 file:...\lib\inspect.py
84 _signature_from_callable(obj, *, follow_wrapper_chains=True, skip_bound_arg=True, sigcls) line:2198 file:...\lib\inspect.py
85 signature(obj, *, follow_wrapped=True) line:3081 file:...\lib\inspect.py
86 _main() line:3086 file:...\lib\inspect.py
类
类中具有数据、还有结构(类中没有禁止定义子类,以及各种类别的函数、方法)。类具有继承、多态、重载等特性,类还有构造、销毁等过程,此外,python还有一系列的特有隐藏属性、运行机制(堆栈)等。因此,类是相当丰富多彩的,既然python中的名言万物皆对象
,我们只有掌握好了类,而且还要快速掌握好,才能编我所需、在python的空间自由翱翔。
类的探索从两个方面。第一个,从类的继承说起。
在前面,我们探索模块,试图搞清万物皆模块
这个玩意,后来发现行不通,因它无逻辑或逻辑性不强(此说法不准确,因为模块精确地定义了程序的命名空间,从来就没有二义性),所以我们放弃了。
如同上面,我们先把模块中所有的类都“抓取”出来。
import inspect
from inspect import *
_dict = inspect.__dict__
s_cls = [en for en in _dict.keys() if isclass(_dict[en])]
i = 1
for key in s_cls:obj = _dict[key]print(f'{i}\t{key}:{obj}')i += 1
输出如下:
1 attrgetter:<class 'operator.attrgetter'>
2 OrderedDict:<class 'collections.OrderedDict'>
3 Attribute:<class 'inspect.Attribute'>
4 EndOfBlock:<class 'inspect.EndOfBlock'>
5 BlockFinder:<class 'inspect.BlockFinder'>
6 Arguments:<class 'inspect.Arguments'>
7 ArgSpec:<class 'inspect.ArgSpec'>
8 FullArgSpec:<class 'inspect.FullArgSpec'>
9 ArgInfo:<class 'inspect.ArgInfo'>
10 ClosureVars:<class 'inspect.ClosureVars'>
11 Traceback:<class 'inspect.Traceback'>
12 FrameInfo:<class 'inspect.FrameInfo'>
13 _WrapperDescriptor:<class 'wrapper_descriptor'>
14 _MethodWrapper:<class 'method-wrapper'>
15 _ClassMethodWrapper:<class 'classmethod_descriptor'>
16 _void:<class 'inspect._void'>
17 _empty:<class 'inspect._empty'>
18 _ParameterKind:<enum '_ParameterKind'>
19 Parameter:<class 'inspect.Parameter'>
20 BoundArguments:<class 'inspect.BoundArguments'>
21 Signature:<class 'inspect.Signature'>
类的继承关系
还记得一图看懂 inspect 资料整理+笔记(大全)里面的那张类的继承关系图吗?这里有必要链接一下:
因为我们马上要讲到这张图是如何做出来的了。
我们随便选择一个类,例如 ArgInfo,
python中的类,都有两个隐藏属性__base__和__bases__。前者是继承的基类,按顺序是第一个;后者是继承的所有基类,按定义顺序,以元组形式返回。当继承的基类只有一个时,也返回元组。
inspect.ArgInfo.__base__
# <class 'tuple'>
inspect.ArgInfo.__bases__
# (<class 'tuple'>,)
从以上看出,ArgInfo
继承了基类 tuple
,那么 tuple
又继承了谁呢?
inspect.ArgInfo.__base__.__base__
# <class 'object'>
tuple 继承了 object, 即最底层的元基类。python 中,所有类,最终都以 object 为根。万类归一 object
。
正是基于这一思路,很容易就写出一个递归函数:只要给定模块,就可找出所有类(type类型),每一个类都可以找到其继承的基类,直至最底层的元基类 object。
# 获得一个模块中,所有 class 对象的所有继承链,即形成继承关系的树图或网络图。
def GetAllObjBases(my_mod):mod_dict = my_mod.__dict__for en in mod_dict:obj = mod_dict[en]if type(obj).__name__ == 'type':str1 = ''str1 = GetObjBases(obj, str1)print(_reverseStr(str1))
运行GetAllObjBases(inspect),即可得到自定义的继承链数据格式。这一格式导入到作图软件中,即可得到上图。
研究类的继承关系,还是有一定的意义的:
- 体现自顶向下学习大法,对于很复杂的库、包、模块群,很容易理清关系。再结合即将展现的类的内部构造,可以说,倾刻间,就对一复杂事物有个概貌了解。
- 你如果需要了解类的内部(因为你一定会了解的,函数或方法你需要知道吧?),那么了解了类的向上的继承链,是不是会很有好处呢?随便举一个例子,比如,tkinter.ttk 的一个组件,它的继承链非常长,如果你了解,就会对它的属性等快速掌握,否则,很容易迷失。
说实话,我研究类、以及提出自顶向下学习的动机,是基于以下两个事实:
1)每次我打开Python IDE时,在软件包那里,显示有 436597
个(此刻)第三方的包等着你去下载。
你不觉得恐怖吗?假如象 tkinter 和 numpy 这样的包都只算一个话。它里面可是有好几百的函数和类。
2)第二个事实,是 ChatGPT 这样的AI智脑出现,它将来会替代普通程序员80%的编程工作。
我总觉得,所有我未来想实现的编程功能、梦想,别人都已经替我实现了。即使剩下5%没有实现的,ChatGPT们
不会再给我们机会了。
所以,现在你确定还要再继续学习python吗?还要立下Flag:不达全栈不剃头
(这是我在朋友看的真实励志案例)
假如你和我一样,虽然脸色戚戚但依然坚定回答:是!
那么,也请你和我一样,找到现阶段学习的利器、更新我们的学习工具。
否则,到明年,我们可能还在一个包里面打转转。
类的内部解析
正如在“类”这一节所讲,类很复杂,所有以上对模块所做的事情,类也同样存在:类与类之间是什么关系?(或者说,这个类与哪些类有关系?有什么关系?)类包含哪些数据?私有的和公共的,属性(类属性、对象属性),方法(静态方法、类方法、对象方法、事件)、系统属性和方法(构造、销毁等)。
以上我们已把类从字典中抽取出来,现在直接展开。先从数据开始。
# 以下为非完整代码
c_int = [] # 分类的常数
c_bool = []
c_str = []
c_float = []
c_tuple = []
c_list = []
c_dict = []
c_set = []
_typelist = ['int', 'bool', 'float', 'str', 'tuple', 'list', 'dict', 'set']
# 对 8 类(可扩充)以 c_ 开头的常量,进行分类。
for _typename in self._typelist:_c_list = eval('self.c_' + _typename) # 代表 c_ 开头的变量(列表)assert type(_c_list) == listfor en in self.keys:obj = self.dic[en]if type(obj).__name__ == _typename:_c_list.append(en)self.keys = list_sub(self.keys, _c_list)
在这里,就用到了我在《python中,可以在运行时定义变量吗?》中所谈及的技术:变量的实时定义。
对于这八类数据,我们可以写一个通用的输出方法。
接下来,描述函数。
在类中涉及的模块、子类,虽然很罕见,但是也列在这里。
内嵌函数和方法,虽然极其重要,但此内容我们仅需掌握一次(并非每涉及到一个类,我们都要将内嵌函数拿出来复习一遍),所以,为完整性,我们列示在此,输出时,可以考虑忽略或根据需要。
# 对 4 类:模块、类、函数、内嵌进行分类。
self.s_mod = [en for en in self.keys if ismodule(self.dic[en])]
self.s_cls = [en for en in self.keys if isclass(self.dic[en]) and self.dic[en].__module__ == self.mod.__name__]
self.s_fun = [en for en in self.keys if isfunction(self.dic[en]) and self.dic[en].__module__ == self.mod.__name__]
self.s_bui = [en for en in self.keys if isbuiltin(self.dic[en])]
你可以看到,我在这里用了 inspect
的四个函数。读完全篇,顺带也把 inspect 掌握了,也算是附加收获吧。(知道我在文章开头为什么选择 inspect 模块本身作为例子了吧?)
实际上,为了区分局部或全局部的对象,还有系统的对象,我们可以根据locals()
或globals()
,或直接根据命名来区分。
# 对以‘__’开头的单独处理。
for en in self.keys:obj = self.dic[en]if str(en).startswith('__'):self.c_sys.append(en)# 对以‘_’开头的单独处理。
for en in self.keys:obj = self.dic[en]if str(en).startswith('_'):self.c_residual.append(en)
先不忙着对以上进行输出。我们进行全篇最重要的东西——类进行进一步的展开。
我们初步将类,展开为:数据、属性、方法、静态方法、类方法等(见以下代码第2行)。
方法和类方法的区别是:方法是对象方法,以 self
为形参,在实例化时,它的副本存储在栈区域,而类方法,以 cls
为形参,除非 override
,它不可被实例改变,存储在堆区域,在定义时加了 @classmethod
修饰符。
attrs = classify_class_attrs(obj)
_clstypelist = ['data', 'property', 'method', 'static method', 'class method']
_li1 = list(filter(lambda _en2: _en2[1] == 'method' and _en2[2] == obj, attrs1)) # method
_li2 = list(filter(lambda _en2: _en2[1] == 'class method' and _en2[2] == obj, attrs1)) # class method
_li3 = list(filter(lambda _en2: _en2[1] == 'static method' and _en2[2] == obj, attrs1)) # static method
_li4 = list(filter(lambda _en2: _en2[1] == 'property' and _en2[2] == obj, attrs1)) # property
_li5 = list(filter(lambda _en2: _en2[1] == 'data' and (not _en2[0].startswith('__')) and _en2[2] == obj, attrs)) # data
_set6 = dict() # inherited
_li7 = attrs + [] # residual
for obj1 in obj.__bases__:_li6 = list(filter(lambda _en2: _en2[1] in ['method', 'static method', 'class method'] and _en2[2] == obj1, attrs))_set6[obj1.__name__] = _li6_li7 = self._list_sub(_li7, _li6)
对以上五类区分,我们用了 inspect
的一个函数 classify_class_attrs
解决。(从命名看,这五类通通叫作属性
,证明我前文把函数、方法叫属性非虚言)
以上,-set6
字典展开继承关系,也就是对方法(方法和函数不加区分,特别是在python里),进一步区分它是类所定义的本地方法,还是继承的方法、继承自谁的方法等。
而 _li7
描述残余的对象。还记得前文所描述的吗?也就是说,对我们编程不太重要的一些对象、私有属性、数据等,我们不太关注它,但是我们检索出来,有需要时,随时备查就OK了。
换一句说法,一个模块或类的字典,除去数据(暂定八类)、函数、内嵌函数、类及类的所有构件、系统保留属性和方法,并非全集,加上这个 _li7
残余,就等于 __dict__
全集了。
剩下的输出工作,翻译工作,还有很多细节的处理,都不是本篇的重点内容,所以省略了。
很抱歉的是,我并没有给出完整的代码。不过大部分的关键代码已经展示出来了,最重要的是,我描述了我这么做的理由,结合了一个方法论。
最后的输出,一定是我在一图看懂
系列里面呈现给大家的。
希望您能独立地去探究类。
我在学习探究的过程中,曾经模仿系统的 help()的输出,如今我做到了。所以也希望新手积极地、象我一样认真地去探究类,这个算是作业吧,也是检验诸位是否读懂此篇。至于老手,大咖、大V等,算了吧,他们一般不会看我这个新手的文章、即使看,也是看个标题就走了,看不到这儿来。
收尾事项
至此,我们对从一个模块展开,重点到类,以及类它里面所有细节,就象庖丁解牛一样,分解得淋漓尽致。
关键的是,我们不是对一个模块、包、第三方库或者类进行的此工作,我们是用一个方法,也就是说:这个方法可以扩展、应用到所有的模块和类上面。
我曾经作过一个比较,如果用自底向上的学习方法,一个月掌握或相对了解一个象 tkinter 这样的模块,是很勉强的。而运用我这个方法,1-2天,相对粗浅地了解一个模块,还是有可能的。
正如我在上述类的继承关系所谈的,我的忧虑来自于两个方面:海量的资源和ChatGPT的紧逼。所以,以下面两句感言作结束:
如此的时点,真的不是一个学习python的好时机。
我的意思不是在说python,而是说任何计算机语言,任何知识。
当我们的对手——AI们
,它们一晚上的训练,就在你睡一觉的功夫,它们可以训练多少万行模块、代码,它们编程的速度、质量远超我们。而我们,花了数天的时间,在打磨一个函数的用法。真的,编程,只是我们的一个兴趣爱好罢了。
不要费尽心思去编一个新的程序了(特指象我这样的新手)。所有的东西就在那里。我们所要做的,只不过是费尽心思去找,找来之后,运用它就够了。
原创?原创当然很重要,不过它是全人类的事。千成不要把自己这个个体,当成了全人类,否则你会很烦恼。