本文由 大侠(AhcaoZhu)原创,转载请声明。
链接: https://blog.csdn.net/Ahcao2008
前言
很久就想写一个关于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__}')
return
else:
if my_mod.__name__ not in modlist:
modlist.append(my_mod.__name__)
_li = getmodule(my_mod)
if len(_li) == 0:
return
else:
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:
return
for _en in _li:
getmodules(mod_dict[_en], modlist, deep)
附录2:递归研究的例子。
用以上递归函数,我们对 urllib3 模块只展到2级,就已很复杂了。(urllib3 的例子,我在后期,也有一图看懂
介绍。)
类似的,例如,numpy、tkinter等很多第三方模块,越是大而复杂,展开的层次就越多。
urllib3
logging
warnings
exceptions
_version
request
packages
six
functools
itertools
operator
sys
types
contrib
_appengine_environ
os
util
wait
errno
select
sys
connection
socket
_appengine_environ
six
response
httplib
retry
email
logging
re
time
warnings
six
url
re
six
ssltransport
io
socket
ssl
six
ssl_
hmac
os
sys
warnings
six
ssl
timeout
time
ssl_match_hostname
re
sys
ipaddress
queue
collections
six
queue
request
proxy
_collections
six
connection
datetime
logging
os
re
socket
warnings
six
ssl
connection
fields
email
mimetypes
re
six
filepost
binascii
codecs
os
six
response
io
logging
sys
warnings
zlib
util
six
connectionpool
errno
logging
re
socket
sys
warnings
six
queue
poolmanager
collections
functools
logging
six
http.client
email
http
io
re
socket
collections
ssl
数据
上面说到,探索一个模块分为两条路径;上面也介绍了模块,可以说是浅尝则止了,理由前面也介绍过了。
在介绍最重要的类、函数的剥茧抽丝的过程之前,先让我们扫清数据
吧。
这时所说的数据,就是指类、方法、函数、模块等之外定义的一切。
包括两大部分:一类,我们叫静态数据(请注意,叫静态数据,其实是不规范的叫法,它其实也是动态
的。)就是前面提到的 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 inspect
def printli():
i = 1
for 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 inspect
def printli():
i = 1
for _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) == list
for 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们
,它们一晚上的训练,就在你睡一觉的功夫,它们可以训练多少万行模块、代码,它们编程的速度、质量远超我们。而我们,花了数天的时间,在打磨一个函数的用法。真的,编程,只是我们的一个兴趣爱好罢了。
不要费尽心思去编一个新的程序了(特指象我这样的新手)。所有的东西就在那里。我们所要做的,只不过是费尽心思去找,找来之后,运用它就够了。
原创?原创当然很重要,不过它是全人类的事。千成不要把自己这个个体,当成了全人类,否则你会很烦恼。