关于类的探索(2)

本文由 大侠(AhcaoZhu)原创,转载请声明。
链接: https://blog.csdn.net/Ahcao2008

Alt

前言

很久就想写一个关于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 *,是因为我们用了 isfunctionsignature 函数。当然,你不导入时,可以加前缀,效果是一样的,例如: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 资料整理+笔记(大全)里面的那张类的继承关系图吗?这里有必要链接一下:
inspect00
因为我们马上要讲到这张图是如何做出来的了。
我们随便选择一个类,例如 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),即可得到自定义的继承链数据格式。这一格式导入到作图软件中,即可得到上图。

研究类的继承关系,还是有一定的意义的:

  1. 体现自顶向下学习大法,对于很复杂的库、包、模块群,很容易理清关系。再结合即将展现的类的内部构造,可以说,倾刻间,就对一复杂事物有个概貌了解。
  2. 你如果需要了解类的内部(因为你一定会了解的,函数或方法你需要知道吧?),那么了解了类的向上的继承链,是不是会很有好处呢?随便举一个例子,比如,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们,它们一晚上的训练,就在你睡一觉的功夫,它们可以训练多少万行模块、代码,它们编程的速度、质量远超我们。而我们,花了数天的时间,在打磨一个函数的用法。真的,编程,只是我们的一个兴趣爱好罢了。

不要费尽心思去编一个新的程序了(特指象我这样的新手)。所有的东西就在那里。我们所要做的,只不过是费尽心思去找,找来之后,运用它就够了。

原创?原创当然很重要,不过它是全人类的事。千成不要把自己这个个体,当成了全人类,否则你会很烦恼旺柴

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AhcaoZhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值