Python入门学习(二)
1 字典
1.1 字典的创建和访问
字典不同于前述的序列类型,它是一种映射类型。它的引入是为了简化定义索引值和元素值存在特定关系的定义和访问问题。
字典的定义形式为:字典变量名 = {key1:value1,key2:value2......}。key代表索引值,value代表映射值,访问字典时直接以字典变量名key进行访问。
例如:
1.2 dict()函数的用法
创建一个空字典,例
通过将key和value作为一个序列类型(元组或列表)创建一个字典,例
之所以要添加括号,是因为dict函数的参数只有一个
通过对关键字赋值创建一个字典,关键字必须是一个字符串类型并且不能加引号,因为程序会默认为其加上一个引号,例:
1.3 修改或添加字典元素
通过将变量赋值给字典访问类修改或者增加字典的元素,例:
key存在字典时,重置key的value值,不存在字典时,增加一个相(key:value)
1.4 内置函数
(1)fromkeys(iterable,value=None)创建并返回一个新的字典,第一个参数为可迭代对象(序列类型),第二个参数是value值。
(2)访问字典的内置函数,主要是访问字典的key的keys,访问字典value的values,访问item的items,也可直接调用get()通过key访问其对应的value,例:
get通过key返回对应的value值,第一个参数为key值,第二个参数为默认返回的value值,key存在则返回对应的value,不存在字典内时返回该值,如果不提供默认为none
(3)clear()和copy()
clear()清空字典
copy()相当于复制出一个新的字典,两者互不影响,而赋值=仅是将内存字典地址赋值出去,两者共同指向一个地址
(4)pop()和popitem()
pop(k,value)去除字典一个元素内置函数,k存在字典的key时,去除该key对应的(key,value),不存在时返回value的值,value不提供时报错。popitem随机去除字典内的一个(key,value)
(5)setdefault()
给字典增加一个item
(6)update()
将一个字典内的item作为item添加到字典内
2 集合
集合与字典类似,都需要大括号,不同的是集合不需要映射。集合内的元素不能重复,集合是无序的。
集合的创建可使用set函数
集合可通过add(元素)增加一个元素,remove(元素)删除一个元素,如果想要将一个集合冻结使其无法添加和删除元素,则可以使用frozenset(集合名)。
更多集合的内置函数可参考:《集合类型内建方法总结》
3 文件
open的参数如下:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
头两个参数比较重要,第一个文件路径及文件名,第二个参数是打开模式,默认为‘r’
更多可参考:《文件的打开模式和文件对象方法》
举例如下:
每一个汉字,字母或数字或特殊符号都算是一个字符,而字节是字符所占位数大小,例如一个字母字符是一个字节,一个汉字字符是两个字节。
4 os模块
Python是跨平台的语言,即同样的源代码在不同的操作系统不需要修改就可以同样实现。这个功能是通过os模块实现的,os模块可帮助我们选择正确的模块并调用。
如下列出了os模块的用法,来源于小甲鱼的:《os模块中关于文件/目录常用的函数使用方法》
os.path模块是完成跟文件路径相关的函数模块。具体可参考:《os.path模块中关于路径常用的函数使用方法》
5 异常处理
5.1 try except语句
用来检测一段代码内出现的异常并将其归类输出相关信息
try:
检测范围
except Exception[as reason]:
出现异常(Exception)后的处理代码
例如打开一个不存在的文件,将会出现FileNotFoundError,该异常属于OSError,使用try except语句可修改如下:
try:
f = open('111.txt')
print(f.read())
f.close()
except OSError:
print('文件出错了T_T')
运行结果为:
还可以使用可选项[as reason]
try:
f = open('111.txt')
print(f.read())
f.close()
except OSError as reasdon:
print('文件出错了T_T')
print('出错原因是%s'%str(reason)
运行结果为:
一个try还可以跟上多个except的语句:
try:
sum = 1 + '1'
f = open('111.txt')
print(f.read())
f.close()
except OSError as reason:
print('文件出错了T_T')
print('出错原因是%s'%str(reason))
except TypeError as reason:
print('类型出错了T_T')
print('出错原因是%s'%str(reason))
运行结果为:
发现结果中只弹出了类型错误,注意当程序检测到第一个异常后即停止运行,在except中找到相应的输出语句,如果except未包含该错误时,则直接曝出异常。
可以将Exception信息放在一个except语句下面:
try:
sum = 1 + '1'
f = open('111.txt')
print(f.read())
f.close()
except (OSError,TypeError) as reason:
print('出错了T_T')
print('出错原因是%s'%str(reason))
5.2 try finally语句
一般是在try except语句下面补充,用于成语检测到异常后仍能执行的语句
try:
检测范围
except Exception[as reason]:
出现异常(Exception)后的处理代码
finally:
无论如何都会被执行的代码
try:
f = open('111.txt','w')
print(f.write('111'))
sum = 1 + '1'
f.close()
except (OSError,TypeError) as reason:
print('出错了T_T')
print('出错原因是%s'%str(reason))
finally:
f.close()
上述代码中打开文件写入内容后,到sum = 1 + ‘1’会出现报错,导致写入内容无法保存,通过finally加上关闭文件的操作,可发现文件中已写入内容。如图已成功写入三个字符。
5.3 raise语句
raise Exception,引入一个异常,例:
6 else语句
在Python中else不仅可以和if语句搭配,实现如果条件为真则如何,否则则如何的功能。
还可以和while或者for循环搭配,实现如果循环体因为循环条件结束则如何,如果在循环体因执行了break语句后跳出则不如何的功能,例输出一个数的最大公约数的程序代码:
def showMaxFactor(num):
count = num // 2
while count > 1:
if num % count == 0:
print('%d的最大公约数是%d'%(num,count))
break
count -= 1
else:
print('%d是素数!'%num)
n = int(input('请输入一个整数:'))
showMaxFactor(n)
else还可以和try语句搭配,用于检测代码段无异常时则执行else内语句,有异常则不执行,例:
try:
m = int('123')
except ValueError as reason:
print('出错了'+str(reason))
else:
print('程序无异常!')
7 easygui
easygui是运行在tkinter上并拥有自身的事件循环,而IDLE也是tkinter写的一个应用程序并也拥有自身的事件循环。因此当两者同时运行的时候,有可能会发生冲突,且带来不可预测的结果,因此如果发现easygui有这样的问题,则应尝试在IDLE外去运行程序。
小甲鱼总结的easygui的学习文档地址为:《EasyGui学习文档》
下例为提供一个文件夹浏览框,让用户选择需要打开的文本文件,打开并显示文件内容,在此基础上增强功能:当用户点击“OK”按钮的时候,比较当前文件是否修改过,如果修改过,则提示“覆盖保存”、“放弃保存”或“另存为...”,并实现相应的功能
在此例中应注意,easygui.textbox函数会在返回字符串后边追加一个行结束符("\n"),因此在比较字符串是否发生改变的时候,如果没有人工忽略这个行结束符,则没有对文本内容做任何改变时也将提示内容发生了变换。我们可以通过text[:-1]来对文本内容进行选择,代码如下所示:
import easygui as g
import os
file_path = g.fileopenbox(default = '*.txt')
filename = os.path.basename(file_path)
msg = '文件【%s】的内容如下'%filename
title = '显示文件内容'
with open(file_path) as old_file:
text = old_file.read()
text_after = g.textbox(msg,title,text)
if text != text_after[:-1]:
choice = g.buttonbox(msg = '检测到文件内容发生改变,请选择一下操作:',
title = '警告',choices = ('覆盖保存','放弃保存','另存为...',))
if choice == '覆盖保存':
with open(file_path,'w') as old_file:
old_file.write(text_after[:-1])
if choice == '放弃保存':
pass
if choice == '另存为...':
new_path = g.filesavebox(default = '*.txt')
new_filename = os.path.basename('new_path')
with open(new_path,'w') as new_file:
new_file.write(text_after[:-1])
8 类和对象
Python也是一种面向对象的编程语言,在其内部可谓是无处不对象,我们所熟知的列表list、字符串str等工厂函数本质上都是对象。对象其实是对属性和方法的封装。属性即对象的静态特征,方法即对象的动态特征。
8.1 类方法的self参数含义
在Python中类的方法都要有self参数,实质为对类的实例化对象的绑定从而使得在类的实例化对象调用方法时能够确认出是对哪个对象进行操作,与C里面的this指针是一样的。
例子借鉴《Python初学基础》,如下所示:
class Calculator: #首字母要大写,冒号不能缺
name='Good Calculator' #该行为class的属性
price=18
def add(self,x,y):
print(self.name)
result = x + y
print(result)
def minus(self,x,y):
result=x-y
print(result)
def times(self,x,y):
print(x*y)
def divide(self,x,y):
print(x/y)
""""
>>> cal=Calculator() #注意这里运行class的时候要加"()",否则调用下面函数的时候会出现错误,导致无法调用.
>>> cal.name
'Good Calculator'
>>> cal.price
>>> cal.add(10,20)
Good Calculator
>>> cal.minus(10,20)
-10
>>> cal.times(10,20)
>>> cal.divide(10,20)
0.5
>>>
""""
8.2 构造方法
在Python中有一种特殊的方法,在实例化类对象的时候自动调用该方法,这些方法如果没有定义则系统会自动生成。定义这些方法必须在方法名左右两侧加上双下划线。比如下面要介绍的类对象属性初始化的方法——构造方法,函数名为__init__,在函数对象实例化时调用,例:
8.3 公有私有成员设置
严格来说,Python中的方法和属性都是公有的,但是可以通过名字转置的方法做出假私有的方法,即在变量或者方法前加上双下划线即可,这样做实际上是系统默认在前面加上‘_类名’,则没有办法直接对其进行访问,但是可以使用“对象名._类名__.成员名”进行访问。
8.4 继承
举例说明如下:
import random as r
class Fish:
def __init__(self):
self.x = r.randint(0,10)
self.y = r.randint(0,10)
def move(self):
self.x -= 1
print('我的位置是:',self.x,self.y)
class Tuna(Fish):
pass
class Carp(Fish):
pass
class Shark(Fish):
def __init__(self):
#调用未绑定的父类方法
#Fish.__init__(self)
#使用super函数,不需要传入self参数
super().__init__()
self.hungry = True
def eat(self):
if self.hungry:
print('吃货的梦想就是天天有的吃^-^')
self.hungry = False
else:
print('太撑了,吃不下啦')
此外,注意在Shark中重写了__init__函数,如果想要保留父类Fish中的方法,可采用两种方式,一种是调用未绑定的父类方法,二是使用super函数,如代码中所示。
Python支持多继承,方法为class 子类名(父类1,父类2,父类3...),但是应确定是否为必须使用多继承的场合,否则有可能导致代码混乱。
8.5 Python的组合机制
Python中类之间的关系可以是纵向关系,采用继承机制可以将基类里面的属性和方法全部被子类使用,而父类却不可以使用子类的方法和属性。类之间也可以是横向关系,可以通过实例化一个类的对象来作为另一类的属性来实现类之间的调用关系,例如池塘和池塘内的鱼和乌龟之间的关系等,举例说明:
class Turtle:
def __init__(self,x):
self.num = x
class Fish:
def __init__(self,x):
self.num = x
class Pool:
def __init__(self,x,y):
self.turtle = Turtle(x)
self.fish = Fish(y)
def print_num(self):
print('水池里总共有乌龟%d只,小鱼%d条'%(self.turtle.num,self.fish.num))
8.6 类内属性和方法重名问题
如果在python中一个类内定义的属性和方法重名,则属性会覆盖方法
为了避免上述情况发生,一方面不要在类内定义太多的方法和属性,另一方面方法和属性名可以根据词性来分别开,例如一个矩形类的属性可以是长、宽,而方法可以是获得长度、获得宽度等。
在python中通过class类来定义一个类,类定义好后就是一个类对象,而通过类对象实例化之后的对象就称之为类实例化对象。一般类内定义的方法和属性都是静态的,其作用域为全局作用域,而类的实例化对象中的方法和属性是动态的。
8.7 类和对象的一些BIF
(1)issubclass(class,classinfo)判断class是否为classinfo的子类,是则返回True,否则返回False。第一个参数必须是类名,第二个参数可以是类名或者由类组成的元组,python默认一个类是本身的子类。
(2)isinstance(object,classinfo)判断一个对象是否属于一个类或者类组成的元组内的某个类,第一个参数是对象名,第二个参数是类名或者元组。如果第一个参数不是对象,则永远返回False。如果第二个参数不是类或者由类对象组成的元组,会抛出一个TypeError异常。
(3)hasattr(object,name)判断一个对象内是否具有某个变量,name需要用单引号括起来
getattr(object,name[,default])返回一个对象内某个成员的值
setattr(object,name,value)设置一个对象内某个变量的值,delattr(object,name)删除一个对象的某个变量,其中第一参数为对象名,第二个参数为一个变量组成的字符串。
(4)property(fget=None,fset=None,fdel=None,doc=None)用属性设置属性,第一个参数为获取对象属性的方法名,第二个参数为设置对象属性的方法名,第三个参数为删除对象属性的方法名,可以将其赋值给一个对象属性,那么当其被一个对象调用时则调用对象内定义的获取对象属性方法,当对其进行赋值时则调用设置对象属性的方法,当用del语句删除时则调用删除对象属性的方法。
9 魔法方法
9.1 构造和析造
(1)__init__(self,*args,**kwargs),在创建完对象后调用来初始化对象,且规定其返回值为None
(2)__new__(cls,*args,**kwargs)在创建对象时调用并返回一个类对象方法,且其调用在__init__方法之前
定义一个类继承int类型,并实现一个特殊功能:当传入的参数是字符串的时候,返回该字符串中所有字符的ASCII码的和(使用ord()获得一个字符的ASCII码值)
class Nint(int):
def __new__(cls,arg = 0):
if isinstance (arg,str):
total =0
for each in arg:
total += ord(each)
arg = total
return int.__new__(cls,arg)
运行结果如下所示:
(3)del(self)用来回收一个对象,在python中只有当对象所有对其引用都消失时才会调用,将该对象空间释放,例:
9.2 算数运算符
divmod(a,b)返回的值是一个元组(a//b,a%b)
举例重写__add__和__sub__,如下所示
简单介绍一下反运算,当运算符左侧数不支持相应的运算操作时,就需要调用右侧数的运算操作,这时相应的魔法方法称为反运算,例:
1不是Nint的实例对象,所以此时调用__radd__,转为3-1=2
此外,还有增量运算符,一元运算符等等,具体可参考《Python魔法方法详解》
9.3 time模块和timeit模块
9.4 属性访问
__getattr__(self,name):定义当用户试图获取一个不存在的属性时的行为
__getattribute__(self,name):定义当该类的属性被访问时的行为
__setattr__(self,name,value):定义当一个属性被设置时的行为
__delattr__(self,name):定义当一个属性被删除时的行为
举例如下:
但当使用这些方法时要小心出现死循环,一般的规避措施是尽可能使用super()方法,例如定义一个矩形类,它有两个默认属性:width和height,当为属性square赋值时,自动将width和height相等,另外定义一个获得矩形面积的方法。
如下所示,有两种方式:
class Rectangle:
def __init__(self,width = 0,height = 0):
self.width = width
self.height = height
def __setattr__(self,name,value):
if name == 'square':
self.width = value
self.height = value
else:
## super().__setattr__(name,value)
self.__dict__[name] = value
def getArea(self):
return self.width*self.height
运行结果如下所示:
9.5 描述符
描述符是将某种特殊类型的类的实例指派给另一个类的属性。那什么是特殊类型的类呢,就是至少要在这个类里边定义__get__()、__set__()或__delete()__三个特殊方法中的任意一个。
__get__(self,instance,owner):用于访问属性,它返回属性的值
__set__(self,instance,value):将在属性分配操作中调用,不返回任何内容
__delete__(self,instance):控制删除操作,不返回任何内容
举例说明如下:
首先定义MyDecriptor类,并包含了上述所说的三个属性。接着定义Test类,将描述符类实例给类Test的属性。
将Test实例化,调用Test类的对象test的属性x,可发现调用了getting方法,self为类MyDecriptor的对象x,instance为类Test的对象test,而owner则为Test类本身
对test.x进行赋值,返回的结果显示value为X-man
重新认识property方法,我们知道property方法使用过程为先定义一个类,类内定义调用属性、设置属性和删除属性的方法并将这些方法作为propety的参数赋值给该类的一个属性。property实际上就是一个描述符类,而类内属性正是property类的实例化对象。下面将通过一个自己定义的类MyProperty来再现property类的功能。
class MyProperty:
def __init__(self,fget=None,fset=None,fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self,instance,owner):
return self.fget(instance)
def __set__(self,instance,value):
self.fset(instance,value)
def __delete__(self,instance):
self.fdel(instance)
定义一个测试类C,并定义三种属性传入到MyProperty中,运行结果如下所示:
接着采用一个更具体的例子来说明,定义一个温度类,然后定义两个描述符用于描述摄氏度和华氏度两个属性。要求两个属性会自动进行转换,即可给摄氏度这个属性赋值,然后打印的华氏度属性是自动转换后的结果。
class Celcius:
def __init__(self,value = 26.0):
self.value = float(value)
def __get__(self,instance,owner):
return self.value
def __set__(self,instance,value):
self.value = float(value)
class Fahrenheit:
def __get__(self,instance,owner):
return instance.cel*1.8 + 32
def __set__(self,instance,value):
instance.cel = (float(value) - 32) / 1.8
class Temperature:
cel = Celcius()
fah = Fahrenheit()
运行结果如下所示:
当给temp.fah赋值时,fah有一个描述符类方法,则会去调用描述符Fahrenheit中的set方法。set对temp实例对象的cel属性进行赋值,该属性也有一个描述符类Celcius。
9.6 定制容器
Python中,像序列类型(如列表、元组、字符串)或映射类型(如字典)都是属于容器类型。
定制容器有关的一些协议:《Python魔法方法详解》
(1)如果希望定制的容器是不可变的话,只需定义__len__()和__getitem__()方法
(2)如果希望定制的容器是可变的话,除了__len__()和__getitem__()方法,还需要定义__setitem__()和__delitem__()方法
举例:定义一个不可变的列表并记录列表中被访问元素的次数
__len__(self),当执行len(self)时被触发,返回一个容器类型的个数
__getitem__(self,key),当执行self[key]时被触发
class Mycountlists:
def __init__(self,*args):
self.countlist1 = [x for x in args]
self.countdict1 = dict.fromkeys(range(len(self.countlist1)),0)
def __getitem__(self,key):
self.countdict1[key] += 1
return self.countlist1[key]
def __len__(self):
return len(self.countlist1)
程序运行如下所示:
9.7 迭代器
迭代器类似循环,每次的循环称为一次迭代,且本次迭代的结果将作为下次迭代的初始值。提供迭代器操作的容器成为迭代器,我们所熟知的序列类型和字典类型均属于迭代器。关于迭代的内置函数有两个:iter()和next(),前者需要一个迭代器类型的参数,后者将迭代对象的参数输入出来,直至出现Stop Iteration异常时终止。
迭代器的内置方法位__iter__(self)和__next__(self)。前者被触发返回迭代器对象,后者将初始值和后续值进行操作。for循环语句能后触发迭代器魔法方法,举一个斐波那契数列实现的程序来说明迭代器类型的魔法方法如何使用。
9.8 生成器
《提高你的Python: 解释‘yield’和‘Generators(生成器)》
Python是通过生成器来实现类似于协同程序的概念:所谓的协同程序就是可以运行的独立函数调用,函数可以暂停或者挂起,并在需要的时候从程序离开的地方继续或者重新开始
生成器就是一类特殊的迭代器,作为一个迭代器,生成器必须要定义一些方法,其中一个就是__next__(),如同迭代器一样,我们可以使用next()函数来获取下一个值。一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字。如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return),除了以上内容,创建一个生成器没有其它多余的步骤了。每当生成器被调用的时候,它会返回一个值给调用者。在生成器内部使用yield来完成这个动作(例如yield 7)。为了记住yield到底干了什么,最简单的方法是把它当作专门给生成器函数用的特殊的return(加上点小魔法)。
yield就是专门给生成器用的return。当一个生成器函数调用yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next()。yield保存的状态就被无视了。
下面为一个简单的生成器函数,并采用了两个简单的方法来使用它。