加载页面中...
python基础02 | lwstkhyl

python基础02

python基础第二部分,包括函数、类、类型注解、异常捕获、模块与包、文件等

写在前面:本笔记为我的python TXT版笔记的重新整理

函数

定义

格式:

def 函数名(形参):
    函数体
    return (可省略)

传参时可以位置传参/关键字传参

def calc(a,b):
    c=a+b
    return c
  • 位置传参:result=calc(10,20) 此时10传给形参a,20传给形参b

  • 关键字传参:result=calc(b=10,a=20) 此时10传给形参b,20传给形参a

注意:python中不能少传入形参。python不能将缺少的形参给一个空值


传参时的值传递/引用传递:

如果是不可变对象(字符串,元组、各基本数据类型如int bool等),在函数体内修改(增/删/改/替换操作)不影响实参,即值传递

如果是可变对象(列表,字典,集合),在函数体内修改会改变实参,即引用传递

def fun(arg1,arg2):
    arg1=100
    arg2.append(10)
n1=11   
n2=[22,33,44]
fun(n1,n2)
print(n1) #11
print(n2) #[22, 33, 44, 10]

函数定义默认值参数:给形参设置默认值,当实参形参不同数量时才用形参默认值。

注意:和C相同,带默认值的形参需放到函数形参列表的结尾,如fun(a,b=10)fun(a=10,b)是不对的

def fun(a,b=10):
    print(a,b)
fun(100) #100 10 --只有100一个实参,才用形参默认值
fun(20,30) #20 30 --与形参数量相同,不用默认值

函数的返回值:

  • 函数可以没有返回值

  • 函数返回值为1个,正常返回

  • 函数返回多个值时,结果为元组

def fun(num):
    odd=[]
    even=[]
    for i in num:
        if i%2==1:
            odd.append(i)
        else :
            even.append(i)
    return odd,even #返回值为一个元组,元素是odd和even两个列表
lst=[10,29,34,24,44,53,55]
print(fun(lst)) #([29, 53, 55], [10, 34, 24, 44])

函数的参数传入

  • 个数可变的位置形参:定义函数时,可能无法事先确定传递的位置实参的个数时,使用可变的位置参数,在参数前加上*,结果为元组,最多只能定义一个

    def fun(*args):
        print(args) #args是一个元组
        print(args[0]) #可以根据索引取到元组中元素,即传入的实参
    fun(10) #(10,)  10
    fun(1,2,3) #(1, 2, 3)  1
    
  • 个数可变的关键字形参:在参数前加上**,结果为一个字典,也是最多只能定义一个

    def fun1(**args):
        print(args) #args是一个字典,键为传入的实参变量名,值为实参值
    fun1(a=10) #{'a': 10}
    fun1(a=20,b=30,c=40) #{'a': 20, 'b': 30, 'c': 40}
    

    注意:想要把实参传给个数可变的关键字形参,就必须有实参变量名,不能fun1(10),只能fun1(a=10)


在一个函数的定义过程中,若既有个数可变的关键字形参也有个数可变的位置形参,要求在定义时先位置形参再关键字形参。同时调用函数也必须先传位置实参再传关键字实参

def fun2(*arg1,**arg2):
    print(arg1)
    print(arg2)
#fun2(a=10,10)  报错
fun2(10,20,a=10,b=20) #(10, 20)  {'a': 10, 'b': 20}

调用函数时:可以使用列表传入位置实参,可以使用字典传入关键字实参

def fun(a,b,c):
    print(a,b,c)

fun(10,20,30) #10 20 30(位置传参)
lst=[10,20,30]
fun(*lst) #10 20 30(位置传参)

fun(a=100,c=200,b=300) #100 300 200(关键字传参)
dic={'a':100,'c':300,'b':200}
fun(**dic) #100 200 300  将字典中的键值对都转换为关键字实参传入

函数形参列表中使用*作为分隔符:*之前的参数采用哪种方式传入都行,之后的只能采用关键字实参传递

def fun1(a,b,*,c,d):
    print(a,b,c,d)
fun1(a=10,b=20,c=30,d=40) #10 20 30 40
fun1(20,10,d=30,c=40) #20 10 40 30
#fun1(10,20,30,40)报错,因为30和40必须以关键字形式传入

变量的作用域

def fun(a,b):
    c=a+b         
    print(c)

上面的c就称为局部变量,因为c是在函数体内进行定义的变量;a,b为函数形参,作用范围也在函数内部,也可看作局部变量。它们都只能在函数内部使用,在函数外使用会报错。


函数内无法访问或改变外部声明的全局变量,除非以实参的形式传入函数:

a=1
def func():
    #print(a)报错,函数内无法访问外部变量
    a=2 #是函数内创建的局部变量,函数内使用的都是它
    print(a) #2 函数内使用局部变量
func()
print(a) #1 函数内无法修改全局变量

若真的想在函数内部使用全局变量,可使用global关键字:

a=1
def func():
    global a #在a前加上global,表明函数内使用全局变量a
    print(a) #1
    a=2 #修改全局变量
func()
print(a) #2

注意:global变量不能与形参变量同名,否则报错;global a=20也会报错,必须分开写

递归函数

递归函数:在一个函数的函数体内调用该函数本身。

必须有递归终止条件,如使用if else等进行return;如果有返回值的话,注意返回值要从最内层逐层传递到最外层

例1:求阶乘

def fac(n):
    if n==1: #一直乘到n为1的时候
        return 1
    else: #n不为1,就乘下一个数 
        return n*fac(n-1)  
print(fac(6)) #720

例2:斐波那契数列(1 1 2 3 5 8 13 21…)

def fac(n): #n为数列的第几位
    if n==1:
        return 1
    elif n==2:
        return 1
    else:
        return fac(n-1)+fac(n-2)
for i in range(1,7): #输出数列中前6个数
    print(fac(i))  #1 1 2 3 5 8

例3:现有一个文件夹test,里面有一个txt文件和2个文件夹a、b,a中有1个txt文件,b中有1个txt文件和文件夹c,c中又有1个txt文件;现要写一个函数找到test下所有的txt文件,就可以用递归实现,返回一个list包含所有.txt文件的路径

test--a-------2.txt
      b-------3.txt
              c--------4.txt
      1.txt

利用os模块下的三个方法:

  • listdir(p)列出给定文件夹p下的所有文件,返回一个列表,元素为文件名

  • path.isdir(p)判断给定路径p是否为一个文件夹,返回bool值

  • path.exists(p)判断给定路径p是否存在

import os
def get_files_recursion_from_dir(path):
    file_list=[] #定义一个列表用于存储文件路径
    if os.path.exists(path)==True: #如果路径存在
        for f in os.listdir(path): #遍历该路径下的所有文件
            new_path=path+"/"+f #f是文件名,需要组装成完整路径进行下一步的查找
            if os.path.isdir(new_path)==True: #新文件是文件夹
                file_list+=get_files_recursion_from_dir(new_path) #就再调用函数采集内部的.txt文件,并把其结果加到file_list内
            else: #新文件不是文件夹,而是.txt文件
                file_list.append(new_path) 就将其路径放入结果列表中
    else: #如果路径不存在,返回空值
        print(f"指定目录:{path}不存在")
        return []
    return file_list
print(get_files_recursion_from_dir("D:/python/test"))

函数作为参数传入/闭包

def use_computer(computer_method): #将computer这个方法作为computer_method传入
    result=computer_method(1,2) 
    print(result)
def computer(x,y): 
    return x+y
use_computer(computer) #3

其中use_computer函数需要一个函数computer_method作为参数传入,这个函数需要能接收2个数字进行计算,计算逻辑由这个被传入函数决定。

lambda表达式(匿名函数)

形式:lambda 形参列表:函数返回值,lambda表达式返回一个函数,这个函数没有名字且只能在定义处使用一次,称为匿名函数

def use_func(func):
    res=func(1,2)
    print(res)
def add(x,y):
    return x+y
use_func(add) #3

这是上面函数作为参数传入的例子,我们可以用lambda表达式来重写add函数:add=lambda x,y:x+y,之后的使用方式不变use_func(add)

更直接的写法:

def use_func(func):
    res=func(1,2)
    print(res)
use_func(lambda x,y:x+y) #3

基础结构

class student: #student为类名,由一个或多个单词组成,类名的首字母常大写
    place='abc' #直接写在类里面的变量称为类属性
    def eat(self): #实例方法(是在类内定义的函数)(类外的def才叫函数)
        print('eat')
    @staticmethod
    def method(): #静态方法(用@staticmethod修饰)
        print('staticmethod') 
    @classmethod
    def cmethod(cls): #类方法(用@classmethod修饰)
        print('classmethod')

    def __init__(self,name,age): #构造方法
        self.name=name 
        self.age=age
print(type(student)) #<class 'type'>
print(student) #<class '__main__.student'>

__init__构造方法

在创建类对象的时候,会自动执行,将调用时传入的参数传递给该方法使用。

def __init__(self,name_val,age_val):
    self.name=name_val 
    self.age=age_val

参数列表中的self为必需形参,指向调用该构造方法的实例对象。

self.nameself.age和称为实例属性,在构造函数中进行赋值操作。它们的属性名为nameage,属性值为传入的name_valage_val


创建类的实例对象:

stu1=student('abc',20)
stu2=student('ABC',18)

student/stu1/stu2的地址都不同,因为student是类对象(上面的定义class student:),而stu1和stu2为两个不同的实例对象。

注:创建出的实例对象具有同一个数据类型student,且可放入容器中进行存储

类属性和实例属性

stu1=student('abc',20)
stu2=student('ABC',18)
print(student.place) #abc
print(stu1.place) #abc

类对象和实例对象都可以使用类属性

#print(student.name)报错,类对象不能使用实例属性
print(stu1.name) #'abc'
print(stu2.name) #'ABC'

只有实例对象才能使用实例属性,因为实例对象有构造方法

student.place='ab'
print(student.place) #ab
print(stu1.place) #ab
stu1.place='AB'
print(student.place) #ab
print(stu1.place) #AB

类属性和实例属性都是可修改的类对象.类属性=新值可以修改类对象和实例对象中的类属性值;但实例对象1.类属性=新值只能修改实例对象1自己的类属性值。

实例方法

实例对象self必须出现在传参列表中,通过它来传递实例的属性和方法(也可以传递类的属性和方法),但在实际调用时可以忽略

class student:
    name=None
    def say(self):
        print(f"hi,I'm {self.name}") #在成员方法内使用类中的变量时必须加上self.
    def say_msg(self,msg):
        print(f"hi,I'm {self.name},{msg}")
stu=student() #构建一个对象
stu.name="abc"
stu.say() #'hi,I'm abc'
stu.say_msg("111") #'hi,I'm abc,111'

注意:实例方法只能由实例对象调用

动态绑定属性和方法

stu1=student(name='abc',age=20)     
stu2=student(name='abcd',age=21)
stu1.gender='female'
print(stu1.gender) #female
#print(stu2.gender)报错,stu2中没有gender属性

动态绑定方法时不用在类定义里新添加一个构造函数,直接加变量名就行。这里新增方法时类似于新增字典元素,属性名就是键,类有这个属性时赋新值就是修改,没有时就是新增属性


def show(): #在类外定义,是函数
    print('show')
stu1.show=show #在这被绑定到stu1上,变成实例方法
stu1.show() #show
#stu2.show()报错,stu2没有绑定show函数

注意:动态绑定属性方法都只能绑定到实例对象上,不能绑定到类对象上,即不能新增类属性和类方法。

类方法和静态方法

类方法

传入的第一个参数必须是类对象,一般约定为cls,通过它来传递类的属性和方法,但不能传实例的属性和方法。函数定义前加@classmethod进行声明

实例对象和类对象都可以调用

class Student:
    school='abc'
    @classmethod
    def say_school(cls):
        #print(self.name)报错,因为name不是类属性
        print(f"My school is {cls.school}") #调用类属性前加cls.
Student.say_school()
stu = Student() #My school is abc
stu.say_school() #My school is abc
静态方法

参数随意,但无法传递类和实例的任何属性和方法。函数定义前加@staticmethod进行声明。

主要是用来存放逻辑性代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作(独立的:便于使用和运维)

实例对象和类对象都可以调用

class Student:
    school='abc'
    @staticmethod
    def say_time():
        import time
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()))
        #self.name报错
        #cls.school报错
Student.say_time()
stu = Student() #2024-02-16 10:46:35
stu.say_time() #2024-02-16 10:46:35

魔术方法

魔术方法:python中有很多内置的类方法,有着不同的功能,称为,相当于函数/运算符重载。

特点:方法名前后都有两个下划线_

__str__字符串方法

控制类转换成字符串的行为,默认情况下print(类)都是输出它的内存地址

class student:
    name=None
    age=None
    def __init__(self,name,age):
        self.name=name
        self.age=age
stu=student("abc",11)
print(stu)
print(str(stu)) #两个输出都是stu对象的内存地址

在类定义中加上:

    def __str__(self):  
        return f"name:{self.name},age:{self.age}"

再进行输出,就会都输出name:abc,age:11

__lt__小于/大于符号比较

默认情况下两个类不能用<>进行比较,即类<类类>类会报错,但通过该方法可以实现

    def __lt__(self, other):
        return self.age<other.age #设置通过age进行比较
stu1=student("abc",11)
stu2=student("bcd",12)
print(stu1<stu2) #true
print(stu1>stu2) #false  

其中def __lt__(self, other)self和other分别代表符号两侧的实例对象,返回一个bool值(<和>返回的也是bool值)。和>

注意:return self.age<other.age中必须是<,如果是>结果会相反。

其它比较符号

<=或>=:__le__

    def __le__(self, other):
        return self.age<=other.age #也是必须是<=
stu1=student("abc",11)
stu2=student("bcd",11)
print(stu1<=stu2) #true  如果不写这个方法会报错

==: __eq__

    def __eq__(self, other):
        return self.age==other.age
stu1=student("abc",11)
stu2=student("bcd",12)
print(stu1==stu2) #false  

注意:如果不写__eq__==比较的是两个对象的内存地址。不会报错,但如果是不同实例对象 结果一定为false

__repr__和eval()方法

__repr__ 方法应该返回一个字符串,该字符串显示如何创建实例对象,以将该字符串传递给eval()来重新构造实例对象。

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
    def __repr__(self):
        return f"Product({self.name!r}, {self.price!r})" #因为self.name是str,所以返回值self.name的两侧要加引号
product = Product("Vacuum", 150.0)
repr(product) #"Product('Vacuum', 150.0)"
evaluated = eval(repr(product))
#此时evaluated属性与product完全相同,是它的深拷贝

__call__方法

__call__将对象当作函数调用时触发,使用形式为对象名称(),会默认调用__call__函数里的内容,如

p = People('liuming', 20)
p('abc')  # 调用__call__方法

其它魔术方法

析构:__del__

迭代器:__iter____next__

实例化:__new__

上下文管理器:__enter____exit__

更精细的属性访问控制:__getattr____setattr__

详见https://blog.csdn.net/weixin_26755331/article/details/108495453

面向对象编程

面向对象编程的简单理解:基于模板(类)去创建实体(对象),使用对象完成功能开发,主要包含三大特性,封装、继承、多态

封装

私有成员变量/方法:以__(两个下划线)开头即可完成私有成员的设置。

私有方法无法直接被类对象/实例对象使用(在主函数中调用),私有变量无法在主函数中赋值和获取

class phone:
    __current=None
    def __single(self):
        print("single")
p1=phone()
#p1.__current报错
#p1.__single()报错
#phone.__current报错
#phone.__single()报错

私有成员/方法可以被类中的其他成员使用

class phone:
    __current=1
    def __single(self):
        print("single")
    def pd_current(self):
        if self.__current>=1: #使用self.调用私有变量
            print("true")
        else:
            self.__single() #使用self.调用私有方法
p1=phone()
p1.pd_current() #true

一个例子:设计带有私有成员的phone类,内部包含:

  • 私有成员变量:is_5g_enable,类型bool,True表示开启5g,False表示关闭5g

  • 私有成员方法:check_5g(),会判断私有成员is_5g_enable的值:

    • 若为True,打印输出:5g开启

    • 若为False,打印输出:5g关闭,使用4g网络

  • 公开成员方法:call_by_5g(),调用它会执行:

    • 调用私有成员方法:check_5g(),判断5g网络状态

    • 打印输出:正在通话中

class phone:
    __is_5g_enable=False
    def __check_5g(self):
        if self.__is_5g_enable:
            print("5g开启")
        else:
            print("5g关闭,使用4g网络")
    def call_by_5g(self):
        self.__check_5g()
        print("正在通话中")
phone1=phone()
phone1.call_by_5g() #5g关闭,使用4g网络	正在通话中

继承

子类可以继承父类的公有属性和方法,但不能访问私有属性和方法

单继承class 类名(父类名):

class phone:
    producer="abc"
    def call_by_4g(self):
        print("4g通话")
class new_phone(phone):
    face_id="10001"
    def call_by_5g(self):
        print("5g通话")
n_phone=new_phone()
print(n_phone.producer,n_phone.face_id)
n_phone.call_by_5g()
n_phone.call_by_4g() #这些功能都可以使用     

多继承class 类名(父类1,父类2,...):

class newest_phone(new_phone,phone):
    def call_by_6g(self):
        print("6g通话")
n_phone=newest_phone()
n_phone.call_by_4g()
n_phone.call_by_5g()
n_phone.call_by_6g() #这些功能都可以使用   

继承的优先级:多个父类中,如果有同名的成员,则默认以继承顺序(从左到右)为优先级,即先继承的保留,后继承的被覆盖

class phone:
    def call(self):
        print("4g通话")
class new_phone():
    def call(self):
        print("5g通话")
class newest_phone(new_phone,phone): #新类的同名函数同最左面的父类
    pass
n_phone=newest_phone()
n_phone.call() #5g通话 

复写:子类在继承父类的成员属性和方法后,可以对其进行修改,只要在子类中重新定义同名的属性/方法即可

#接上例
class newest_phone(new_phone,phone):
    def call(self):
        print("6g通话")
n_phone=newest_phone()
n_phone.call() #6g通话

调用父类的同名成员:如果想使用被复写的父类的成员,有两种方法:

  • 父类名.成员变量/成员方法(self),如phone.producerphone.call(self)

    class phone:
        def call(self):
            print("4g通话")
    class new_phone(phone):
        def call(self):
            print("5g通话")
        def use_call(self,is_old=False):
            if is_old:
                phone.call(self) #调用父级成员方法,注意需传入self
            else:
                self.call() #调用复写后的新方法
    new_p=new_phone()
    new_p.use_call() #5g通话
    new_p.use_call(True) #4g通话
    
  • super().成员变量/成员方法(),如super().producersuper().call()

注意这里调用父级方法时不用写self

多态

完成某个行为时,使用不同的对象会得到不同的状态;比如同一个函数,当传入的参数(类型)不同时会返回不同的值。

多态常作用在继承关系上,比如函数形参声明接收父类对象,实际传入父类的子类对象进行工作,达到同一行为不同状态的结果。

class animal:
    def speak(self):
        pass
class dog(animal):
    def speak(self):
        print("wolf")
class cat(animal):
    def speak(self):
        print("meow")
def do_speak(ani:animal):
    ani.speak()
d=dog()
c=cat()
do_speak(d) #'wolf'
do_speak(c) #'meow'

抽象类:如前面的父类animal中的speak方法是空实现pass,这样设计的含义是:由父类确定有哪些方法,而具体的实现由子类自行决定。这种写法就叫做抽象类(接口),方法体是空实现pass的称为抽象方法

class ac: #抽象类
    def cool_wind(self):
        pass
    def hot_wind(self):
        pass
    def swing_wind(self):
        pass
class m_ac(ac): #具体实现1
    def cool_wind(self):
        print("m制冷")
    def hot_wind(self):
        print("m制热")
    def swing_wind(self):
        print("m_swing_wind")
class g_ac(ac): #具体实现2
    def cool_wind(self):
        print("g制冷")
    def hot_wind(self):
        print("g制热")
    def swing_wind(self):
        print("g_swing_wind")
def make_cool(a:ac):
    a.cool_wind()
m=m_ac() #实例对象1
g=g_ac() #实例对象2
make_cool(m) #'m制冷'
make_cool(g) #'g制冷'

类型注解

类型注解:在代码中涉及数据交互的地方,提供数据类型的注解(显式说明),如对变量和函数(方法)形参列表及返回值的类型注解

变量的类型注解

  • 第一种方法:创建变量时使用变量:类型,如:

     #基本数据类型
     var_1:int=10
     var_2:str="abc"
     var_3:bool=True
     #类
     class student:
         pass
     stu:student=student()
     #容器类数据类型
     my_list:list=[1,2,3]
     my_tuple:tuple=(1,"abc",True)
     my_dict:dict={"abc":111}
    

    类型详细注解:标注容器内元素的类型–使用[元素类型,...]

    my_list:list[int]=[1,2,3]
    my_tuple:tuple[int,str,bool]=(1,"abc",True)
    my_dict:dict[str,int]={"abc":111}
    

    其中,元组类型设置类型详细注解,需要将每一个元素都标记出来;字典类型设置类型详细注解,需要两个类型,第一个是key第二个是value

  • 以注释的形式#type:类型,各类型的表示方式同上

    var_1=10 #type:int
    my_dict={"abc":111} #type:dict[str,str]
    

函数的类型注解

对形参的类型注解:如果不加该注解,函数体中使用形参时无任何提示,同时调用该函数传入参数时也没有参数类型的提示

def 函数方法名(形参名:类型,形参名:类型,...):

def add(data1:list,data2:list):


对返回值的类型注解:def 函数方法名(形参)->返回值类型:

def func(a:int)->int:

联合类型注解

对于[1,"abc"]{"name":"abc","age":11}这种混合的类,可以使用联合类型注解union[类型,类型,...]

在使用联合类型注解前需导入typing模块的Union函数:

from typing import Union
my_list:list[Union[str,int]]=[1,"abc"] #列表中元素为str或int类型
my_dict:dict[str,Union[str,int]]={"name":"abc","age":11} #str表示该字典的键是str类型,Union[str,int]表示值是str或int类型
def func(data:Union[str,int])->Union[str,int]: #返回str或int类型

一些补充说明

注意:一般可以直接看出变量类型时不添加类型注解,当无法直接看出类型时(如json.loads()random.randint()这种)才会添加。

类型注解主要用于帮助ide对代码进行类型推断、协助做代码提示,以及帮助作者自己备注变量类型,但不会真正决定变量的类型,ide仍会根据注解中的实际值给变量赋值,而不是根据作者写的类型

var1:int="abc"
var2:str=111
print(var1,var2) #'abc' 111

异常捕获

常见错误:

  • input返回字符串,若想得到一个数字,需要int(input())

  • 使用while循环若需要计数变量,必须在外部声明并赋初值

  • if里面==写成=

  • 结构体内代码缩进错误

  • 按索引取元素时越界

常见的异常类型:

  • ZeroDivisionErrorprint(10/0)->ZeroDivisionError: division by zero除数不能为0

  • IndexErrorlst=[1,2] print(lst[2])->IndexError: list index out of range索引越界

  • KeyErrordic={'name':'abc','age':20} print(dic['gender']) ->KeyError: 'gender'字典中没有这个键

  • NameErrorprint(num)->NameError: name 'num' is not defined未定义变量

  • SyntaxErrorint a=20->SyntaxError: invalid syntax语法错误

  • ValueErrora=int('abc')->ValueError: invalid literal for int() with base 10: 'abc'字符串不能转为int

为应对这些数据处理过程中出现的问题,引入try...except...else...finally异常捕获机制,结构:

try:
    ...
except 异常种类:  #可以有多个except块
    ...
else:
    ...
finally:
    ...

先执行try块中的代码,若执行过程中抛出异常,则找有没有捕获对应异常except块;如果try块中没有抛出异常,则执行else块;无论是否发生异常,finally块都会被执行,常用来释放try块中申请的资源

注意try语句必须至少有一个exceptfinally子句

一个例子:

try:
    a = int(input('第一个整数:'))
    b = int(input('第二个整数:'))
    result = a / b
except ZeroDivisionError as e: #e指代ZeroDivisionError
    print('除数不能为0',e)
except ValueError as f: #f指代ValueError
    print('只能输入数字',f)
else: #若没有异常则说明计算正常,输出结果
    print('结果为:', result)
finally: #程序结束
    print('程序结束')

输入–输出1:

第一个整数:10 
第二个整数:0
除数不能为0 division by zero
程序结束

输入–输出2:

第一个整数:10
第二个整数:ab
只能输入数字 invalid literal for int() with base 10: 'ab'
程序结束

输入–输出3:

第一个整数:10
第二个整数:5
结果为: 2.0
程序结束

异常具有传递性:若在函数1里面try函数2,函数2中出现异常,则也会被捕获。

模块与包

模块

一个模块中包含函数、类、语句等,很多个模块组成python程序。

导入模块:

  • import 模块名,如:

    import math
    print(dir(math)) #可以查看该模块中都有什么功能
    print(math.pow(2,3)) #使用模块中的函数
    
  • from 模块名 import 函数,如:

    from math import pow
    print(pow(2,3)) #只导入了math中的pow函数,调用时不用加math.
    from math import * #导入math中的所有函数,使用math中的函数都不用加math.
    
  • 导入自己创建的模块:自己创建一个.py文件,写入函数;左侧文件列表中找到它所在文件夹,右键->将目录标记为->源代码根目录;之后在main中可以使用前面两种方法导入模块(模块名就是文件名.py之前的部分)。

    注意这个.py文件和main.py要在一个文件夹下。

以主程序形式运行:

在自己创建模块调试程序时,有时需要运行代码来检测结果。如果直接写在模块中,在main导入该模块后,运行main时也会运行模块中的代码,产生不必要的输出。解决办法:

将模块中的测试代码放入if __name__=='__main__':语块内,这样只有在模块文件界面运行时才执行测试代码,在main界面运行不会执行(此时模块不是主程序)

快捷打出if __name__=='__main__':的方式:输入main,敲回车即可

一个包中可能有很多个模块。

新建一个python软件包package1,里面自动包含__init__.py文件,在这个文件夹里面再新建一个model1.py(包中新建一个模块),在里面写入a=10,回到main中:

import package1.model1 #导入package1包中的model1模块
print(package1.model1.a) #10

也可以:

import package1.model1 as pm #声明pm为package1.model1的别名
print(pm.a) #更加简洁

注:这种as声明别名的方式也可用于直接导入模块/包。


总结:使用import方式进行导入时只能写包名或模块名;使用from-import时:from+包名+import+模块名from+模块名+import+函数名 均可,可以导入包、模块、函数、变量等。


第三方包的安装:

  • 命令行中输入:pip install 包名称,也可设置国内镜像地址提高安装速度:

    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名称 (https://pypi.tuna.tsinghua.edu.cn/simple 为国内镜像地址)

    安装特定版本:pip install cchardet==2.1.3即是安装2.1.3版本的cchardet包

  • 用pycharm自带的安装:点击右下角的python.exe,选择解释器设置,可以看到安装了哪些包;

    点击+,可以进入安装界面,搜索想安装的包,点击右下角的选项,输入-i https://pypi.tuna.tsinghua.edu.cn/simple设置国内镜像地址

一个创建自定义包的实例

创建一个自定义的包my_utils,包内提供两个模块str_util.pyfile_util.py

str_util.py包含函数:str_reverse(s)接受传入字符串,将字符串反转返回;substr(s,x,y)按照下标x和y,对字符串进行切片。

file_util.py包含函数:print_file_info(file_name)接收传入文件的路径,打印文件的全部内容,如文件不存在则捕获异常,输出提示信息,通过finally关闭文件对象;append_to_file(file_name,data)接收文件路径以及传入数据,将数据追加写入到文件中>

  • 创建包:右键左侧pythonproject,选择new(新建)->python package(软件包),名称为my_utils,回车,可以找到一个__init__.py的文件;

  • 右键左侧my_utils,选择new(新建)->python file(文件),名称为str_util.py,同样方法添加一个file_util.py

  • 说明文档:在.py文件中连续输入3次",再回车,会出现

    """
    #第一行可以写入描述函数功能的文字
    :param s: #函数传入参数
    :return: #函数返回参数
    """
    
  • 在str_util.py中测试函数功能:记住一定要在if __name__=='__main__':下写测试代码,这样在作为包导入的时候就不会执行调用函数进行测试的内容;注意右上角运行按钮左面的那个小框框里面应该是当前文件或者是当前.py文件名

完整代码:

str_util.py:

def str_reverse(s):
    """
    功能是将字符串完成反转
    :param s: 要被反转的字符串
    :return: 反转后的字符串
    """
    return s[::-1] #直接使用切片的方式完成
def substr(s,x,y):
    """
    功能是按照给定的下标完成给定字符串的切片
    :param s: 要被切片的字符串
    :param x: 切片的开始下标
    :param y: 切片的结束下标
    :return: 切片完成后的字符串
    """
    return s[x:y:]
if __name__ == '__main__':
    print(substr("12345",1,3)) #"23"
    print(str_reverse("abc")) #"cba"

file_util.py:

def print_file_info(file_name):
    """
    功能是将给定路径的文件内容输出到控制台中
    :param file_name: 要读取的文件路径
    :return: 无返回值
    """
    f=None #声明变量初始值,确保当文件不能打开时f变量存在,以便后续判断
    try:   #要解决路径不存在的异常
        f=open(file_name,"r",encoding="UTF-8") #以只读方式打开文件
        content=f.read()
        print("文件的内容如下")
        print(content)
    except Exception as e: #捕获异常
        print(f"程序出现异常,原因是:{e}")
    finally: #关闭文件
        if f!=None:  #只有f不是none(路径存在)才能进行close操作
            f.close()
def append_to_file(file_name,data):
    """
    功能是将指定的数据追加到指定的文件中
    :param file_name: 指定的文件路径
    :param data: 指定的数据
    :return: 无返回值
    """
    f=None
    try: 
        f=open(file_name,"a",encoding="UTF-8") #以追加方式打开文件
        f.write(data)
        f.write("\n")
    except Exception as e: #捕获异常
        print(f"程序出现异常,原因是:{e}")
    finally: #关闭文件
        if f!=None: 
            f.close()
if __name__ == '__main__':
    append_to_file("D:/test1.txt","222")
    print_file_info("D:/test1.txt")

main.py主函数调用:

import my_utils.str_util #这种方法调用其中函数时要加上my_utils
from my_utils import file_util #这种方法就不用,只需加file_util
print(my_utils.str_util.str_reverse("123"))
print(my_utils.str_util.substr("abcde",1,3))
file_util.append_to_file("D:/test1.txt","main")
file_util.print_file_info("D:/test1.txt")

文件

打开和关闭

  • open(文件名,打开模式,编码格式)

    • 文件名:以字符串形式,用/不是\

    • 打开模式:只读–r、只写–w(原有内容被删除)、追加–a

    • 编码格式:一般为utf-8,open方法默认为gbk,如果想要保存中文,就必须指定编码格式为utf-8

    如:

    f=open("D:/python/shiyan.txt","r",encoding="UTF-8") #encoding参数不是open构造函数中的第三个参数,中间省略了多个复杂参数,所以要用关键字传参
    print(type(f)) #<class '_io.TextIOWrapper'>
    

    最后使用f.close()关闭文件,注意用这种方法打开文件后必须手动关闭

  • 另一种打开文件的方法:

    with open(文件名打开模式编码格式) as f: 
        #对文件的操作
    

    该方法可以自动关闭文件

读取内容

  • a=f.read(n)读取文件中的前n个字节,若不给n这个参数,就全读取。读取的内容以str形式保存到a内,包括换行\n

  • line_list=f.readlines()以行为单位,把每行内容都转化为列表中的一个元素,回车换行用\n表示

    ['123456789\n', 'abcd']

  • line=f.readline()一次读取一行,不读取换行\n,以str形式保存到a内

  • 用for循环读取每行数据:也不读取换行\n

    for line in f: #用for循环读取每行数据
        print(line) #123456789  abcd
    

注意:如果对同一文件连续多次读取操作,第二次读取会从第一次的末尾开始读,比如第一次读了前10个,第二次仍然read(10),就会读取实际中第10-20个字符

写入内容

f.write("123")将”123”写入到文件中,

注意:直接调用write()函数时,内容并未真正写入文件,而是积攒在程序的内存中,称为缓冲区;使用f.flush()刷新内容后,此时内容才真正写入文件。这样做是为了避免频繁操作硬盘,导致效率下降(积攒很多后一次性写入效率更高)


使用w模式打开文件时,如果向文件中写入内容,会在清空文件的原有内容后,再写入内容。若文件不存在就直接创建

如:

f=open("D:/python/shiyan.txt","w",encoding="UTF-8") 
f.write("123")  
f.close() #close函数会内置flush的功能,若有close就无需flush

with open('D:/python/shiyan.txt','w',encoding='utf-8') as fp:
    fp.write("123")

使用a模式打开文件时:原文件不清空,而是追加写入,文件不存在时也会自动创建。

使用方法同上

打开模式

使用w/a模式打开文件,只能写不能读取;使用r模式打开文件,只能读取不能写。如果同时读写,需使用其它的模式:

常用的打开模式 可做操作 若文件不存在 是否覆盖文件原来内容
r 只读 报错 -
r+ 可读、可写 报错
w 只写 创建
w+ 可读、可写 创建
a 只写 创建 否,追加写
a+ 可读、可写 创建 否,追加写

使用案例

统计给定单词的出现次数
  • 第一种方法:把所有内容读取成一个字符串,用字符串的count(给定单词)方法直接统计

    f=open("D:/python/shiyan.txt","r",encoding="UTF-8")
    content=f.read()
    count = content.count("word")
    print(f"word出现了{count}次")
    f.close()
    
  • 第二种方法:按照空格进行切分,把每个单词与给定单词进行比较

    f=open("D:/python/shiyan.txt","r",encoding="UTF-8")
    count=0
    for line in f:
        line=line.strip() #去除每行单词开头和结尾的空格以及换行符,若没有,切分完成后每行的最后一个单词后面就会带一个\n影响判断
        words=line.split(" ") #以空格为分界线进行切分
        for word in words:
            if word=="word":
                count+=1
    print(f"word出现了{count}次")
    f.close()
    
文件的备份

有一份文件bill.txt记录了多次实验的数据以及是否为测试实验,是否为测试标记在每行数据的末尾(行内的第5个数据),每项数据间用,进行分隔。现在要将文件写出到bill.txt.bak文件作为备份,同时将标记为测试的数据行丢弃。

思路:r模式打开一个文件对象并读取文件,再用w模式打开另一个文件用于写出,用for循环检测是否是测试,如果不是就写出,最后将两个文件关闭。

fr=open("D:/python/bill.txt","r",encoding="UTF-8")
fw=open("D:/python/bill.txt.bak","w",encoding="UTF-8")
#这里使用w或a模式都可,因为写出的内容在内存中,还没到磁盘中,可以在循环中追加写入(w模式写入内存中是不会覆盖的,只有写入文件中才会覆盖原有文件)
for line in fr :
    line=line.strip()  #去除换行符
    if line.split(",")[4]=="测试": #line.split(",")是一个列表,[4]表示其中的第5个元素
        continue #直接跳出这一次循环
    fw.write(line)  #不是测试就直接写入该行
    fw.write("\n")  #前面对line去除了换行符,这里要加上
fr.close()
fw.close()