python基础-汇总
机器码就是机器能识别的代码
python的字节码,转换成字节码
python和java既能写前台,也能写后台
pypy,用cpython写的python,第一次执行后通过C语言转换成机器码,python存储机器码,以后再来相同的代码,直接找操作系统,不再找C语言
python软件称为python解释器(内存管理)
用终端打开文件的方法
- 1创建.py文件
- 2终端进入文件夹位置,op+cmd+c即可复制位置,或者直接将文件拖入至终端,不要文件名
- 3python 文件名执行即可(改成txt等后缀都能执行)
python两种执行方式
- 1python解释器 py文件路径
- 2python 然后进入解释器:实时输入并获取到执行结果
python变量,由字母,数字,下划线组成,不能由数字开头,不能占用python已经用的关键字(如class),驼峰式命名
条件语句
1代码块,TAB四个空格
2基本语句,支持嵌套,if elif多个判断。如果什么都不执行,写pass,无意义,仅仅用于表示代码块
基本数据类型
数字,字符串,元组,列表,词典,集合
字符串,只要引号引起来就行,' '," ",''' ''', """ """都行。字符串乘法,就是重复出现。
数字,//取商的整数部分
GBK编码是中文编码
以一种编码编,以另外一种编码读,肯定会乱码
utf8中文占用3个字节
gbk中文占用2个字节
python的while也有else语句,但是只执行一次
IDE集成开发环境
“郑建文”,建文,是子字符串,也叫子序列
运算符
- in,和not in。 if判断后的条件,本质上就是true或者false
- 算数运算符和赋值运算符,得到的是数值
- 比较运算,逻辑运算和成员运算符,得到的布尔值
数据类型
- 数字,字符串,布尔值,每种类型有自己的魔法功能,变量.就可以调用魔法
数字
- int将字符串转换为数字,num = "0011",v = int (num, base = 16),将16进制的num转换成10进制
- type()获取数据的类型
- age.bit_length(),表示age的数字取值,用二进制至少用几位来表示。如5的二进制位数至少是3
字符串test= "alex"(7个基本魔法,4个灰魔法)
- test.capitalize(),首字母大写
- test.casefold()和test.lower,所有的变小写
- 遇到魔法中def center(self,width,fillchar = None)没有等号的,都是必须带的。如果有=None,则默认为None。该函数是设置宽度,并将内容居中
- test.l just(20,"*"),占用20位,将test放置左边,*补充空位。r just。
- test.count('r'),计算r在test中出现多少次
- encode和decode很重要
- test.endswith('x'),字符串是否以x结尾。startswith
- test.find('ex')在字符串中找到第一个ex,获取其位置,找不到返回-1。 index(‘x’)也可以找x的位置,找不到返回错误。
- test = 'i am {name}, age = {a}'。v= test.format(name='alex', a=19),格式化,将format中的值替换到test对应的占位符
- test = ‘ i am {0}, age = {1}’,test.format('alex',19),按照format中的顺序填入test对应的占位符,{0},{1}是占位符。 test.format(**{"name":'alex','age':19})也可以传值
- test.format_map({"name":'alex',"a":19}),利用字典的性质进行格式化,与format功能相同
- test.isanum(),检测test字符串是否只包含字母和数字
- test.expandtabs(6),表示按照6位一组来检测有无tab,如果有,则当前组不够6位的数字用空格凑。/t+前边的等于6位长度
- test.isalpha检测test是否仅为字母
- test.isdecimal,和test.isdigit均是判定是否是数字,test.isnumeric()也能判断是否为数字,包括“二”
- test.isidentifier()检测test是否是标识符,只由字母,数字,下划线组成
- test.isprintable(),检测字符串中是否有打印时不可显示的东西,如/t,/n等
- test.isspace(),判断字符串是否全为空格
- test.istitle(),检测字符串是否为标题。标题的每个单词的首字母需要大写。test.title(),将字符串转换为标题
- !!!t.join(test),将字符串的每个元素按照指定分隔符t拼接起来,如t = " ",则将test每个元素之间加个空格。在列表中也能用
- test.upper,isupper,lower,islower,转换为大写,是否全部为大写
- test.lstrip('abc')从左向右,使用字符abc中的子序列进行最大正向匹配,然后去除匹配成功的序列。rstrip,strip,去除左右空白。最长公共子序列
- m = str.makettrans("aeiou", "12345"),将两个字符做匹配,a对应1,然后test.translate(m),是将字符串test中的字符按照m给出的对应关系进行替换,如将test中的a替换为1
- 正则表达式是以下两个魔法的功能合集:
- test.partition('s)将test按照s从左开始分割成3份,rpartition('s')是从右开始分割
- !!!test.split('s',2),是将test从左开始,按照s分割成3份,不显示s。rsplit是从右开始。
- test.splitlines(True),是将test按照换行符号进行分割,并显示换行符号
- test.swapcase(),是对test每个字符进行大小写互换,大写字符转成小写,小写字符转成大写
- test.replace("ex","bbb",n),对test从左开始的前n个ex替换成bbb
-
灰魔法:
- 索引下标来获取字符串中的某一个字符。
- 截取或切片[ 2: 4],左闭右开。
- len获取长度。len在其他数据类型中也能用到,如列表。
-
for循环,for 变量名 in 字符串:print (变量名)。for循环自动完成+1的操作,将字符串一个个遍历。for循环的灰魔法,在其他数据类型中也能用,break和continue也支持
-
深灰魔法
-
字符串在内存中一旦创建,就不可修改。如果修改,则重新创建一个字符串。原因是,如果修改内容大于原本内容,则空间不够用。在java,c#同样
- 创建连续的数字,v= range(0,100,x),for item in v:print(item),python3中不会一开始就创建0到100,只有使用时才创建。x是步长,每x个创建一个。python2的xrange和python的range功能一样。x为负数时,可以理解为从100减少到1(100,1,-1)
-
数字和字符串的作业反馈
- int()转换为数字,str()转换为字符串,bool()转换为布尔值
- 只要能被for循环获取,就是可迭代对象
- str,int是类型,s1= "alex",即根据str类型创建一个对象s1。
- pycharm中code-reformat code即可帮助更正代码的正确格式
-
列表
- 深灰魔法
- list是类,特征是[],逗号分隔每个元素,列表可以嵌套任何数据类型。
- 索引取值,0开始。索引修改。索引删除,del_li[ x],即删除li的下标为x的元素
- 切片取值,[],左闭右开。切片修改,切片删除
- 列表的内容可以修改,因为列表是链表,只需要改变存储的下一个地址即可
- in操作,not in操作
- li[4][1][0],找到列表第4个元素的第1个元素的第0个元素
- 字符串可以转换为列表,数字不能转换为列表
- 列表转换成字符串:
- 既有数字又有字符串的情况,需要自己写for循环一个一个处理
- 如果只有字符串的情况,v = "".join(li),既可以将列表转换为字符串
- 灰魔法,list中提供的方法
- li对象调用append方法。li.append(5),在列表li后追加一个元素5
- li.extend([9898,"不得了"]),将可迭代对象的每一个元素加至列表li中。iterable是可迭代对象,列表和字符串是可迭代对象
- li.clear(),清空列表
- v = li.copy(),将列表li浅拷贝至列表v中
- li.coount(22),计算元素在列表li中出现的次数
- li.index(22),在列表li中检索22所在位置,左边优先,可以限定起始位置和终止位置
- li.insert(0,99),在列表li的索引位置0加入元素99
- v=li.pop(),获取那个列表li中被删除的值,按照索引删除,可以指定删除的索引,默认删除最后一个
- li.remove(22),删除列表中的指定值,左边优先
- 删除列表的方式,pop方式,remove方式,del li[0]方式,del li[7:9]方式,clear方式
- li.reverse(),列表反转
- li. sort(reverse = true),将列表元素从大到小进行排序
-
元组
- 深灰魔法
- tuple,类,(11,22,33,44)。对列表的二次加工。元组的一级元素不可修改,不能增加或删除,但是如果一级元素的列表,则列表的元素可以修改
- 创建元组的时候,在右括号前加逗号,不报错,一眼认出是元组,(11,22,"alex",)
- 索引取值
- 切片取值
- 支持for循环,也是可迭代对象
- 元组,列表,字符串可以互相转换,元组只有字符串时,可以用join。如果有数字和字符串,要自己写for循环
- 元组也是有序的
- v = tu[3][0],获取元组中索引为3的元素中索引为0的元素
- 灰魔法
- tu.count(22),获取指定元素在元组中出现的次数
- tu.index(22),获取指定元素在元组的索引位置
-
字典
- info = { "k1": "v1","k2":"v2"} ,每个元素称为键值对
- 类,dict,{}。key不能取列表/字典,其余均可以。value可以是任意数据类型,可以嵌套多层。但是key不能重复,如果重复,则后边的失效
- 字典是按照哈希值保存的,key不论是什么类型,都会被转换为哈希值。列表等不能被转换为哈希值,所以不能作文key
- 字典是无序的,一个字典每次打印时,显示顺序不一定
- v = info[key],key做为索引来取值
- v = info['k3'][5]['kk3'][0],找到多层嵌套的元组第一个元素
- 因为无序的,所以不能用切片
- del info['k1'],删除字典info中key为'k1'的键值对
- for k,v in info.items() info.keys info.values,依次是获取字典info的键值对(key给k,对应的value给v),key,value
- not in和in也能用,v = "k1" in dic,默认是循环key
- 灰魔法
- info.clear(),清空字典
- info.copy(),浅拷贝
- 静态方法,dict.fromkeys(["k1",123,"999"],123)建立一个以k1,123,999为key的字典,指定统一的value值123
- v = dic.get('k11111',111),从字典中获取key为k1111的value,如果不存在,则返回111。
- keys,items,values,以集合的形式返回字典的相应参数
- v = dic.pop('k1',999),在dic中删除key为'k1'的键值对,同时v获取到对应的value。如果删除失败,则返回999
- v = dic.popitem(),随机删除字典键值对,但是v拿到的是键值对
- v = dic.setdefault('k1111','123'),在字典dic中设置键值对,如key已经存在,则不设置,v拿到已有值。如果key不存在,则设置,v拿到新值
- dic.update({'k1':'1111','k3':123}),将已有key的value更新,将未有key的键值对新增。
- dic.update(k1= 123, k3= 345,k5 = "asdf"),功能与上边相同
-
布尔值
-
空字典'None',空列表[],空元组(),空字典{} , 数字0 都是false
-
列表list/元组tuple/字典dict-作业
- print("aaa", sep = "搞", end=""),print("bbb"),输出的aaa和bbb处于同一行,且用搞分割
- li = ['alex', 'eric',123],li[2]= str(li[2]),v = "_".join(li),先将123转换为字符串,然后再拼接起来。或者写个for循环,将每个元素转换为字符串再拼接
-
enumerate(sequence, [start=0]),seq是任意可迭代对象,如列表,元组,字符串,start是下标开始标号,默认为0
-
定义变量的根本目的是记录状态的变化
- name = 'sb',name = ‘alex’,id(name)可以取得name所存数据在内存中的地址编号
- 不可变类型,重新给变量赋值,必须新开辟地址:字符串,数字,元组
- 可变类型,重新给变量赋值,id地址不变:列表,字典
- 顺序访问:字符串,列表,元组
- 映射访问:字典无序
- 直接访问:数字
- 容器类型:列表,元组,字典
- 原子类型:字符串,数字
-
集合
- 不同元素组成,集合无序,元素必须是不可变类型,s = {1,2,3,4,5}
- 另一种定义方式s= set('hello'),s中包括('o','h','l','s')
- s.clear(),清空集合
- s1 = s.copy()
- s.pop(),随机删除集合中元素
- s.remove(''sb),删除集合中的元素'sb',不存在会报错
- s.discard('sbbb'),删除不存在的元素时不报错
- 交集,p_s.intersection(l_s),等价于p_s&l_s,求ps和ls的交集
- 并集,p_s.union(l_s),等价于p_s|l_s,求ps和ls的并集
- 差集,p_s.difference(l_s),等价于p_s -l_s,求得ps中有,ls没有的元素
- 交叉补集,并集-交集。p_s.symmetric_difference(l_s),等价于p_s^l_s
- p_s.difference_update(l_s),等价于p_s = p_s-l_s,即将差集更新为ps
- s1.isdisjoint.(s2),如果集合s1和s2无交集,则返回true
- s1.issubset(s2),如果s1是s2的子集,则返回true
- s1.update(s2),将s1更新为s2一致,update()的括号中必须是可迭代对象,如元组,列表,字典,字符串。用于一次新增多个元素
- 集合是可变的。但是frozenset()是定义不可变集合。
- 简单去重方法,列表转换为集合,s = set(names),然后集合转换为列表names = list(s),得到无序的去重列表
-
字符串格式化
- msg='i am %s my hobby is %s' %('1hf','alex'),将后两个数据传给前两个%s
- %s,后边可以接数字,可以接列表。
-
s = 'percent %.4s'%'abscdsf'#取前4位
- 在该写%d的位置,写%s,会导致程序可读性变差
- 打印浮点数,percent %.2f %%。打印至小数点后两位,并显示一个%
- 在python中,单引号和双引号没有区别
- tp1 = i am %(name)s age %(age)d %{'name':'alex', ' age':'18'},将字典的value赋值给对应的key
-
12
msg
=
'i am %(name) -60s my hobby is alex'
%
{
'name'
:
'1hf'
}
print
(msg)<br>
#flags,取60位并左对齐。+是右对齐
-
12
msg
=
'i am \033[45;1m%(name)+60s\033[0m my hobby is alex'
%
{
'name'
:
'1hf'
}
print
(msg)<br>
#打印时有颜色
- print('root','x','0','0',sep=':'),用:将几部分连接起来
-
format字符串格式化
- tp1 = "i am {2}, age {1}, {0}".format("seven",18,'alex'),将format后的值按照012的顺序分别传给前边
-
tpl = "i am {name}, age {age}, really {name}".format(name="seven", age=18)
tpl = "i am {name}, age {age}, really {name}".format(**{"name": "seven", "age": 18})
**代表取字典的内容 -
tpl = "i am {:s}, age {:d}, money {:f}".format("seven", 18, 88888.1) tpl = "i am {:s}, age {:d}".format(*["seven", 18])
*相当于取列表的内容 -
tp1 = "numbers: {:b},{:o},{:d},{:x},{:X}, {:%},{}".format(15, 15, 15, 15, 15, 15.87623, 2)
b是二进制,o是八进制,d是整数,xX是小写大写十六进制,%是显示成百分比 -
函数
- 函数定义时不需要形参,在调用时就不要给实参,否则报错
- print(test),打印出函数test的地址
- 如果定义了两个重名的函数,则后一个自动覆盖前一个,即调用后一个函数
- 过程就是没有返回值的函数。python中没有过程,即不定义返回值的情况下,python会返回None
- 函数返回值如果>1,返回tuple
- 位置参数,即实参与形参一一对应,缺一或多一不行
- 关键字参数,位置无顺序,找对应的关键字形参赋值,缺一或多一不行
- 位置参数一定要在关键字参数的左边,以下OK
def test(x,y,z): print(x) print(y) print(z) test(1,z=2,y=4)
- 默认参数,如果实参不传值,则默认
- **跟字典有关系,*跟列表/元组有关系。没有加*,代表将一个整体传给函数的*args。如果加*,则遍历列表,将其挨个传入函数
- *args传位置参数,返回一个元组。**kwargs传关键字参数,返回字典
- def test(x,*args,**kwargs):符合大原则,位置参数必须在关键字参数的左边。能接受任意形式的参数
-
全局变量与局部变量
- 顶头定义,或者叫没有缩进的定义变量,就叫全局变量
- 在子程序中要对全局变量做操作,则申明global 变量名
- 如果函数的内容无global关键字,优先读取局部变量,没有的话再读取全局变量,无法对全局变量重新赋值,但是对于可变类型,可以对内部元素进行操作,例如append,clear
- 全局变量全部大写,局部变量全部小写,代码规范
- 函数可以嵌套函数,在嵌套函数中,用nonlocal name可以申明位name为外层函数的同名变量,而不是全部变量
-
前向应用
- 风湿理论,函数即变量,在函数语句运行之前,函数已经被申明即可执行。这点跟变量一样
- 函数定义时不会执行,在内存中开辟地址,变量名为门牌号,函数内容被当作字符串存入门牌中
-
递归
- 编程方法论:函数式编程中没有循环,都是递归
- 递归特性:必须有一个明确的结束条件,否则是死循环,直接报错。在函数内满足条件时,执行return语句
- 列表可以用person_list.pop(0),弹出列表的第一个元素
- 加上断点分析,debug
-
作用域
- 函数名相当于函数的内存地址,加上()才是函数
- return 函数名,即返回一个函数地址,函数地址()即可执行函数。return 可以接任意参数
- 想拿到函数执行的地址,就让a= f()
- 函数的作用域在申明时已经决定,跟调用位置无关
-
匿名函数
- 用lambda x:x+1,第一个x是形参,x+1是函数的返回值
- func = lambda x:x+1,然后func(9)即可执行
- lambda应该跟其他函数一块用,而不是func = lambda的形式,如map函数
- 其中x+1的位置,一般是简单逻辑,而不是for循环,if判断等
- 返回多个值时,需要自己加括号,lambda x,y,z: (x+1,y+1,z+1)
-
函数式编程
- 语言是用来跟计算机沟通的
- 编程方法论,面对过程,函数式编程,面向对象
- 面向过程,把大问题分成小问题,找到解决问题的入口,按照一个固定的流程去模拟解决问题的流程
- 函数式编程= 编程语句定义的函数+数学意义的函数。定义好数学模型y=2*x+1,用python函数实现上述数学模型。hasell clean erlang是严格意义上的函数式编程语言
- 面向过程,def cal(x):res = 2*x res+=1 return res
- 函数式编程的特性:不定义变量,不修改变量,直接往下操作。
- 函数即变量,函数名可作为参数传递,返回值可以是函数名,叫高阶函数,装饰器会用到
- return test1(),即先执行tets1(),返回函数test1的执行结果
- 调用栈,多层嵌套函数的情况下,上层没执行完就要进入下一层时,要保留上一层运行情况,等下一层执行完,再回来执行上一层,称为调用栈
- 尾调用优化:在函数的最后1步调用另外一个函数,这样上一层函数就不用保留了,相当于直接转入下一个函数。最后1行不一定是最后1步,例如if else判断
- map函数。map(lambda x:x+1,num_1),第一个位置传函数名,不带()。第二个位置传实参。map执行逻辑,遍历第二个实参,每个都交给第一个位置函数处理一遍。map返回结果是个迭代器,需要外套list可转换为列表
- filter函数。filter(lambda n:not n.endwith('sb'),movie_people),第一个参数是函数,第二个参数是可迭代对象,把第二个可迭代对象可循环遍历,拿出每个元素交给前边函数筛选一遍,得到布尔值,为true则保留
- reduce函数。from funtools import reduce,先导入。把一个序列进行合并操作。reduce(lambda x,y:x+y, num_1 ,1),1为初始值,把列表num_1中的值遍历,交给lambda函数累加一遍
-
python内置函数
- all([1,2,'1',''])判断每个元素的布尔值,全部为真,则all返回true
- any跟all相反,只要有一个为真,就为真
- bin()转换为十进制二进制,hex是十进制转16进制,oct是转换8进制
- 编程时就是写了字符串,要想保存到硬盘上就要编码。存到内存中用unicode,硬盘上自己选择编码。
- name = ‘你好’,bytes(name,encoding='utf-8').decode(utf-8),将字符串按照utf-8编码,1个汉字占用3个字节,然后解码。ASCII码不能编中文
- divmod(10,3)结果(3,1),分页功能。用在搜索结果分页上,10个搜索结果,每页3条,则需要3+1页面显示结果
- eval()功能,1,把字符串的数据结构提取出来,例如从字符串包含字典中,提取出字典来。2,可以把字符串中的数学运算做一遍
- hash运算,1不论输入多长,输出定长,2不能根据输出反推出输入。同样输入,输出一一定,可校验软件安装包是否安全
- help(dir)获取函数的帮助文档。dir获取函数有哪些模块。
- isinstance(1,int),判断1是否为int类型
- print(globals()),打印出全局变量,locals打印出局部变量
- print(_file_)打印出文件路径
- zip(('a','b','c'),(1,2,3)),拉链函数,生成三个元组('a',1),左边一个对应右边一个。zip前后两个参数,只要是序列类型即可,元组,列表,字符串都是序列类型
-
max函数和min是一对。第一步:处理的是可迭代对象,相当于一个for循环取出每个元素进行比较。第二步,元素之间比较大小是从每个元素的第一个位置依次比较
- 比较字典的value值大小时,先用zip函数生成新的元组序列,然后max即可。不同数据类型不能比较。
- max(people, key=lambda dic:dic['age'],将字典people中元素遍历取出,每个调用一遍lambda函数,然后比较大小
- chr(97)和ord('a')是一对
- pow(3,3,2),3的3次方,对2取余数。如果只有两个,叫3的3次方
- round(3.5)。四舍五入
- slice(1,4,2),对字符串切片,左闭右开,步长为2
- type()查看元素的数据类型
- vars(),如果没有实参,则等效于locals,即局部变量。如果有实参int,则将实参int的所有方法存储为字典形式
- import,模块就是py文件。import不能导入字符串,_import_()导入的就是字符串类型文件
-
文件读操作
- windows的文件是以gbk存储的
- f = open('陈粒', ‘r’, encoding='utf-8'),以utf-8编码
- 在pycharm创建文件,在右下角选择编码方式,gbk
- 就是应用程序➡️通过操作系统➡️获得管理硬盘数据的渔网,就叫文件句柄
- 记得关闭文件
- f.read()读出来是str文件
- f.readable(),判断文件是否可读
- f.readline(),一次读一行
- f.readlines(),把文件读取成列表格式
-
文件写操作
- f.writelines(['555\n','666\n']),将列表按照一行一行写入文件f指的文件
- 写的东西都是字符串,不能是数字
- 用w和w+都是新建立一个文件,如已经有,则清空
-
文件追加操作append
- 文件没有修改一说,全都是覆盖,因为在硬盘上都是二进制存储。
- 从硬盘中读取文件,在内存中修改,然后覆盖到硬盘上
- with open('a.txt', 'w') as f: f.write('11111\n'),打开文件自动关闭
-
文件处理b模式
- rb的方式对linux没有任何用处,一切皆文件,linux全都是用二进制处理。但是windows不都是二进制,rb的b是bytes,字节方式
- 8个二进制bit是1个字节bytes。字节本质上就是二进制
- b的方式不能指定编码模式,f=open('test11.py', 'rb'),以二进制形式打开py文件,要想显示字符,还需解码,data.decode('utf-8')
- f=open('test22.py','wb'),f.write(bytes('111\n', encoding='utf-8')),以wb打开文件,写入时要将字符串通过bytes转换为二进制编码写入。或者直接x.encode('utf-8')
- b的方式,用来处理图片视频。二进制方法可以跨平台,跨系统
-
文件操作的其他方法
- f.closed,检测文件是否关闭
- f.encoding,获取文件打开的编码,不同平台对utf-8代号不同
- 文件1以utf-8编码,不能用其他编码读取,但可以用gbk等其他编码写入新东西
- 不清楚文件编码时,可以使用f=open('b.txt' , 'r' ,encoding='latin-1')万能编码。忽略
- f.flush(),把数据从内存刷新到硬盘上
- f.tell()读取文件中的光标位置
- f=open('b.txt','r',encoding='utf-8', newline=''),使用newline读取文件中真正的换行符号
- 只有f.read(3)是3个字符,即'a','b'为单位,其余的文件内光标移动都是以字节为单位,如f.seek,f.tell,f.truncate,f.readline
- ASCII码中,一个英文字母(不分大小写)为一个字节,一个中文汉字为两个字节
- UTF-8编码中,一个英文字母为一个字节,一个中文为三个字节。
- f.truncate(10)是一种文件的写操作,截取操作,将文件只保留从0-10个字节的内容
- f.seek()用来移动光标
-
文件seek方法补充
- seek(10,1),相对位置seek,前提是用rb打开文件,二进制执行
- seek(-10,2),从文件末尾开始seek,是倒叙
- 故障检查要查看最新的日志。
- 不用seek方法,data= f.readlines(),print(data[-1], decode('utf-8')),readlines读取文件每一行,然后打印最后一行
- 循环文件的推荐方式:for i in f: 一次一行,要一行给一行,而不是存储一个大列表。设置一个偏移量offs,seek(offs, 2),从末尾开始,每次一个offs
for i in f: offs = -10 while True: f.seek(offs,2) data = f.readlines() if len(data) >1: print('文件的最后一行是%s' %(data[-1].decode('utf-8'))) break offs*=2#如果不执行if,则加大偏移量,用于下次循环
- data是个列表,len(data)是指列表的行数
-
迭代器
- 迭代和递归的区别
- 递归,问路,是甲问乙,乙问丙,丙问丁,丁告诉丙,丙告诉乙,乙告诉甲
- 迭代,是甲问乙,乙让甲问丙,甲问丙,丙让甲问丁,甲问丁,得到结果
- 迭代器协议,对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stoplteration异常,以终止迭代,即只能往下一步走,不能往后退。
- 可迭代对象,实现了迭代器协议的对象
- 字符串,列表,字典等for循环对象本身都不是可迭代对象,for循环先调用字符串/列表的_iter_方法,将其转换为可迭代对象,然后就有了next方法,最后捕捉异常
-
l = [1, 2, 3] iter_l=l.__iter__()#遵循迭代器协议,将列表生成可迭代对象 print(iter_l.__next__())
#for循环的机制跟索引没有一点关系,for循环就是基于迭代器协议工作的 - 基于索引和while只能遍历字符串/列表/元组等有下标的对象,for循环才能遍历字典/集合/文件对象。for循环处理字典时,迭代的是字典的key值,这是因为__iter__._next_就是key值
- print(next(iter_1)),next()是python内置函数,_iter_next_是数据对象内置的,next()本质就是在调用iter_l.next()方法
- 迭代器就是可迭代对象
-
生成器
-
可以理解为一种数据类型,自动实现了迭代器协议,不用iter方法,直接调用.__next__方法
- 函数中只要有yield,得到的就是生成器。yield相当于return,但是可以yield多次,可以保留函数运行状态,用next继续执行
- 三元表达式,列表解析,字典生成
- 三元表达式,'SB' if name == 'alex' else '帅哥',如果if成立,返回SB,否则返回“帅哥”
- 列表解析,l=['鸡蛋%s' %i for i in range(10) ],生成['鸡蛋0','鸡蛋1'.....]的列表,中括号是列表解析式,小括号就是生成器表达式
- l1 = ['鸡蛋%s' %i for i in range () if i>5],这也是三元表达式,没有4元表达式
-
{i:self.__getattribute__(i) for i in dir(self) if not i.startswith('__')}
- 生成器表达式:laomuji = ('鸡蛋%s' %i for i in range(10)),laomuji也是生气器对象。
- sum,map,filter都是迭代器协议
- 生成器函数的好处,1不占空间,2效率高
- 生成器特性总结:
- 语法上和函数类似,都是使用def定义,生成器使用yield语句返回值
- 生成器可以被for循环
- 状态挂起,可以保存状态,延续上次操作
-
def get_population(): with open('人口普查', 'r', encoding='utf-8') as f: for i in f: yield i g = get_population() m = get_population()#用这种方式存储两个迭代器 all_pop = sum(eval(i)['population'] for i in g) print(all_pop) for k in m: print('%s %%' % ((eval(k)['population'] / all_pop) * 100))
-
生成者消费者模型
- 运行生成器的方式
- t.__next__()
- next(t)
- c1.send(‘abc’)从yield开始,把‘abc’赋值给yield,然后像__next__一样开始推动程序执行。
- yield 3相当于return,控制的是函数的返回值
- x= yield的另外一个特性,接受send传过来的值,赋值给x
- 使用send,在一个程序中实现并发操作
-
import time def consumer(name): print('我是[%s],我准备开始吃包子了' %name) while True: baozi=yield time.sleep(1) print('%s 很开心的把[%s]吃掉了'%(name, baozi)) def producer(): c1=consumer('wupeiqi') c2=consumer('yuanhao_sb') c1.__next__() c2.__next__() for i in range(10): time.sleep(1) c1.send('包子 %s' %i)#通过send调用consumer函数 c2.send('包子 %s' %i) producer()
- 异步携程
- pythonic,python风格的代码,简洁优美
- 生成器产生时不会生成值,只有next时才会产生值,且只能遍历一次
-
def test(): for i in range(4): yield i t = test() t1=(i for i in t) t2=(i for i in t1)#生成器在产生时不做任何操作,只有执行时才会遍历,所以t2此刻没有值 print(list(t1)) print(list(t2))
- 把一个对象变成迭代器,l = [1,3],l.__iter__()或者直接itel(l)也行
-
装饰器
- 本质就是函数,功能是为其他函数添加附加功能
- 原则
- 不修改被修饰函数的源代码,一点也不能动
- 不修改被修饰函数的调用方式
- 程序上线后,不能修改函数源代码
- 装饰器 = 高阶函数+函数嵌套+闭包
- 接受的参数是函数名,返回值也是函数名,差一步实现装饰器,只有高阶函数不能实现装饰器
- 函数嵌套,在函数中定义另外一个函数
- 闭包就是作用域的概念
-
装饰器1.0
- 语法糖,@timmer 就相当于 test=timmer(test)
-
@timmer装饰器加上返回值
- 运行加上装饰器的test,实际是执行装饰器,test如果有返回值/参数,都应该加到装饰器对应位置,包括形参和实参
-
import time# 装饰器的架子 def timmer(func): # func=test def wrapper(*args,**kwargs):#*args接受所有参数,**kwargs接受关键字参数,位置参数必须在关键字参数左边 start_time = time.time() res = func(*args,**kwargs) # 就是在运行test() stop_time = time.time() print('运行时间是%s' % (stop_time - start_time)) return res return wrapper
- 解压序列a,b,c=(1,2,3),a获取1,b获取2,c获取3
-
#技巧
>>> m [10, 3, 2, 3, 5, 1, 2] >>> a,*_,c=m #_没有意义,写*d也行>>> a 10 >>> c 2 - python中变量互相换值,a=1,b=2,然后a,b = b,a即可
- for else语句,表示for非正常结束,则执行else语句
- 如何给装饰器加参数,再外加一层定义,装饰器最外层新增参数,则内层均可以获取。先研究无参的,再研究有参的
- 程序解耦
- tag的用法
-
模块(module)
- 一个.py文件就是一个模块(module)
- 模块包括:python标准库,第三方模块,应用程序自定义模块
- pycharm中,package中必须有__init__.py文件,direcory中没有
- package就是组织.py文件用的,一个包中放同一类型py文件
- 不同路径下可以有重名py文件
- namespace名字空间
- import(cal)做2件事,所以一般不在cal中定义逻辑函数,只定义功能函数
- 是把cal文件执行一遍
- 引用cal的变量名
- import cal, time,引用多个模块
- from cal import add,from cal import *,从cal中引入一个函数add,或者引入全部函数
- import sys,sys.path找到的路径,就是import寻找导入模块时的路径顺序。这是执行文件对应的路径,是解释器唯一能认识的路径,引入文件也是按照执行文件的路径进行查找,所以必须在执行文件中import所有.py。或者将所有引入模块的路径加入sys.path的列表中。
- sys.path.append(),临时修改环境变量,不是永久修改。永久修改得改系统path。把bin.py文件写在一个包中
-
import sys, os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #abspath拿到文件的绝对路径,dirname向外层找文件地址,两个外层就行 print(sys.path.append(BASE_DIR)) print(sys.path)
- from my_module import cal,从同层的my_module文件夹中找到cal模块
- bin.py是起始文件,main.py是逻辑功能主函数,其他功能函数放在别的地方
- from web.web1.web3 import cal,引用三层包的cal模块,用.连接。import后一般接模块,如果接包,则是执行包中的_init_文件
- _name_在执行文件中打印,就是字符串_main_。在被调用文件中执行,拿到的就是被调用文件的路径
- 测试代码时用,if __name__=='_main_'后接测试代码,如果匹配,说明当前处于测试环境,则执行代码。如果不匹配,说明当前是被调用状态,不执行测试代码
- 这句话放在bin.py文件,说明不想让bin成为被调用文件。bin可以提供接口,但不能执行我的主函数
-
time模块
- time.time()是时间戳,单位是秒,从19700101凌晨开始算。做计算用的
- 结构化时间
- time.localtime(),拿到的是当前时间,是字符时间。将时间戳时间即可转换为结构化时间。
- time.gmtime(),显示格林尼治时间,UTC
-
mktime(localtime)将结构化时间转换为时间戳。
- 字符串时间
- time.strftime('%Y-%m-%d %X',time.local.time()),将当前结构化时间转换为字符串时间,年月日小时
- time.strptime('2016:12:24:17:50:36', '%Y:%m:%d:%X),将字符串时间转换为结构化时间
- time.asctime(),将结构化时间转换为固定的字符串时间,使用python内置的字符串时间显示形式
- time.ctime(),将时间戳转换为固定形式的字符串时间
- time.sleep(),线程推迟指定的时间运行,单位为秒
- datetime.datetime.now(),显示另外一种字符串时间
- 给自创py文件起名字时,不要跟python内置py文件重名。time,sys文件特殊,不是py文件,是c 语言实现的,python解释器也是c语言,内置在python解释器中,直接在封装在了python中,即使重名,也不影响内置time运行
-
random模块
- random.random随机生成一个0到1的浮点数
- random.randint(1,3),在[1,3]之间取整数
- random.randrange(1,3),在[1,3)取整数
- random.choice([1,'23',[4,5]])三个内容中随机取一个,处理可迭代对象
- random.sample([11,22,33,44,55], 2)从列表中随机选择出两个值
- random.uniform(1,3)随机生成一个指定范围的浮点数据
- random.shuffle(),将列表打乱,例如洗扑克
-
随机生成验证码
-
OS模块
- dirname,abspath
- os.getward(),获取当前文件目录,即文件路径
- os.chdir()改变当前脚本工作目录,相当于shell下的cd。chdir('..')到上层目录
-
os.makedirs(
'dir1/dir2'
) 可生成多层递归目录
-
os.removedirs(
'dirname1'
) 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
- os.stat('path/filename')获取文件/目录信息,其中st_size是文件占用的字节数
- os.linsep,输出当前平台使用的行终止符,win下为"\r\n",linux为"\n"
- os.system('bash comman'),运行shell命令,直接显示
-
os.path.dirname(),拿py文件的路径,os.path.basename(),拿py文件名
- a="路径1",b="路径2",os.path.join(a,b),将两个路径拼接,形成新的路径
-
sys模块
sys.argv 命令行参数
List
,第一个元素是程序本身路径。可以直接在调用py文件命令后边,加上输入参数,然后在函数内取值
-
sys.exit(n) 退出程序,正常退出时exit(
0
)
-
sys.version 获取Python解释程序的版本信息
-
sys.maxint 最大的
Int
值
-
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
-
sys.platform 返回操作系统平台名称
-
sys.stdin 输入相关
-
sys.stdout.write('#') 向屏幕输出相关内容
-
import time,sys for i in range(100): sys.stdout.write('#')#向终端输出内容 time.sleep(0.1)#与下一步结合,营造出动态显示的效果 sys.stdout.flush()#调用系统接口,从内存中向终端刷新
-
sys.stderror 错误相关
########################################
-
json模块
- 可以进行任何语言的数据交换,是从js语言中提取出来的
- 把字典存入文件,需要转换成字符串,提取出来需要加eval(),但是有局限性
- pickle可以存任何东西,函数变量,类变量
- json.dumps(dic),将任意数据类型封装成json字符串数据,无论dic什么引号,或者list,int,json都将其变为双引号字符串,称为json字符串。
- 除了存到磁盘上要保存成字符串,前端和后端互相传输时也要存成字符串
- json.load(),将json字符串还原成原数据类型
-
f = open('new_hello', 'w') dic_str = json.dumps(dic) f.write(dic_str) # 虽然二者等价,但dump只能用于文件操作,因此推荐用上一种 json.dump(dic.f)
- load与loads相比,少了f.read
- 只要字符串符合json规范,是双引号,就可以loads,非必须得dumps
-
pickle 模块
- 用法与json相同,只是转换成字节byte。文件打开是用wb。序列化和反序列化
- 支持序列的对象更多,除了字典等基本数据类型,还可以序列函数,类
- 序列化,把对象变量从内存中变成可存储或传输的过程
-
xml模块
- java中xml是必须的,也是包装语言,在金融领域以前用的多
- 标签语言,html语言也是标签语言,所有语法都是通过标签实现的。
- 有头有尾的标签叫非闭合标签,有头无尾的叫自闭合标签
- 增删改查
- import xml.etree.ElementTree as ET,引入xml模块
- ET.parse('xml_lesson.xml'),打开xml文件。要有对象概念,实体对象,可以调用方法,可以调用属性。
- 属性是对象下边的属性,是键值对,属性名和属性值。标签是通过属性完成信息传输的,属性用来解释标签名字。非闭合标签如有属性,一定加在首部位置
- root = tree.getroot(),拿到根结点
- root.tag,打印出节点的标签
-
for i in root: #print(i.tag)
for j in i:
print(j.tag)#对根结点进行遍历操作 - i.attrab,打印出对象的属性
- i.text取出标签内容
-
for node in root.iter('year') print(node.tag, node.text)#遍历root结点,只拿标签year的标签名和内容
-
#修改xml中year中的属性
import xml.etree.ElemenTree as ET
tree = ET.parse("xml_lesson")#导入文件 root = tree.getroot() for node in root.iter('year'): new_year = int(node.text) + 1#修改标签的内容 node.text = str(new_year) node.set("updated","yesy")#增加一个属性,包括属性名和属性值 tree.write("ET") -
删除country中rank中大于50的country
for country in root.findall('country'):#找到root中所有的country标签 rank = int(country.find('rank').text)#find找标签 if rank >50: root.remove(country) -
#创建一个xml文件 导入模块 new_xml = ET.Element("namelist")#创建一个namelist的标签 name = ET.SubElement(new_xml, "name",attrib={"enrolled":"yes"}
#在对象new_xml中创建一个标签,name,然后属性为...
!!!最后
et = ET.ElementTree(new_xml)#生成文档树
et.write("test.xml",encoding="utf_8",xml_declaration=True)#将内容写入文档对象中 -
shelve模块
- 与pickle模块类型,支持的处理对象也是有限的
- 将所有内容都处理成字典,按照字典的逻辑为大家存储数据
-
re模块
- 字符串.find是完全匹配,做不到模糊匹配,正则可以做到模糊匹配(管理身份证信息)
- RE是小型的,高度专业化的编程语言,就是处理字符串,在其他语言中也可以引用正则表达式,引擎是c编写的,因此效率非常高
- re包括元字符,和6个方法
- 元字符:. ^ $ * + ? {} [] | () \
- . 是通配符,可以代替所有的字符,除了\n不行。re.findall("a..x", "asdfkljlkxdf"),一个 . 代表一个字符
- 尖角号^ 是位于开头,re.findll("^a..x", "sadfasdf"),检测后边数据中是否存在a开头的对应字符串
- re.findall("a..x$", "sadfasldjf"),检测是否存在以ax结尾的字符串,把匹配结果放到列表中。
- 四个重复符,都是贪婪匹配,都是尽可能匹配。加?变为惰性匹配
-
re.findall("alex*", "asdfale"),匹配成功,因为*是0到正无穷个
-
re.findall("alex+","sdafale"),匹配失败,因为+是1到正无穷
-
re.findall("alex?","sadfalexxxx"),结果为alex,即匹配0个或1个x,如果后边只是ale,即返回ale
- {}能表示前边所有的情况,{0, }等价于*, {1, }等价于+, {0,1}等价于?
- 字符集[]
- re.findall("x[y,z]p", "xypuuuxzpux,pu"),[]中取一个,起‘或’的作用
- 在字符集[]中没有特殊符号,*就是*
- [a-z],是取a到z中任意一个,横杠在字符集中‘范围’的意思
- q.[^a-z],^在字符集中是‘非’的意思,只要不是就能匹配。提取最底层的括号: re.findall("\([^()]*\)","12+(34*6+2-5*(2-1))"
- 反斜杠\在字符集中用于,恢复(等符号在字符集中括号的作用。
- 反斜杠\,叫转义符
- 反斜杠后边跟元字符去除特殊功能,比如\.
- 反斜杠后边跟普通字符实现特殊功能,比如\d
\w 匹配字母或数字或下划线或汉字 \s 匹配任意的空白符。\S非空白 \d 匹配任何十进制数字,相当于类[0-9]。\D是相反作用,匹配所有非数字字符。 \b 匹配单词的开始或结束,特殊字符边界,如空格,&,#等,即\b等于这些特殊字符
- \b的说明。
- re.findall(r"I\b","I am xxx"),如果不加r,python解释器会将处理\b的结果传给re模块,加上r则表示该字符串不需要处理,直接传给re
- 或者"I\\b",因为\在python中有特殊含义,\\表示去除其特殊含义
- re.findall("c\\\\|"),想匹配c\|。因为\在re模块有特殊意义,所以向\\去除特殊意义,向re传c\\|。为实现目标,在python中需要\\\\才能向re模块传入2个\\
ret
=
re.sub(r
'(.)\1+'
, r
'\1'
, res)中的,
\1表示重复正则第一个圆括号内匹配到的内容,如:([a-zA-Z])\1+,匹配多次符合([a-zA-Z])中的内容
- 管道符|
- r"ka|b",是ka或b的意思
- 分组()
- r."(abc)+",将abc作为整组进行匹配
- re.search("\d+", "asdjkhf34kjs76").group(),只返回34,即search只返回一个结果即可,返回的是一个对象
- re.search("(?P<name>[a-z]+)\d+","alex36wusir34"),将alex取出,并命名为name的对象,格式就是?P<>,分组用的。用在url路径匹配时
- re.match("\d+", "56alex36wushis34")返回56,功能与search相同,但仅在字符串开始处匹配,.group()取出对象
- re.split("[ab]","abc"),先按a分为 空 和 bc,空不能按b分,bc再按b分为 空 和 c,所以结果是 空 空 c
- re.sub("\d","A","jask2353sdhfjk233",4),将前4个数字替换成A。不加参数4的情况下,全部替换为A
- re.subn,将元素匹配成元组
-
com = re.comile("\d+")#将待匹配方法写到此处 com.findall("fijksad23sdf234sdkj4")#原字符串 #功能与findall相同,但可以用多次
-
ret = re.finditer("\d","sdfjh8977ksjdfkj98989")#将匹配内容作为迭代器 next(ret).group#取出迭代对象
- re.findall("www\.(?:baidu|163)\.com", "sdafhjwww.baidu.comsdkjfl"),findall优先将()中的内容拿出来。?:去优先级,返回网站全部
- 同上,re.findall("(abc)+", "abcabcabc"),只输出一个abc,即优先把组里的内容打印出来。re.findall("(?:abc)+","abcabcabc"),去掉优先级,打印出abcabcabc
-
re.I 使匹配对大小写不敏感 re.L 做本地化识别(locale-aware)匹配 re.M 多行匹配,影响 ^ 和 $ re.S 使 . 匹配包括换行在内的所有字符 re.U 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 -
logging模块
- 自己写日志,可以通过文件操作来完成
- 日志级别。debug, Info, warning, error, critical,级别逐渐升高
- logging.info或者.debug等方式调用日志信息
-
import logging logging.basicConfig( level = logging.DEBUG#将日志级别调整为debug filename = 'logger.log'#将日志写入此名字的文件,默认采用追加模式。 filemode = 'w'#
format="%(asctime)s %(filename)s",#将日志按照format的格式输出 ) logging.debug('hello')#输出日志内容,在调试阶段,要看到所有信息 logging.info() logging.warning()#在上线阶段,只看warning级别之上,提高效率 logging.error() logging.critical() - stream:流的方式即写到终端上,加了firename后写到文件中。屏幕和文件只能选择一个显示
- format参数中可能会用到的格式化串
- logger对象
- 跟吸心大法特别像
-
def logger():
import logginglogger=logging.getLogger()#创建一个吸心大法的低手。
#()中无内容,则是root根用户,如有内容,则是root下的子用户。
logger.setLevel("DEBUG")#设置logger的级别,debug就全部显示 fh=logging.FileHandler(test_log)#创建一个向文件test_log发送内容的高手 ch=logging.StreamHandler()#创建一个向屏幕发送内容的高手。如果注释掉,则不在屏幕显示,程序正常运行
fm=logging.Formatter("%(asctime)s %(message)s")#定义输出格式
fh.setFormatter(fm)#fh应用这种输出格式
ch.setFormatter(fm)#如果注释掉,则按照默认格式显示
logger.addHandler(fh)#学第一个技能 logger.addHandler(ch)#学第二个技能
return logger
#然后用学会了吸心大法的人打印信息,向屏幕打印,同时向文件输出
logger=logger()#调用logger函数
logger.debug("debug")
logger.info("info")
logger.warning("warning")
logger.error("error")
logger.critical("critical") -
如果在root下创建两个"my logger"的子用户,其实是一个,后边覆盖前边
- 一个日志py文件中,有父子两个logger,则打印子logger时,有几个上层在工作或者在输出,就重复打印几次,此举例中打印2次
-
configparser模块,配置解析模块
- 为配置文件开发的,对配置文件进行操作
-
#配置文件基本格式,包括三部分,三个大key和value,大key的每个value又包括key和value [DEFAULT]#默认信息,在遍历其他信息时,都会输出这部分。default默认在其他key中也存在 ServeAliveInterval=45#键值对 Compression=yes CompressionLevel=9 ForwardX11=yes [bitbucker.org]#另外一个键 User=hg
ServeAliveInterval=45#default这部分默认存在 Compression=yes CompressionLevel=9 ForwardX11=yes
[topsecret.server.com] #另外一个键
Port=5022 ForwardX11=no -
#通过类字典操作来生成配置文件 import configparser config=configparser.ConfigParser() #调用configparser类实例化出来一个对象。就相当于有个空字典config
with open('example.ini','w') as f:#将文件命名为f
config.write(f)#调用config的write方法,将配置写入文件中,与传统的f.write不同 -
config.get('bitbucket.org','compression'),是连续在两个key中取值,而字典的get第一个值是key,第二个值是没有key时的返回值
-
最后写完配置文件,config.write(open('i.cfg','w')),将配置信息写入新文件i.cfg中。这种文件打开方式,不是fopen,不用关闭
-
hashlib模块,哈希模块
- 摘要算法
- 把一个不定长的内容,转化成定长的内容
import hashlib obj=hashlib.md5("asdfhas".encode('utf-8'))#调用md5算法,给md5加盐,自己定义的 obj.update("hello".encode("utf8"))#将字符串写成字节 print(obj.hexdigest())#打印加密
obj.update("root".encode("utf8"))#root是在hello基础上加密的 -
一段字符串转成一个密文,且不能反解。
- 只能全部变成密文后比较
- 一般有sh256就可以了,要不断更新,没有一劳永逸的解
-
模块补充
- 多个py文件嵌套,路径搜索永远是执行文件的路径。
- print(lhg.cal.add(1,2)),当前文件bin.py调用lhf.py, lhf.py调用cal.py中的add函数
- 面向过程,函数式编程,面向对象,门派没有高低之分,只有练武之人有高低之分
- 函数式编程:函数式就是用编程语言去实现数学函数。这种函数内对象是永恒不变的,要么参数是函数,要么返回值是函数,没有for和while循环,所有的循环都由递归去实现,无变量的赋值,即不用变量去保存状态,无赋值即不改变。
-
面向对象
- 在函数内容嵌套函数,函数有形参
- 类,把一类事物的相同的特征和动作,也叫数据属性和函数属性整合到一起就是类,类是抽象概念。
- 对象,就是一个基于类创建的具体事物,也是特征和动作整合到一起,人是类,姚明是对象
- d1 = dog('袁浩','母','藏獒'),调用类dog,传入特征,生成一条叫d1的狗,面向对象设计
-
#面向对象设计
def school(name,addr,type): def init(name,addr,type): sch = { 'name':name, 'addr':addr, "type":type, 'kao_shi':kao_shi,#考试的函数名 'zhao_sheng':zhao_sheng,#招生的函数名,不能执行 } return sch def kao_shi(school): print('%s 学校正在考试' %school['name']) def zhao_sheng(school): print('%s %s正在招生' %(school['type'],school['name'])) return init(name,addr,type)#在执行之前,函数已经定义即可 s1=school('oldboy','沙河','私立学校') print(s1) print(s1['name']) s1['zhao_sheng'](s1) - 把上述的school用类来声明,如class school,就是面向对象编程。class定义的没有return,跟函数的差别
- 面向对象编程:使用class独特的语法,来实现面向对象设计。用def也可以,但没有class方便
- 用面向对象语言写程序,和一个程序的设计是面向对象的,两者是八杆子打不着的两码事。c语言写的linux kernel是面向对象的程序设计,虽然c不是面向对象语言
- python就是面向对象语言,提供了大量的class的方法应用。
-
#面向对象编程
class Dog: def __init__(self,name,gender,type):#_init_是系统内置的意思,默认有return操作 self.name=name self.gender=gender self.type=type def bark(self): print('一条名字为[%s]的[%s],狂吠不止'%(self.name,self.type)) def yao_ren(self): print('[%s]正在咬人'%(self.name)) def chi_shi(self): print('[%s]正在吃屎' %(self.type)) dog1=Dog('alex','female','京巴')#实例化的过程 print(dog1.__dict__) - 由类生成对象的过程叫实例化,从抽象概念生成出来的具体对象
- 实例化到底干了什么?就是调用了类中的_init_函数
-
#经典类,python3中这也是新式类 class Chinese: '这是一个中国人的类' pass #新式类 class Chinese(object): pass
#通过.调用数据属性和函数属性,本质上就是在查_dict_属性字典
chinese.sui_di_tu_tan()等价于这种方法,chinese._dict_[sui_di_tu_tan]()
print(dir(Chinese))#查看类的属性
print(chinese._dict_)#查看属性字典
p1=Chinese('yuanhao',18,'female')#此语句实际上就是执行下边语句
p1=chinese._init_(p1,name,age,gender)#默认把p1放到第一个参数
#实例只有数据属性,没有函数属性。
#p1只有数据属性 -
chinese._name_#类名 chinese._doc_#类的描述 chinese._base_#python所有类都有共同的祖先,叫object chinese._module_#显示类所在的模块,即py文件
- 类跟函数是一样的,也有作用域的概念。实例先访问自己的属性,再访问类的属性
- 类名()自动调用_init_函数
- 先生成实例,再新增类的属性,然后调用实例的该属性也可以调用,说明调用实例属性的过程是引用过程,类的实时变化也可以反应到实例中
-
#给类新增函数属性 def eat_food(self,food) print('%s 正在吃%s' %(self.name,food) Chinese.eat = ear.food#把函数的地址给到类的函数属性名
-
#实例属性的增删改查 #如果给实例增加函数属性,但是不要这样干,违背了class的简洁本义 def test(self): print('我是来自实例的函数属性' p1.test = test#给实例加个函数属性 print(p1._dict_) p1.test(p1)#给实例加了函数属性,调用实例自己的函数属性,括号内要传自己,class不再给语句自动传值
-
不要修改底层的属性字典
-
#继续实例属性的增删改查
#修改 p1.age=19 print(p1._dict_) print(p1.age) #删除 del p1.age print(p1._dict_) -
就不信玩不蒙你
-
class Chinese: country = 'China' def __init__(self,name): self.name = name def play_ball(self, ball): print('%s 正在打 %s' % (self.name, ball)) p1 = Chinese('alex')# 实例化 print(p1.country)# 实际调用类的country p1.country = '日本'# 在实例中新增数据属性,没有影响_init_,以后新建的实例中没有该数据属性 print('类的-->',Chinese.country) print('实例的', p1.country) p2 = Chinese('yuanhao') print(p2.__dict__) print(p2.country)
-
class Chinese: def __init__(self): print('-------->?') name=input('请输入用户名字>>:') #不要这么写,init是用来生成实例的属性字典的,不应该使用输入语句 self.name=name def play_ball(self,ball): print('%s 正在打%s' % (self.name,ball)) p1=Chinese() print(p1.name)
#应该改为
class Chinese:
def __init__(self,name):
self.name=name
def play_ball(self,ball):
print('%s 正在打%s' % (self.name,ball))
name=input('请输入用户名字>>:')
p1=Chinese(name)
print(p1.name) -
country='中国' class Chinese: def __init__(self,name): self.name=name print('-->',country)#只有p.这种调用方式才适用风湿理论,此处的country只是变量 def play_ball(self,ball): print('%s 正在打%s' % (self.name,ball)) p1=Chinese('alex')
-
country='中国-------' class Chinese: country='中国' def __init__(self,name): self.name=name print('-->',country) #只有p.这种调用方式才适用风湿理论,才从实例和类中找数据属性,此处的country只是变量,不从类中找 def play_ball(self,ball): print('%s 正在打%s' % (self.name,ball)) p1=Chinese('alex')
-
#换个姿势搞你
-
country='中国-------' class Chinese: country='中国' l=['q','d'] def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打%s' % (self.name,ball)) p1=Chinese('alex') print(p1.l) p1.l=[1,2,3]#这种是给p1赋新值,p1有了新的数据属性 p1.l.append('c')#这种是给p1指向的l追加新值,p1本身没有l的数据属性
- 定义类的方法
- 找出一类事物共有的数据属性和函数属性
- 把数据属性封装到init方法中
- 面向对象的类中,函数属性跟实际生活的行为还是有区别的,可以将学校的基本信息定义为函数
-
静态属性,实际就是数据属性
-
class Room: def _init_(self,name,owner,width,length) self.name= name self.name=owner self. @property#将函数属性封装成静态属性,不用()即可运行 def cal_area(self): return(self.width * self.length) print(r1.cal_area) print(r2.cal_area)
-
类方法
- 不跟实例捆绑,直接调用自己的方法。跟实例完全区分开
-
@classmethod
def tell_info(cls):#这是类方法,类名.类方法,自动传参数。不能访问实例属性
print(cls)
print('-->',cls.tag)
#def tell_info(self): #print('--->',self.tag) -
静态方法
- 只是名义上归属类管理,不能使用类变量和实例变量,是类的工具包
-
@staticmethod#类的工具包,跟实例和类都不绑定 def wash_body(a,b,c): print('%s%s%s正在洗澡'%(a,b,c)) Room.wash_body('alex','yuanhao','wupeiqi') def test(x,y):#这种定义,实例不能调用,实例是传self print(x,y)
-
组合
- 类之间的组合使用,就叫组合
-
类的组合
-
面向对象编程三大特性:继承,多态,封装
- 继承,派生衍生出新动作
-
class ParentClass1: pass class ParentClass2: pass class Subclass(ParentClass1): # 单继承 pass class Subclass(ParentClass1, ParentClass2): # 多继承 pass
- 子类可以调用父类的数据属性和函数属性
- 子类优先调用自己的属性,然后去找父类的,即使重名也是如此,并没有覆盖父类属性
- 当类之间显著不同,小类是大类的组件时,用组合。当类之间有很多相同功能,提取这些共同功能作为基类,用继承,基类即父类
- 接口继承,父类定义基本方法,但是父类不实现,子类必须能具体的实现父类的方法。
-
#归一化设计,使得高层,相对于底层开发而言的高层,外部使用者可以不加区分的处理所有接口兼容的对象集合 import abc#子类必须实现读和写的方法 class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass
- python可以继承多个类,java和c#只能继承一个类
- 新式类继承类时,应用广度优先,先左支走到头,不包括基类,然后走右边,最后找基类。F.__mro__中存储有子类继承多个父类的继承顺序
- 新式类的子类也是新式类。在python2中,只要基类没有继承object,就是经典类。python2的经典类,是深度遍历,中途会检测基类
-
子类调用父类的方法
-
class Vehicle: Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦') class Subway(Vehicle): def __init__(self,name,speed,load,power,line): #子类调用父类的方法,函数属性 # Vehicle.__init__(self,name,speed,load,power)#得写self,类调用自己的函数属性时,要传。子类调用不用传 super().__init__(name,speed,load,power)#通过super方法调用父类方法 self.line=line def show_info(self): print(self.name,self.line) def run(self): # Vehicle.run(self)#子类中调用父类的函数属性 super().run() print('%s%s号线才开动啦'%(self.name,self.line)) line13=Subway('北京地铁','10km/s','100000','电',13) line13.show_info() line13.run()
-
应用举例
-
import pickle class Base: def save(self): with open('school.db', 'wb') as f: pickle.dump(self, f) # 通过此种方式,将学校信息写入f中 class School(Base): def __init__(self, name, addr): self.name = name self.addr = addr class Course(Base): def __init__(self, name, price, period, school): self.name = name self.price = price self.period = period self.school = school school_obj = pickle.load(open('school.db', 'rb'))#打开存储有学校信息的文件 print(school_obj.name, school_obj.addr) # s1=School('oldboy','北京') # s1.save()#存储上述学校的信息
-
多态
- 定义:由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同
- 面向对象的类叫工厂,来生产实例的。所以工厂函数str就是生成字符串对象的函数
- len(str1)本质上就是str.__len__()。即不同类,可以调用同一个方法len
- 多态是动态时的绑定状态,调用不同对象的方法时。多态反应在执行时候
- 面向对象,就是不要强迫自己去做事情,定义类
- 多态是继承的一种体现。继承的目的是实现代码的重用,多态让这种重用目标的以体现
-
封装
- 是特性,是种思想,不是具体的技术
- 封装提供接口,封是隐藏的意思
-
_author_='xiansuo ' class People: _star='earth'#单下划线开头,就是隐藏,不应该在外部使用该属性 __star='earth'#python给重命名了,_People__star def get_star(self):#访问函数,或者接口函数,为外部提供访问隐藏属性的接口 print(self._star)
- 封装三层意思
- 类就是麻袋,这本身就是一种封装
- 类中定义私有的,只在类的内部使用,外部无法访问。但这也只是约定,python并没有实际限制访问
- 明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用
- 面向对象的优点
- 通过封装明确了内外,上帝造物的逻辑你无需知道,上帝想让你知道的你才能知道,明确了划分等级
- 继承+多态在语言层面支持了归一化设计,即一切皆对象,按照统一的格式去调用即可,归一化
- 泛化:所有子类和父类有一样的特点。特化:描述子类与父类的不同
-
反射/自省
-
class BlackMedium: feture='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('[%s]正在卖房子,傻逼才买呢'%self.name) def rent_hourse(self): print('[%s]正在租房子,傻逼才租呢'%self.name) b1=BlackMedium('万成置地','天露园')#生成实例 print(hasattr(b1,'name'))#检测实例b1能否调用name的数据属性 print(getattr(b1,'name'))#获取实例b1中的name属性的value func=getattr(b1,'rent_hourse')#获取实例b1中的函数的地址,等价于b1.rent_hourse func()#执行上述函数 print(getattr(b1,'rent_hoursesdfsdaf','没有这个属性'))#匹配不到,则返回后边的值 setattr(b1,'sb',True)#给实例b1的字典加个值,key和value print(b1.__dict__) setattr(b1,'name','SB') setattr(b1,'func',lambda x:x+1)#可以实例加函数属性 print(b1.__dict__) print(b1.func(10)) delattr(b1,'sb') print(b1.__dict__)
- 反射就是自己找自己,应用场景:适用于团队合作,彼此分工,一人休假,不影响其他人的代码工作。可插拔程序。
-
动态导入模块
- 动态导入的底层就是基于反射做的
- 一切皆对象,模块也是对象
-
m=__import__('dic')#以字符串的形式导入模块。无论dic.套多少层,m只拿到dic print(m) m.t.test()#执行m下边t模块的test函数 #m1.t中_test2()函数,在此种*的情况下,不能被导入。 from m1.t import * from m1.t import test1,_test2#如此方可导入 test1() test2() #import importlib m=importlib.import_module('m1.t') print(m) m.test1()#也是以字符串的形式导入上述内容
-
类的内置attr属性
- 类本质上也是一个对象,上述的反射方法同样适用于类
- 双下划线开头的attr方法
-
class Foo: x=1 def __init__(self,y): self.y=y #这三个方法是给实例用的,类不能调用 #以下三个方法系统都有内置,如果用户不定义,则在适当条件下触发系统内置的 def __getattr__(self, item): print('执行_getattr_') #__getattr__是python给类的内置方法,如果用户不定义,则执行系统默认的,就是报错。 #如果用户定义,则不报错,执行自定义的函数 #以下两种操作很多余,系统默认__delattr和__setattr_就是在执行这种操作 def __delattr__(self, item): print('删除操作_delattr_') #del self.item#无限递归了 self.__dict__pop(item):#操作底层字典进行删除 def __setattr__(self, key, value):#可以加上if判断,实现符合某种条件再设置 print('_setatter_执行') # self.key=value#这种操作属于设置操作,就会触发_setattr_操作,陷入递归 self.__dict__[key]=value#操作底层字典进行设置 f1=Foo(10) print(f1.y) print(getattr(f1,'y')) f1.ssssss#调用f1没有的属性,则执行getattr函数 del f1.y#删除操作时,会触发__delattr_操作
-
二次加工标准类型的两种方式
-
继承+派生
- str,list,tuple都是类
- class List(list),List继承list类
-
class List(list): def append(self, p_object): if type(p_object) is str: # self.append(p_object) #如此调用,又陷入递归陷阱 # list.append(self,p_object) #调用父类append本身的,建议用super的方法 super().append(p_object) else: print('只能添加字符串类型') def show_midlle(self): # 继承列表的类,并进一步加工,取得字符串得中值 mid_index = int(len(self) / 2) return self[mid_index] l1 = List('helloworld') # print(l1.show_midlle()) l1.append('SB') print(l1) l1.append(1212)
-
组合的方式完成授权,效果等价于继承加工
- 授权:授权是包装的一个特性,包装一个类型通常是对已经存在的类型的一些定制,这种做法可以新建,修改或者删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。实现授权的关键点就是覆盖__getattr__方法
- 授权的步骤
- 基于标准类型来定制自己的类型
- 跟继承没关系,通过getattr实现
-
class FileHandle:#在系统内置的文件上,新增方法,其余的授权给系统内置的文件对象即可,用getattr实现了继承的操作 def __init__(self,filename,mode='r',encoding='utf-8'): # self.filename=filename self.file=open(filename,mode,encoding=encoding)#系统提供的 self.mode=mode self.encoding=encoding def __getattr__(self, item): # self.file.read return getattr(self.file,item)#返回系统的item方法 f1=FileHandle('a.txt','w+') print(f1.__dict__) print('==>',f1.read)#触发__getattr__。看似执行自己的read方法,实则是执行open打开文件的read方法
- 文件也是对象,也可以用反射模块中的四个方法
- 在团队协作中,可插拔机制,使用反射方法判断下模块中是否有对应的函数
- 导入就是将文件拿过来,在当前文件中执行一遍
-
#导入其他模块用import as的方法,想在模块自身应用反射方法,需要通过以下语句方法来导入模块本身,然后应用反射的方法
import sys obj1=sys.modules[__name__]#__name__是当前模块名,sys.modules通过key的方法能取到当前模块print('==>',hasattr(obj1,'x'))
-
内置函数补充
- isinstance(obj,cls)判断一个对象obj是否为某类class本身或子类生成的实例
- issubclass(sub,super)判断sub是否为super的子类
- type(b1)显示实例b1所属的类
- !没啥用__getattribute__:属性存在,返回匹配的值。属性不存在,抛出一个异常
-
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print('执行的是getattr') def __getattribute__(self, item):#甭管属性是否能找到,都会触发此, print('执行的是getattribute') raise AttributeError('抛出异常了')#在此抛出这个指令的情况下,才会触发getattr f1=Foo(10) # f1.x f1.xxxxx
-
item系列
-
#.的调用方式跟getattr等三个方法相关,字典形式的操作跟getitem等三个方法相关
class Foo: def __getitem__(self, item): print('getitem') return self.__dict__[item] def __setitem__(self, key, value): print('setitem') self.__dict__[key] = value # 模拟字典新增键值对的方法 def __delitem__(self, key): print('delitem') self.__dict__.pop(key) f1 = Foo() print(f1.__dict__) # f1.name='egon'#这种方式不会触发setitem f1['name'] = 'egon' # 按照字典方式赋值,会触发setitem方法,字典是['属性']的形式 f1['age'] = 18 print(f1.__dict__) del f1.name # 通过.的方式,跟delattr那种方式匹配,不匹配delitem的方式 print(f1.__dict__) -
str方法:改变对象的字符串显示
- 一切皆对象,打印时本质都应该是object,有些对象的打印结果不是object。表明可以在自己的类中定制一些方法,来控制该类实例出的对象输出结果是什么形式
-
class Foo: #类中有默认的str方法,如果不自定义,则采用系统的 def __str__(self):#自定义该类实例的对象的打印结果 return '自定制的对象的显示方式'#必须return字符串类型 f1=Foo() print(f1)#本质是就调用的--str(f1)--方法,就是f1.__str__()方法
-
#在解释器中用__repr__
class Foo: #类中有默认的str方法,如果不自定义,则采用系统的 def __str__(self): return '这是str' def __repr__(self):#自定义该类实例的对象的打印结果 return '名字是%s 年龄是%s'%(self.name,self.age)
#不能return非字符串格式 f1=Foo() #在解释器中输入f1#本质是就调用的--repr(f1)--方法,就是f1.__repr__()方法 print(f1)#如果有str就找str,如无,则找repr。如都无,则找系统内置的 -
为实例定制format
-
format_dic = { 'ymd': '{0.year}{0.mon}{0.day}', 'm-d-y': '{0.mon}-{0.day}-{0.year}', 'y:m:d': '{0.year}:{0.mon}:{0.day}' } class Date: def __init__(self, year, mon, day): self.year = year self.mon = mon self.day = day def __format__(self, format_spec): print('我执行啦') print('-->', format_spec) if not format_spec or format_spec not in format_dic: format_spec = 'ymd' fm = format_dic[format_spec] return fm.format(self) d1 = Date(2016, 12, 26) # format(d1) print(format(d1, 'y:m:d'))
-
slots属性
- _solts_是一个类变量,变量值可以是列表,元组或者可迭代对象,也可以是字符串(意味着所有实例只有一个数据属性)
- 类的字典是共享的,供该类产生的实例调用
- 当类的属性很少,但是需要产生成千上万的实例时,每个实例都有独立的存储空间,很浪费内存
- _slots_上场后,每个实例不再有_dict_,这样不用为每个实例创建属性字典,节省内存空间
-
class Foo: # __slots__ = ['name','age'] __slots__ = 'name' # 不用每个实例创建属性字典,统一到类属性字典中找 # 缺点:每个实例只能创建一个name的属性,不能创建其他属性 # 优势所在是省内存,而不是处于限制实例扩展属性 f1 = Foo() f1.name = 'egon' print(f1.name) # print(f1.__dict__) print(f1.__slots__)
-
_doc_类方法
-
class Foo: '我是描述信息' # 这个信息不能被继承 pass class Bar(Foo): pass print(Bar.__doc__)
- c1.__module__获取当前c1是哪个模块产生的
- c1.__class__查看c1是哪个模块的哪个类产生的
-
__del__函数又叫析构函数
- 与描述符__delete__不同,当对象在内存中释放时,自动触发执行,一般无需定义。只在实例被删除,或者程序执行完毕后运行
-
__call__方法
- 对象后边加括号,触发执行,呼叫不动。f1(),就是实例f1调用创建该实例的类的call方法
class Foo: def __call__(self, *args, **kwargs): print('实例执行了,obj()') f1 = Foo() f1() # 执行就是调用该实例的类的call方法 Foo() # 调用创建该Foo实例的类的call方法#类是对象,所以也是别的类产生的
-
迭代器协议
- for循环的步骤,先通过_iter_方法把对象变为可迭代对象
-
#手动实现迭代器协议,包括iter和next方法即可
class Foo: def __init__(self, n): self.n = n def __iter__(self): return self def __next__(self): if self.n==100: raise StopIteration('终止了') self.n += 1 return self.n f1 = Foo(10) print(f1.__next__()) print(f1.__next__()) print(f1.__next__()) print(next(f1)) for i in f1:#步骤一,iter(obj),步骤二,调用obj.__iter__方法,步骤三,调用迭代器的__next__方法 print(i) -
# 迭代器实现斐波那契数列 class Fib: def __init__(self): self._a = 1 self._b = 1 def __iter__(self): return self def __next__(self): if self._b > 100: raise StopIteration('终止了') self._a, self._b = self._b, self._a + self._b return self._a f1 = Fib() print(next(f1)) print(next(f1)) print(next(f1)) print('>>>>>>>>>>>>>>') for i in f1:#接着上边的for循环开始 print(i)
-
描述符
- 如果一个类中包含三个魔术方法(__get__)(__set__)(__delete__)方法,该类就称为一个描述符
- 描述符的作用,就是对类或对象中某个成员进行详细的管理操作。描述符只管理某个成员,而setattr则是管理全部数据
- 数据描述符就至少有get和set,非数据描述符没有set
- 要想使用描述符,必须在类之前声明描述符
- 属性描述方法和描述符的区别
- 属性描述方法,__getattr__, __setattr__, __delattr__等。针对当前类/对象的所有成员的管理,属性描述方法仅对当前类有效
- 描述符,__del__, __set__, __deleate__。仅仅针对类/对象某一个成员的设置,描述符可对不同的类使用
- 描述符的使用方法
- 在py文件中定义一个描述符,既描述符,包括以上三个魔术方法
- 然后在想调用的类中,讲数据属性或函数属性=描述符,即交给描述符管理,
- 最后,在描述的三个魔术方法中,设置对应的返回值,或者设置,删除等操作即可
- 属性描述方法的使用方法
- 定义在类中,在满足条件时,触发类中自定义的getattr方法
- 如果没有定义,则调用系统内置的getattr方法
- 描述符第二种使用方式,将描述符和应用类写在一个类中,然后将成员交给描述符管理,username=property(获取描述符getusername,设置描述符 setusername, 删除描述符delusername, 文件d=‘ ’)
-
#描述符的第三种使用方式
@property # 将用户名交给描述符管理,默认是get描述符 def username(self): pass @username.setter # 设置描述符 def username(self,val): pass @username.deleter # 删除描述符 def username(self): pass - 描述符的优先级。类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发getattr
-
软件开发规范
- 有bin目录,放置可执行文件。写一端小的脚本,不同角色登录程序
- 有conf文件。静态配置
- db存放数据
- lib,公共类库,公共功能
- log模块
- src,core存储核心逻辑
- 对象序列化后,在打开时,对象是依托类存在的,要在打开序列文件的程序导入类方可执行
-
python干的好事
- 新建包或者目录后,pycharm会自动将当前路径加入环境变量中
- 为了让程序在所有平台都能用,执行以下步骤
-
import sys, os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) sys.path.append(BASE_DIR)
-
上下文管理协议
- 操作文件有三步骤
- 打开文件,赋值给一个文件句柄
- 文件操作
- 关闭文件
- with open('a.txt') as f:#open()也是工厂函数,用这种方式不用关闭函数
- 应用场景,文件,网络连接,也需要打开关闭操作,和锁的编程环境
-
class Foo: def __init__(self, name): self.name = name def __enter__(self): print('执行enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('执行exit') print(exc_type)#异常类 print(exc_val)#异常值 print(exc_tb)#异常的追踪信息 # return True#把异常吞了 # 可以在exit方法中添加close()方法 with Foo('a.txt') as f:#这句会触发enter方法,enter方法的return值赋值给f # 等同于f=obj.enter() print(f) #print(sdafasdfh)#一遇到错误就执行exit方法 print(f.name) print('000000')
-
描述符应用
- 描述符本身是新式类,被代理的类也应该是新式类
- 必须把描述符定义成类的类属性,不能定义到构造函数init中
- Python是弱类型语言,即不用定义变量的类型即可使用,没有类型检测的功能
-
# 实现针对传入值的类型检查 class Typed: def __init__(self, key, expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): # instance是实例本身,owner是实例的拥有者,即类名 print('这是get方法') # print('instance参数【%s】' % instance) # print('owner参数【%s】' % owner) return instance.__dict__[self.key] # 返回实例字典中的值 def __set__(self, instance, value): # value是key的属性值 print('set方法') # print('instance参数【%s】' % instance) # print('value参数【%s】' % value) if not isinstance(value, self.expected_type): # 如果name不是str类型,则不让设置key # print('你传入的类型不是字符串,错误') # return raise TypeError('%s 传入的类型不是%s' % (self.key, self.expected_type)) instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') # print('instance参数【%s】' % instance) instance.__dict__.pop(self.key) class People: name = Typed('name', str) # 实例的数据属性是字典形式存储的,是字符串格式,将该name数据属性代理给描述符 age = Typed('age', int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary # p1 = People('alex', 13, 13.3) # print(p1.__dict__) # p1 = People(122, 13, 13.3) p1 = People('alex', '13', 13.3) print(p1.__dict__) # print(p1.name) # p1.name # p1.name = 'egon' # print(p1.__dict__)
- 类的装饰器
-
装饰器也可以装饰类
-
def deco(obj): print('-------', obj) obj.x = 1 obj.y = 2 obj.z = 3 return obj # @deco #等价于test=deco(test),将test值传给deco # def test(): # print('test函数运行') # # test() @deco # Foo=deco(Foo),类的装饰器,将类传给装饰器的函数,返回值作为新类。以后再运行Foo,就是被deco处理过的Foo class Foo: pass print(Foo.__dict__)
# 目的:用同一个装饰器,为不同类增加不同的属性 def Typed(**kwargs): def deco(obj): # print('-->', kwargs) # print('类名-->', obj) for key, val in kwargs.items(): setattr(obj, key, val) return obj # print('==>', kwargs) return deco @Typed(x=1, y=2, z=3) # 步骤一:Typed()本身会执行一遍,返回deco。步骤二,@deco class Foo: pass print(Foo.__dict__) @Typed(name='egon') class Bar: pass print(Bar.name)
- 装饰器的应用
-
# 用装饰器来实现类属性被描述符代理,以实现针对传入值的类型检查 class Typed: def __init__(self, key, expected_type): self.key = key self.expected_type = expected_type def __get__(self, instance, owner): # instance是实例本身,owner是实例的拥有者,即类名 print('这是get方法') return instance.__dict__[self.key] # 返回实例字典中的值 def __set__(self, instance, value): print('set方法') if not isinstance(value, self.expected_type): raise TypeError('%s 传入的类型不是%s' % (self.key, self.expected_type)) instance.__dict__[self.key] = value def __delete__(self, instance): print('delete方法') # print('instance参数【%s】' % instance) instance.__dict__.pop(self.key) def deco(**kwargs): def wrapper(obj): for key, val in kwargs.items(): # print('==>', key, val) setattr(obj, key, Typed(key, val)) # Typed(key,val),给类增加一个类属性,该类属性被描述符代理 # 所以key的value不是简单的value,而不是描述符 return obj return wrapper @deco(name=str, age=int, salary=float) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary # p1 = People('alex', 13, 13.3) p1 = People('122', 13, 13.3) print(p1.__dict__)
- 自定制property
- @property,所有的@都是执行,将函数传入property执行,返回值作为新函数
-
#自定制property
class Lazyproperty: def __init__(self, func): print('====>', func) self.func = func def __get__(self, instance, owner): res = self.func(instance)#此步运行area方法,area方法就是property类实例化的self实例 return res class Room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length @Lazyproperty # 实现了给类增加描述符的功能 def area(self): return self.width * self.length r1 = Room('厕所', 1, 1) print(r1.area) -
#同上页的描述符的第二种方式
class Foo: @property#前提,必须先定义此,方可定义后边 def AAA(self): print('get的时候运行我') @AAA.setter#给AAA赋值时,触发此函数 def AAA(self,val): print('set的时候运行我',val) @AAA.deleter#删除AAA时执行此操作 def AAA(self): print('del的时候运行我') f1=Foo() f1.AAA f1.AAA='aaa' -
元类
- 产生类的类,就是type
- 元类的实例是类,正如类的实例是对象
-
#另外一种产生实例的方法
def __init__(self,name,age):#定义类中的init方法 self.name=name self.age=age def test(self): pass FFo=type('FFo',(object,),{'x':1,'__init__':__init__,'test':test})#定义类 print(FFo) print(FFo.__dict__) f1=FFo('alex',18)#实例化 print(f1.name) -
自定义元类,控制类的生成过程
-
#自定义元类
class MyType(type): def __init__(self, a, b, c): print('元类的构造函数执行') print(self)#self是产生的类Foo print(a) print(b) print(c) def __call__(self, *args, **kwargs): print('=====') obj = object.__new__(self) # object.__new__(Foo),即Foo产生一个新对象 self.__init__(obj, *args, **kwargs) # Foo.__init__() #此处的self.__init__方法,就是在执行Foo中的init方法 class Foo(metaclass=MyType): # MyType(self,'Foo',(object),{}),使用元类创建一个类Foo # MyType(4个参数)--》__init__ def __init__(self, name): self.name = name f1 = Foo('name') # 执行Foo方法就是在调用父类的call方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话