Python面向对象编程

Python面向对象编程

一、面向对象基础

1、面向对象

(1)面向对象与面向过程的区别

面向过程

需要实现一个功能时,着重的是过程,分析出一个个步骤,并把一个个步骤用一个个函数实现,再依次调用一个个函数即可。

面向对象

需要实现一个功能时,看重的是谁去帮我做这件事情。

2、类和对象

类是一系列具有相同属性和行为的事物的统称,不是真实存在的事物。
对象是类的具体实现,是类创建出来的真实存在的事物,面向对象思想的核心。
在开发中,先有类,再有对象

(1)类的三要素

1、类名(要符合标识符规定,遵循大驼峰命名法,见名知义)
2、属性:对象的特征描述,用来说明是什么样子的
3、方法:对象具有的功能(行为),用来说明能够做什么

举例

类名:人类
属性:身高、体重、年龄
方法:走路、说话、学习

(2)定义类

基本格式

class 类名:
     代码块

类属性:即类所拥有的的属性
查看类属性:类名.属性名
新增类属性:类名.属性名 = 值

举例

class Washer:
     height = 800    #类属性
print(Washer.height) 
Washer.width = 450   
print(Washer.width)   

(3)创建对象

创建对象的过程也称实例化对象

实例化对象的基本格式:对象名 = 类名()

举例:实例化一个洗衣机对象

#第一次实例化
wa = Washer()
print(wa)    #显示的是对象在内存中的地址
#第二次实例化
wa2 = Washer()
print(wa2)

输出结果
在这里插入图片描述
由输出结果不同,即内存地址不同,说明是不同的对象,所以可以实例化多个对象

(4)实例方法和实例属性

1、实例方法

实例方法:由对象调用,至少有一个self参数,执行实例方法的时候,自动调用该方法的对象赋值给self
对象调用类中方法基本格式:对象名.方法名()

class Washer:
     height = 800      #类属性
     def wash(self):   
         print("我在洗衣服")
         print(self)
 wa = Washer()
 wa.wash()    
 print(wa)        

输出结果
在这里插入图片描述
由输出结果,print(self)和print(wa)输出的结果一样,同时也说明self代表对象本身

关于self

self参数是类中实例方法必须具备的
self表示当前调用方法的对象
self代表对象本身,当对象调用实例方法时,python会自动将对象本身的引用作为参数,传递给实例方法的第一个参数self里面

2、实例属性

基本格式: self.属性名

class Person:
    name = 'zct'           #类属性
    def introduce(self):   #实例方法
        print('我是实例方法')
        print(f'{Person.name}的年龄:{self.age}')    #self.age---实例属性
pe = Person()
pe.age = 18
pe.sex = '女'      #实例属性
print(pe.sex)     #根据对象名访问实例属性
#print(Person.sex) #报错,实例属性只能由对象名访问,不能由类名访问
pe.introduce()

#访问类属性,类可以访问到,实例对象也可以访问到
print(Person.name)   #类访问
print(pe.name)       #实例对象访问

pe2 = Person()
# print(pe2.sex)     #报错,pe.sex='女',是给pe对象新增的属性,其他对象依然是没有这个属性的

pe2.sex = '男'       #每实例化一次,就需要添加一次这个实例属性 
print(pe2.sex)

由此,类属性和实例属性的区别

1、类属性属于类,是公共的,大家都能访问到的,实例属性属于对象的,是私有的
2、类属性,类可以访问到,实例对象也可以访问到;实例属性只能由对象名访问,不能由类名访问

3、构造函数__init__()

(1)初始化方法__init__()

Python里的内置方法
作用:通常用来做属性初始化或者赋值操作
注意:在类实例化对象的时候,会自动调用

class Test:
    def __init__(self):    #self--实例方法
        print('这是__init__()函数')
te = Test()      #在实例化对象时,就自动调用了__init__()实例方法

输出结果
在这里插入图片描述
(2)在初始化方法内部定义属性
在__init__()方法内部,使用self.属性名 = 属性的初始值定义属性。在实例化对象后,对象就拥有该属性

class Person:     #人类
    def __init__(self):
        self.name = 'zct'     #实例属性
        self.age = 18
        self.height = 183
    def play(self):
        print(f"{self.name}在打王者荣耀")
    def introduce(self):
        print(f"{self.name}的年龄是{self.age},身高是{self.height}cm")
#实例化对象
pe = Person()
pe.play()
pe.introduce()

输出结果
在这里插入图片描述
缺点:实例属性的值固定,创建新的对象时,实例属性不能改变,不灵活。
(3)使用形参的方式

class Person:     #人类
    def __init__(self,name,age,height):
        self.name = name     #实例属性
        self.age = age
        self.height = height
    def play(self):
        print(f"{self.name}在打王者荣耀")
    def introduce(self):
        print(f"{self.name}的年龄是{self.age},身高是{self.height}cm")
#实例化对象
pe = Person('zct',18,183)
pe.play()
pe.introduce()
#实例化第二个对象
pe2 = Person('zzz',19,163)
pe2.play()
pe2.introduce()

输入结果
在这里插入图片描述

4、析构函数__del__()

del()主要是表示该程序块或者函数已经全部执行结束
删除对象的时候,解释器会默认调用__del__()方法

class Person:
    def __init__(self):
        print('我是__init__()')
    def __del__(self):
        print('被销毁了')
p = Person()

输出结果
在这里插入图片描述

正常运行的时候,不会调用__del__(),执行结束后系统会自动调用__del__(),程序执行顺序如下输出结果所示:

class Person:
    def __init__(self):
        print('我是__init__()')
    def __del__(self):
        print('被销毁了')
p = Person()
print("这是最后一行代码")

输出结果
在这里插入图片描述
del p 执行的时候,内存会立即被回收,会调用对象本身的__del__()方法,此时程序执行顺序如下输出结果所示:

class Person:
    def __init__(self):
        print('我是__init__()')
    def __del__(self):
        print('被销毁了')
p = Person()
del p           #删除p对象
print("这是最后一行代码")

输出结果
在这里插入图片描述

二、面向对象的三大特性

面向对象三大特性:封装继承多态

1、封装

封装是指隐藏对象中一些不希望被外部所访问到的属性或方法

(1)私有属性(受保护的属性)

①私有属性
私有属性是指只允许在类的内部使用,无法通过对象访问的属性。隐藏属性会在属性名或者方法名前面加上两个下划线__。

class Person:
    name = 'zct'    #类属性
    __age  = 28     #私有属性
pe = Person()
print(pe.name)
print(pe.__age)     #报错

输出结果
在这里插入图片描述
因为name是类属性,可以在类外由对象访问,而__age是私有属性,只允许在类的内部使用,无法通过对象访问。

②使用对象名._类名__属性名访问私有属性(了解)

print(pe._Person__age)
pe._Person__age = 18        #对隐藏属性修改
print(pe._Person__age)

输出结果
在这里插入图片描述

③在类的内部访问私有属性(推荐使用的方法)
通过在实例方法内部直接通过类名.__私有属性访问

class Person:
    name = 'zct'    #类属性
    __age  = 28     #私有属性
    def introduce(self):    #实例方法
        Person.__age = 18
        print(f"{Person.name}的年龄是{Person.__age}")   #在实例方法中访问私有属性
pe = Person()
pe.introduce()

输出结果
在这里插入图片描述

(2)隐藏属性

①单下划线开头、双下划线开头、无下划线属性的区别

1、无下划线属性
普通属性/方法,完全公开,可被外部直接访问或修改
2、双下划线开头
私有属性和方法,外部不能直接访问,需要通过名称修饰机制来访问。
子类不能直接继承父类的私有属性和方法,因为名称修饰机制会改变属性和方法的名称。
在另一个py文件中通过from xxx import *导入的时候,可以导入,因为Python 的 all 列表默认不会排除双下划线开头的成员
魔法方法和属性是双下划线开头且双下划线结尾,而双下划线开头但不双下划线结尾的是私有成员
3、单下划线开头
隐藏属性,如果定义在类中,外部可以直接访问,但不建议这么做
子类可以继承父类单下划线开头的属性和方法
另一个py文件中通过from xxx import *导入的时候,默认一般不会导入。
主要是作为一种约定,提示开发者这是类内部使用的成员

class Person:
    name = 'zct'
    __age = 22    #私有属性(双下划线)
    _sex = '男'    #隐藏属性(单下划线)
pe = Person()
#print(pe.sex)    #报错
#使用对象名._属性名调用
print(pe._sex)

访问隐藏属性,使用对象名._属性名方式

(3)私有方法

①私有方法

class Man:
    def __play(self):    #私有方法
        print('玩手机')
    def funa(self):      #普通的实例方法
        print('普通的实例方法')
ma = Man()
ma.funa()
ma.__play()    #报错

输出结果
在这里插入图片描述
因为私有方法,外部不可以访问
②通过对象名._类名__私有方法调用(不推荐)

ma._Man__play()

输出结果
在这里插入图片描述
③在实例方法中**类名.__私有方法(self)**调用私有方法(不建议)

class Man:
    def __play(self):    #私有方法
        print('玩手机')
    def funa(self):      #普通的实例方法
        print('普通的实例方法')
        Man.__play(self)     #在实例方法中调用私有方法  ----不推荐
ma = Man()
ma.funa()

④在实例方法中通过**self.__私有方法()**调用私有方法(推荐)

class Man:
    def __play(self):    #私有方法
        print('玩手机')
    def funa(self):      #普通的实例方法
        print('普通的实例方法')
        self.__play()        #推荐使用,更简便
ma = Man()
ma.funa()

(4)隐藏方法

class Girl:
    def _buy(self):      #隐藏方法
        print('整天买买买')
girl = Girl()
girl._buy()

隐藏方法可以在外部通过**对象名._隐藏方法()**调用(不建议)

2、继承

(1)继承概念

类的继承是一种面向对象编程的重要特性,允许一个类(子类、派生类)继承另一个类(父类、基类)的属性和方法。子类可以复用父类已有的代码,避免重复编写。子类能在继承父类的基础上,添加新的属性和方法,或者对父类的方法进行重写(覆盖),以实现特定功能。

(2)继承方式

①单继承
class Person:
    def eat(self):
        print('我会吃饭')
class Girl(Person):
    pass       #占位符,代码里面类下面不写任何东西,会自动跳过,不会报错
class Boy(Person):
    pass
girl = Girl()
girl.eat()
boy = Boy()
boy.eat()

由此也可知,子类可以继承父类的属性和方法,即便子类自己没有,也可以使用父类的

②多继承

多继承指一个子类可以同时继承多个父类,并且具有所有父类的属性和方法。
在Python中,一个类可以通过在类定义时在括号中指定多个父类来实现多继承,多个父类之间使用逗号分隔。
多继承的缺点:容易引发冲突;会导致代码设计的复杂度增加

class Father(object):
    def money(self):
        print('拥有一百万财产需要被继承')
class Mother(object):
    def appearance(self):
        print('绝世容颜需要被继承')
class Son(Father,Mother):
    pass
son = Son()
son.money()
son.appearance()

输出结果
在这里插入图片描述

当多继承中,不同的父类存在同名的方法时,子类调用父类方法时采用就近原则,括号的父类哪一个离的最近,就优先调用哪个方法(在开发中尽量避免这种情况),例如:

class Father(object):
    def money(self):
        print('拥有一百万财产需要被继承')
class Mother(object):
    def money(self):
        print('拥有一百二十万财产需要被继承')
    def appearance(self):
        print('绝世容颜需要被继承')
class Son(Father,Mother):
    pass
son = Son()
son.money()
son.appearance()

输出结果
在这里插入图片描述
因为在搜索方法时,父类的money()方法在前面,故优先继承父类的money()方法。

补充:方法的搜索顺序(了解)
python中内置属性 __mro__可以查看方法搜索顺序,搜索方法时,会先按照__mro__的输出结果,从左往右的顺序查找,如果找到最后一个依旧没有找到,就会报错。例如:

print(Son.__mro__)

输出结果
在这里插入图片描述
举例:

class Father(object):
    def money(self):
        print('拥有一百万财产需要被继承')
class Mother(object):
    def money(self):
        print('拥有一百二十万财产需要被继承')
    def appearance(self):
        print('绝世容颜需要被继承')
class Son(Father,Mother):
    # pass
    def money(self):
        print('十万')
son = Son()
son.money()
son.appearance()

输出结果
在这里插入图片描述
因为由上面print(Son.mro)输出结果可知,如果子类中有同名方法,优先调用子类的该方法,所以输出“十万”。

③继承的传递性

C类继承B类,B类继承A类,C类具有A类和B类的属性和方法。继承的传递性,即子类拥有父类以及父类的父类中的属性和方法。

class Father:
    def eat(self):
        print('吃饭')
    def sleep(self):
        print('睡觉')
class Son(Father):
    pass
class Grandson(Son):
    pass
son = Son()
son.eat()
son.sleep()
grandson = Grandson()
grandson.eat()
grandson.sleep()

输出结果
在这里插入图片描述

(3)覆盖父类方法(重写)

重写指的是子类重新定义父类中已有的方法,子类的方法会覆盖掉父类的同名方法。当调用该方法时,执行的是子类中重写后的方法逻辑,而非父类原有的逻辑。

class Person:
    def money(self):
        print('一百万需要被继承')
class Man(Person):
    def money(self):
        print('自己赚一千万')
man = Man()
man.money()

输出结果
在这里插入图片描述

(4)扩展父类方法

扩展父类方法通常是指在子类中既保留父类方法的原有功能,又添加新的功能。

调用在子类中添加的方法的方式有三种:
1、父类名.方法名(self)
2、super().方法名() ----推荐使用
3、super(子类名,self).方法名()

class Person:
    def money(self):
        print('一百万需要被继承')
    def sleep(self):
        print('睡觉了')
class Man(Person):
    def money(self):
        Person.money(self)     #第一种方法
        print('自己赚一千万')
man = Man()
man.money()
class Person:
    def money(self):
        print('一百万需要被继承')
    def sleep(self):
        print('睡觉了')
class Man(Person):
    def money(self):
        super().money()          #第二种方法
        super().sleep()          #可以调用父类中的其他方法
        print('自己赚一千万')
man = Man()
man.money()
class Person:
    def money(self):
        print('一百万需要被继承')
    def sleep(self):
        print('睡觉了')
class Man(Person):
    def money(self):
        super(Man,self).money()    #第三种方法
        print('自己赚一千万')
man = Man()
man.money()

(5)新式类写法

Python中的类(class)有两种:

#经典类
class A:      
    pass
#新式类
class A(object):
    pass    

在Python 2中,新式类是通过继承自object类来定义的,而经典类类(也称为旧式类)则不继承自任何基类。
Python 3开始,所有的类都默认是新式类,即使它们不显式地继承自object。不过,为了兼容性和明确性,在Python 3中仍然推荐显式地继承自object。

3、多态

(1)多态概念

多态是指同一种行为具有不同的表现形式。
多态的前提:继承、重写
举例:

class Animal(object):
    '''父类:动物类'''
    def shout(self):
        print('动物叫')
class Cat(Animal):
    '''子类1:小猫类'''
    def shout(self):
        print('小猫喵喵喵')
class Dog(Animal):
    '''子类2:小狗类'''
    def shout(self):
        print('小狗汪汪汪')
cat = Cat()
cat.shout()
dog = Dog()
dog.shout()

输出结果
在这里插入图片描述
子类继承了父类,但对某些方法进行了重写(override),不同的子类可以有不同的实现。

(2)多态性

多态性:定义一个统一的接口,一个接口多种实现。也即一种调用方式,会有不同的执行结果。如下:

class Animal(object):
    def eat(self):
        print('我会干饭')
class Pig(Animal):
    def eat(self):
        print('猪吃猪伺料')
class Dog(Animal):
    def eat(self):
        print('狗吃狗粮')
def test(obj):
    obj.eat()
animal = Animal()
test(animal)
pig = Pig()
test(pig)

输出结果
在这里插入图片描述
在这个例子中,test函数并不关心是什么类型的动物,什么动物都有吃的能力,只要有一个eat方法就行。test函数传入不同的对象,就可以执行不同对象的eat方法。

4、静态方法

(1)静态方法的基本概念

静态方法是通过 @staticmethod 装饰器定义的方法。静态方法既不需要传递实例对象,也不需要传递类对象,即没有self,cls参数的限制。静态方法与类无关,可以被转换成函数使用。

(2)静态方法的定义格式

class 类名:
    @staticmethod
    def 方法名(形参):
        方法体

(3)静态方法的调用格式

静态方法既可以使用类访问,也可以使用对象访问

类名.方法名(实参)
对象名.方法名(实参)

举例:

class Person(object):
    @staticmethod      #静态方法
    def study(name):
        print(f'{name}人类会学习')
#静态方法既可以使用对象访问,也可以使用类访问
Person.study('zct')
pe = Person()
pe.study('zct')      #调用方法时传参数

输出结果
在这里插入图片描述

(4)静态方法的使用场景

不需要访问实例对象或实例属性,也不需要访问类属性或类方法或不需要创建实例的时候,可以使用静态方法,这样取消不必要的参数传递,有利于减少不必要的内存占用和性能消耗。

5、类方法

(1)类方法的基本概念

类方法是定义在类中的方法,通过装饰器​​@classmethod​​来标识。它的第一个参数是​​cls​​(表示类本身),而不是实例对象。类方法可以访问类的属性,并且可以在没有实例的情况下被调用。

(2)类方法的定义格式

class 类名:
   @classmethod
   def 方法名(cls,形参):
        方法体

(3)类方法的使用方式

类方法可以类名或实例对象来调用

类名.方法名()
对象名.方法名()

举例:

class Person(object):
    @classmethod
    def sleep(cls):
        print("cls:",cls)     #cls代表类对象本身,类本质上就是一个对象
        print('人类在睡觉')
print(Person)
Person.sleep()   #通过类名调用类方法

输出结果
在这里插入图片描述
由cls和Person打印出来的结果是一样的,也可说明cls代表类对象本身。
另外,类方法内部可以使用cls.类属性名访问类属性,或者调用其他的类方法。如下例:

class Person(object):
    name = 'zct'     #类属性
    @classmethod
    def sleep(cls):
        print(cls.name)    #类方法内部访问类属性name
Person.sleep()

输出结果
在这里插入图片描述

(4)类方法的应用场景

当方法中需要使用到类对象(如访问私有类属性等)时,定义类方法。因此类方法一般是配合类属性使用。

6、实例方法、静态方法、类方法的区别

1、实例方法,第一个参数为self,可以访问实例属性或类属性,必须通过类实例调用。
2、静态方法,使用@staticmethod装饰器修饰,无默认参数,不需要访问实例属性或类属性,因为静态方法与类无关,访问没有意义,但可以通过类名.类属性访问类属性,但无法访问实例属性,可以通过类或实例调用。
3、类方法,使用@classmethod装饰器修饰,第一个参数为cls,只能访问类属性,可以通过类或实例调用。

class Person(object):
    name = 'zct'        #类属性

    def eat(self):          #实例方法
        print(self.food)    #可以访问实例属性
        print(Person.name)  #可以访问类属性,不建议使用self.name,虽然也可以访问到

    @staticmethod              #静态方法
    def sing(song):
        print(f'我在唱{song}')   #不能访问实例属性或类属性

    @classmethod        #静态方法
    def sleep(cls):
        print(cls.name) #只能访问类属性

pe1 = Person()
pe1.food = '香蕉'       #实例属性
pe1.eat()               #通过实例调用

#Person.sing('儿歌')     #通过类调用
pe2 = Person()
pe2.sing('儿歌')         #通过实例调用

#Person.sleep()         #通过类调用
pe3 = Person()
pe3.sleep()             #通过实例调用

7、单例模式

(1)什么是单例模式

单例模式可以理解为一个特殊的类,这个类只存在一个对象。即每次实例化所创建出来的对象都是同一个,内存地址都是同一个。
优点:可以节省内存空间,减少了不必要的资源浪费
缺点:多线程访问时容易引发线程安全问题

(2)new()

①__new__()作用

new()是object基类提供的内置的静态方法。
其作用:1、在内存中为对象分配空间;2、返回对象的引用。

②__new__()与__init__()

一个对象实例化过程:
首先执行__new__(),如果没有写__new__(),默认调用object里面的__new__(),返回一个实例对象, 传给__init__()中的self,然后再去调用__init__(),对这个对象进行初始化。

new()与__init__()区别:
1、new()是创建对象,init()是初始化对象
2、new()是返回对象引用,init()定义实例属性
3、new()是类级别的方法,init()是实例级别的方法

举例:

class Person(object):
    def __new__(cls, *args, **kwargs):
        print("这是new方法")
        res = super().__new__(cls)
        #print("返回值:", res)
        return res
    def __init__(self,name):
        self.name = name      #实例属性
        print("名字是:",self.name)
pe = Person('zct')

输出结果
在这里插入图片描述

(3)单例模式的实现方式

1、通过@classmethod
2、通过装饰器实现
3、通过重写__new__()实现(重点)
4、通过导入模块实现

①通过重写__new__()实现单例模式

设计流程:

1、定义一个类属性,初始值为None,用来记录单例对象的引用(内存地址)
2、重写__new__()方法。注意一定要return super().new(cls), 否则python解释器得不到分配空间的对象引用,就不会调用__init__()。
3、进行判断,如果类属性是None,把__new__()返回的对象引用保存进去
4、返回类属性中记录的对象引用(内存地址)

举例:

class Singleton(object):
    #记录第一个被创建的对象的引用
    obj = None    #类属性
    def __new__(cls, *args, **kwargs):
        print("这是__new__()方法")
        #判断类属性是否为空
        if cls.obj == None:
            cls.obj = super().__new__(cls)
        return cls.obj
    def __init__(self):
        print("我是__init__()")
s = Singleton()
print("s:",s)
s2 = Singleton()
print("s2:",s2)
s3 = Singleton()
print("s3:",s3)

输出结果
在这里插入图片描述
有输出结果可知,s,s2,s3的内存地址一样,即是同一个对象,实现了单例模式。

(4)单例模式的应用场景

1、回收站对象
2、音乐播放器(一个音乐播放软件负责播放的对象只有一个,即同一时间只能播放一首歌曲)
3、开发游戏软件 场景管理器
4、数据库配置、数据库连接池的设计

8、魔法方法和魔法属性

(1)魔法属性

①__doc__

类的描述信息,例如:

class Person(object):
    '''人类---类的描述信息'''    #只能使用多行注释,单行注释无效
    pass
print(Person.__doc__)

输出结果
在这里插入图片描述

②__module__与__class__

__module__表示当前操作对象所在的模块
__class__表示当前操作对象所在的类

'''pytest01.py'''
class B:
    def funa(self):
        print("哈哈哈")    
import pytest01
b = pytest01.B()
print("b:",b)
# b.funa()
print("对象b所在的模块:",b.__module__)   #输出模块
print("对象b所在的类:",b.__class__)    #输出类    

输出结果
在这里插入图片描述

(2)魔法方法

①__str__()

对象的描述信息。如果类中定义了此方法,那么在打印对象时,默认输出该方法的返回值,也就是打印方法中return的数据
注意:str()必须返回一个字符串

class C:
    def __str__(self):
        return "这里是str的返回值"    #这个方法必须要有返回值,并且一定是字符串类型
        # return 123 
c = C()
print(c)

输出结果
在这里插入图片描述

②__call__()

使一个实例对象成为一个可调用对象,就像函数那样可以调用。
可调用对象:函数、内置函数和类都是可调用对象,凡是可以把一对()应用到某个对象身上都可以称为可调用对象。
判断一个对象是否是可调用对象可用callable(),是返回True,否则返回False。

def func():
    print("呵呵呵")
func()
print(callable(func))     #True
name = 'zct'
# name()     #报错
print(callable(name))     #False
class A:
    pass
a = A()
# a()    #报错,实例化对象是不可调用的
print(callable(a))          #False
class A:
    def __call__(self, *args, **kwargs):
        print("这是__call__()")
a = A()
a()       #调用一个可调用的实例对象,其实就是在调用它的__call__()方法
a2 = A()
a2()
print(callable(a))            #True

由上可知,使用了__call__(),使一个实例对象成为一个可调用对象。

9、文件

(1)文件

①什么是文件

文件就是存储在某种长期储存在设备上的一段数据。

②文件操作

文件操作的过程即打开文件—>读、写文件—>关闭文件。

③文件对象的方法

1、open():创建一个file对象,默认以只读模式打开
2、read(n):n表示从文件中读取的数据的长度,没有传n值就默认一次性读取文件的所有内容
3、write():将指定内容写入文件
4、close():关闭文件

④文件名的属性

文件名.name: 返回要打开的文件名,可以包含文件的具体路径
文件名.mode: 返回文件的访问模式
文件名.closed: 检测文件是否关闭,关闭就返回True

# # 1.打开文件
# f = open('test.txt')
# print(f.name)      #返回文件的文件名
# print(f.mode)      #文件访问模式
# print(f.closed)    #False
# # 2.关闭文件
# f.close()
# print(f.closed)    #True

(2)读写操作

①read(n)

n表示从文件中读取的数据的长度,没有传n值或者传的是负值就默认一次性读取文件的所有内容。

f = open("test.txt")
f = open(r'D:\learn-work\Python\test.txt')
# print(f)
print(f.name)       #文件所在的具体路径(绝对路径)
print(f.read())     #一次性把文件内容都读取出来了
# print(f.read(5))  #最多读取5个数据
f.close()

输出结果
在这里插入图片描述

②readline()

一次只读取一行内容(行末的换行符也一并读取进来),方法执行完,会把文件指针移到下一行,准备再次读取。

f = open("test.txt",encoding = 'utf-8')
# print(f.readline())
# print(f.readline())
# print(f.readline())
while True:
    text = f.readline()     #读取一行内容
    # 读不到内容退出循环
    if not text:
        break
    print(text)
f.close()

输出结果
在这里插入图片描述

③readlines()

按照行的方式把文件内容一次性读取,返回的是一个列表,每一行的数据就是列表中的一个元素。

f = open("test.txt",encoding = 'utf-8')
text = f.readlines()
print(text)
print(type(text))
for i in text:
    print(i)
f.close()

(3)访问模式

1、r:只读模式(默认模式),文件必须存在,不存在就会报错
2、w:只写模式,文件存在就会先清空文件内容,再写入添加内容,不存在就创建新文件
3、r+:可读写文件,文件不存在就会报错
4、w+:先写再读,文件存在,就重新编辑文件,不存在就创建新文件

(4)文件定位操作

只要用到tell()和seek()方法。tell()用于显示文件内当前位置,即文件指针当前位置;seek(offset,whence)用于移动文件读取指针到指定位置,其中offset表示偏移量,即表示要移动的字节数,whence:起始位置,表示移动字节的参考位置,默认是0,0代表文件开头作为参考位置,1代表当前位置作为参考位置,2代表将文件结尾作为参考位置。如seek(0,0)就会把文件指针移动到文件开头。

f = open("test.txt",'w+')
f.write('hello Python')
pos = f.tell()
print("当前文件指针所在的位置:",pos)
f.seek(0,0)      #把文件指针移动到文件开头
pos2 = f.tell()
print("移动后所在位置:",pos2)
print(f.read())
f.close()

输出结果
在这里插入图片描述

(5)with open与编码格式

with open作用:代码执行完,系统会自动调用f.close(),可以省略文件关闭步骤。

with open("test.txt",'w') as f:    #f是文件对象
    f.write("ememem...")
    print(f.closed)                #False
print(f.closed)                    #True

由结果可知,在with open 里面打印f.closed是False,说明此时文件是打开状态的,在with open外面打印f.closed是True,说明此时文件已关闭了,不用自己使用f.close()关闭文件了。
使用encoding = 'utf-8’来设置编码格式,windows默认编码格式是gbk。

(6)目录常用操作

目录操作要导入模块import os

1、文件重命名 os.rename(旧名字,新名字)
2、删除文件 os.remove
3、创建文件夹 os.mkdir()
4、删除文件夹 os.rmdir()
5、获取当前目录 os.getcwd()
6、获取目录列表 os.listdir(),其中os.listdir(‘…/’)获取上一级目录列表

10、可迭代对象、迭代器、生成器

(1)可迭代对象

①什么是可迭代对象
可迭代对象Iterable。迭代(遍历),即依次从对象中把一个个元素取出来的过程。数据类型为str、list、tuple、dict、set等一般是可迭代对象。
②可迭代对象的条件
1、对象实现了__iter__()方法
2、iter()方法返回了迭代器对象
③for循环的工作原理
1、先通过__iter__()获取可迭代对象的迭代器
2、对获取到的迭代器不断调用__next__()方法来获取下一个值并将其赋值给临时变量i
④使用isinstance(o,t)判断是否是可迭代对象或者是否是一个已知的数据类型。o是对象,t是数据类型。

print(isinstance(123,Iterable))      #False
print(isinstance(123,int))           #True
print(isinstance('123',(str,int)))   #True

(6)迭代器

①什么是迭代器
迭代器 Iterator。是一个可以记住遍历位置的对象,在上次停留的位置继续做一些事情。
②如何创建迭代器
使用**iter()来获取可迭代对象的迭代器,使用next()**来一个个去取元素,取完元素后会引发一个异常。

li = [1,2,3,4,5]
# 1、创建迭代器对象
li2 = iter(li)
print(li2)
# 2、获取下一条数据
print(next(li2))
print(next(li2))
print(next(li2))
print(next(li2))
print(next(li2))
# 3、取完元素后,再使用next()会引发StopIteration异常
#print(next(li2))         #StopIteration异常

创建迭代器对象,并获取元素步骤:

1、iter()调用对象的__iter__(),并把__iter__()方法的返回结果作为自己的返回值
2、next()调用对象的__next__(),一个个取元素
3、所有元素都取完了,next()将引发StopIteration异常

li = [1,2,3,4,5]
li2 = li.__iter__()
print("li2:",li2)
print(li2.__next__())
print(li2.__next__())
print(li2.__next__())
print(li2.__next__())
print(li2.__next__())
#print(li2.__next__())        #StopIteration异常

③可迭代对象iterable和iterator

凡是可以作用于for循环的都属于可迭代对象
凡是可以作用于next()的都是迭代器

from collections.abc import Iterable,Iterator
name = 'zct'
print(dir(name))
print(isinstance(name,Iterable))      #True
print(isinstance(name,Iterator))      #False

输出结果
在这里插入图片描述
由结果可知,字符串name是可迭代对象,不是迭代器对象;另外由dir(name)打印结果中有__iter__,而没有__next__,也说明了name是可迭代对象,不是迭代器对象。故可迭代对象并不一定是迭代器对象。

name2 = iter(name)      #将name转换成迭代器对象
print(dir(name2))
print(isinstance(name2,Iterable))     #True
print(isinstance(name2,Iterator))     #True

输出结果
在这里插入图片描述
由结果可知,name2经iter()方法转换为迭代器对象,并且由dir(name2)的输出结果可知,既有__iter__,也有__next__,也可以说明是迭代器对象。同时,也可得出迭代器对象一定是可迭代对象。

总结:
1、可迭代对象可以通过iter()方法转换成迭代器对象
2、如果一个对象拥有__iter__(),是可迭代对象,如果一个对象拥有__next__()和__iter__()方法,是迭代器对象
3、dir():查看对象中的属性和方法

④迭代器协议
对象必须提供一个next方法,执行该方法要么就返回迭代中的下一项,要么就引发StopIteration异常,来终止迭代。
⑤自定义迭代器类
迭代器的两个特性: iter()和__next__()

class MyIterator:
    def __init__(self):
        self.num = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.num == 10:
            raise StopIteration("终止迭代,数据已经被取完了")
        self.num += 1
        return self.num
mi = MyIterator()
print(mi)
for i in mi:
    print(i)

输出结果
在这里插入图片描述

(6)生成器

①什么是生成器
生成器 generator。Python中一边循环一边计算的机制,叫做生成器
②生成器表达式
将列表推导式的[]改成()就成了生成器表达式。

gen = (i * 5 for i in range(5))   #列表推导式的[]改成()就成了生成器表达式
print(gen)
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
# print(next(gen))      #StopIteration

输出结果
在这里插入图片描述
③生成器函数
Python中使用了yield关键字的函数就称之为生成器函数。

yield的作用:
1、类似return,将指定值或者多个值返回给调用者
2、yield语句一次返回一个结果,在每个结果中间,挂起函数,执行next(),再重新从挂起点继续往下执行
是函数中断,并保存中间的状态。

def gen():
    print("开始了")
    yield 'a'     #返回一个a,并暂停函数,在此处挂起,下一次再从此处恢复执行
    yield 'b'
    yield 'c'
gen_01 = gen()
print(gen_01)
print(next(gen_01))    
print(next(gen_01))

第一次print(next(gen_01)),输出a,第二次print(next(gen_01)) ,输出b。

def test_a():
    yield 1
    yield 2
    yield 3
print(test_a())
ta = test_a()              
print(next(ta))            #从对象中取值
print(next(ta))
print(next(ta))
print(next(test_a()))      #加括号就是调用函数,会重新调用
print(next(test_a()))
print(next(test_a()))

输出结果
在这里插入图片描述
注意:print(next(ta)) 和print(next(test_a())) 的区别,前者是从对象中取值,后者是函数的调用,每次调用都会有一个新的对象,每个对象都是从1开始取值,故后者打印出来的始终是1。

(7)可迭代对象、迭代器、生成器三者区别

1、可迭代对象:指实现了python迭代协议,可以通过for…in,循环遍历的对象,比如list、dict、str…、迭代器、生成器。
2、迭代器:可以记住自己遍历位置的对象,直观体现就是可以使用next()函数返回值,迭代器只能往前,不能往后,当遍历完毕之后,next()会抛出异常。
3、生成器:特殊的迭代器,需要注意迭代器并不一定是生成器,它是Python提供的通过简便的方法写出迭代器的一种手段。
4、包含关系:可迭代对象 >迭代器>生成器

11、线程

(1)什么是线程

执行一个程序时,默认情况下会创建一个进程,在一个进程里面又会创建一个线程,线程是可以真正工作的单位,而进程是为线程提供资源的单位。程序,进程和线程三者的关系就类似于工厂、车间和工人的关系。

(2)多线程

多线程即同时运行多个线程。
使用import threading,导入线程模块

1、target:执行的任务名
2、args:以元组的形式给任务传参
3、线程.name:获取线程的名字
4、线程.name = 值:更改线程的名字
5、join():暂停的作用,等子线程执行结束后,主线程才会继续执行,必须放在start()后面
6、线程.daemon = True:守护线程,必须放在start()前面。主线程执行结束,子线程也会跟着结束

举例:

def sing(name):
    print(f"{name}在唱歌")
    time.sleep(2)
    print("唱完歌了")
def dance(name2):
    print(f"{name2}在跳舞")
    time.sleep(2)
    print("跳完舞了")
#主程序入口
if __name__ == '__main__':
    # 1、创建子线程
    t1 = threading.Thread(target = sing,args = ('zct',))   #已元组的形式传参
    # print(t1)
    t2 = threading.Thread(target=dance,args = ('zzz',))
    # 3、守护线程,必须放在start()前面:主线程执行结束,子线程也会跟着结束
    t1.daemon = True
    t2.daemon = True
    # 2、开启子线程
    t1.start()
    t2.start()
    # 4、阻塞主线程join():暂停的作用,等子线程执行结束后,主线程才会继续执行,必须放在start()后面
    t1.join()
    t2.join()
    # 5、获取线程名字
    # print(t1.getName())   #已被弃用
    # print(t2.getName())
    print(t1.name)
    print(t2.name)
    # 6、更改线程名
    t1.name = "子线程一"
    t2.name = "子线程二"
    print(t1.name)
    print(t2.name)
    print("完美谢幕,本次表演结束")

输出结果
在这里插入图片描述

(3)线程之间的执行是无序的

线程执行是根据cpu调度决定的。

def task():
    time.sleep(1)
    print("当前线程是:",threading.current_thread().name)   #显示当前线程对象名
if __name__ == '__main__':
    for i in range(5):
        #每循环一次就创建一个子线程
        t = threading.Thread(target = task)
        #启动子线程
        t.start()

输出结果
在这里插入图片描述
每次执行的结果可能不同。

(4)线程之间共享资源(全局变量)

li = []
#写入数据
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(0.2)
    print("写入的数据是:",li)
def rdata():
    print("读取的数据是:",li)
if __name__ == '__main__':
    #创建子线程  线程名 = Thread(target = 函数名,args = (参数1,参数2,...)),线程会执行这个函数,以此完成这件事
    t1 = Thread(target = wdata)
    t2 = Thread(target = rdata)
    #开启子线程(让线程开始工作)
    t1.start()
    #阻塞线程
    t1.join()        #第一种方法:加了join()就会等待t1任务执行结束
    #time.sleep(0.8) #第二种方法:写线程程写四个数据时,读线程就开始读了,因此读线程只读到了四个数据,但写线程还在继续写,直到写完,即写了5个数据
    t2.start()
    # t2.join()

输出结果
在这里插入图片描述

(5)资源竞争

a = 0
b = 1000000
#循环一次就给全局变量a+1
def add():
    for i in range(b):
        global a   #如果在函数内部直接修改全局变量(如 a += 1),必须先用 global 声明该变量为全局变量。如果只是读的话,就不用global
        a += 1
    print("第一次:",a)
def add2():
    for i in range(b):
        global a  # 如果在函数内部直接修改全局变量(如 a += 1),必须先用 global 声明该变量为全局变量。如果只是读的话,就不用global
        a += 1
    print("第二次:", a)
# add()      #第一次: 1000000
# add2()     #第二次: 2000000
if __name__ == '__main__':
    first = Thread(target = add)
    second = Thread(target = add2)
    first.start()
    second.start()

输出结果
在这里插入图片描述
由于存在资源竞争,每次输出的结果都可能不同

(6)线程同步

线程同步有两种实现方式。
①join()

a = 0
b = 1000000
#循环一次就给全局变量a+1
def add():
    for i in range(b):
        global a   #如果在函数内部直接修改全局变量(如 a += 1),必须先用 global 声明该变量为全局变量。如果只是读的话,就不用global
        a += 1
    print("第一次:",a)
def add2():
    for i in range(b):
        global a  # 如果在函数内部直接修改全局变量(如 a += 1),必须先用 global 声明该变量为全局变量。如果只是读的话,就不用global
        a += 1
    print("第二次:", a)
if __name__ == '__main__':
    first = Thread(target = add)
    second = Thread(target = add2)
    first.start()
    first.join()     #等待第一个子线程执行结束完成以后,代码再继续往下执行,开始执行第二个子线程
    second.start()

②互斥锁
对共享数据进行锁定,保证多个线程访问共享数据不会出现数据错误问题:保证同一时刻只能有一个线程去操作。
acquire() 加锁,release() 解锁,这两个方法必须要成对出现,否则容易形成死锁。死锁即一直等待对方释放锁的情景,会造成应用程序停止响应,不再处理其他任务。

a = 0
b = 1000000
# 1、创建互斥锁
lock = Lock()
def add():
    lock.acquire()    #加锁
    for i in range(b):
        global a   
        a += 1
    print("第一次:",a)
    lock.release()    #解锁
def add2():
    lock.acquire()  # 加锁
    for i in range(b):
        global a 
        a += 1
    print("第二次:", a)
    lock.release()  # 解锁
if __name__ == '__main__':
    first = Thread(target = add)
    second = Thread(target = add2)
    first.start()
    second.start()

输出结果
在这里插入图片描述
注意:互斥锁是多个线程一起去抢,抢到锁的线程先执行。
总结:

1、互斥锁作用:保证同一时刻只有一个线程去操作共享数据,保证共享数据不会出现错误问题
2、上锁和释放锁必须成对出现,否则容易造成死锁现象
3、互斥锁的缺点:会影响代码的执行效率

12、进程

(1)进程

①含义
进程是操作系统进行资源分配和调度的基本单位,是操作系统结构的基础;
一个正在运行的程序或者软件就是一个进程,程序跑起来就成了进程。
注意:进程里面可以创建多个线程,多进程也可以完成多任务。
②进程的状态

1、就绪状态:运行的条件都已经满足,正在等待cpu执行
2、执行状态:cpu正在执行其功能
3、等待(阻塞)状态:等待某些条件满足,如一个程序sleep了,此时就是等待状态

举例

# print("我是zct")           #程序执行,处于执行状态
# sex = input("输入性别:")    #光标闪动,等待用户输入,处于等待状态
# print(sex)                #执行状态
# time.sleep(1)             #延迟1秒,等待(阻塞)状态

(2)进程的语法结构

multiprocessing模块提供了Process类代表进程对象
①Process 类参数

1、target:执行的目标任务名,即子进程要执行的任务
2、args:以元组的形式传参
3、kwargs:以字典的形式传参

②方法

1、start():开启子进程
2、is alive():判断子进程是否还活着,存活就返回True,死亡就返回False
3、join():主进程等待子进程执行结束

③属性

1、name :当前进程的别名,默认Process-N
2、pid:当前进程的进程编号

def sing():
    # os.getpid():获取当前进程编号
    # os.getppid():获取当前父进程编号
    print(f"sing子进程编号:{os.getpid()},父进程pid:{os.getppid()}")    #父进程的pid就是py文件主进程的pid
    print("唱歌")
def dance():
    print(f"dance子进程编号:{os.getpid()},父进程pid:{os.getppid()}")
    print("跳舞")
if __name__ == '__main__':
    # 创建子进程
    # 修改子进程名第一种方式
    p1 = Process(target = sing,name = "子进程一")
    p2 = Process(target = dance,name = "子进程二")
    # 开启
    p1.start()
    p2.start()
    # 修改子进程名第二种方式
    p1.name = "子进程1"
    p2.name = "子进程2"
    # 访问name属性
    print("p1:",p1.name)
    print("p2:",p2.name)
    # 查看子进程的进程编号
    print("p1.pid:",p1.pid)
    print("p2.pid:",p2.pid)
    print(f"主进程pid:{os.getpid()},主进程的父进程pid:{os.getppid()}")
    # cmd命令提示符窗口输入tasklist就可以查看电脑里面进程的命令
    # Ctrl + F 查找
    # pycharm64软件进程编号就是主进程的父进程编号
def eat(name):
    print(f"{name}在干饭")
def sleep(name):
    print(f"{name}在睡觉")
if __name__ == '__main__':
    p1 = Process(target = eat,args = ("zct",))  #元组只有一个元素时,必须要加逗号
    p2 = Process(target= sleep, args=("zzz",))
    p1.start()
    p1.join()      #主进程处于等待状态,等待p1执行完毕,p1是运行状态
    p2.start()
    print("p1存活状态:",p1.is_alive())   #False,此时p1已执行完,p2开始启动了
    print("p2存活状态:",p2.is_alive())   #True

输出结果
在这里插入图片描述
注意:写在主进程中判断存活状态的时候需要加入join阻塞一下。

④进程间不共享全局变量

li = []
def wdata():
    for i in range(5):
        li.append(i)
        time.sleep(0.2)
    print("写入的数据是:",li)
def rdata():
    print("读取的数据是:",li)
# 1、防止别人导入文件的时候执行了main里面的方法
# 2、防止windows系统递归创建子进程
if __name__ == '__main__':
    p1 = Process(target = wdata)
    p2 = Process(target = rdata)
    p1.start()
    p1.join()
    p2.start()

输出结果
在这里插入图片描述
读取一直是空的,是因为进程不共享全局变量。

(3)进程间的通信

from queue import Queue或者from multiprocessing import Process,Queue导入Queue模块

q.put():放入数据
q.get():取出数据
q.empty():判断队列是否为空
q.qsize():返回当前队列包含的消息参数
q.full():判断队列是否满了

# 初始化一个队列队形
q = Queue(3)   #最多可以接收三条消息,没写或者是负值就代表没有上限,直到内存的尽头
q.put("爱你到老")
q.put("你在做梦")
print(q.full())
q.put("年轻人不讲武德")
print(q.full())
# q.put("陪你度过余生")
print(q.qsize())
print(q.get())   #获取队列的一条消息,然后将其从队列中移除
print(q.get())
print(q.empty())
print(q.get())
print(q.empty())
print(q.qsize())

输出结果
在这里插入图片描述
进程操作队列

li = ['张三','李四','王麻子','赵六']
def wdata(q1):
    for i in range(5):
        print(f"{i}已经被放入")
        q1.put(i)
        time.sleep(0.2)
    print("写入的数据是:",li)
def rdata(q2):
    while True:
        #判断是否为空,队列为空就退出循环
        if q2.empty():
            break
        else:
            print("取出数据:",q2.get())
    print("读取的数据是:",li)
if __name__ == '__main__':
    # 创建队列对象
    q = Queue()
    p1 = Process(target = wdata,args = (q,))
    p2 = Process(target = rdata,args = (q,))
    p1.start()
    p1.join()    #等待队列中的数据放入完成
    p2.start()

输出结果
在这里插入图片描述

13、协程

(1)协程

协程,单线程下的开发,又称为微线程。线程和进程的操作是由程序触发系统接口,最后的执行者是系统,协程的操作则是程序员。
①简单实现协程

import time
def task1():
    while True:
        yield '哈哈哈'
        time.sleep(1)
def task2():
    while True:
        yield '嘿嘿嘿'
        time.sleep(1)
if __name__ == '__main__':
    t1 = task1()
    t2 = task2()
    while True:
        print(next(t1))
        print(next(t2))

②应用场景

1、如果一个线程里面由I/O操作比较多的时候,就用协程
常见的I/O操作:文件操作、网络请求、
2、适合高并发处理

(2)greenlet

greenlet是一个由C语言实现的协程模块,通过设置switch()来实现任意函数之间的切换。需要安装第三方模块。
注意:greenlet属于手动切换,当遇到I/O操作,程序会阻塞,而不能进行自动切换 通过greenlet实现任务的切换。

from greenlet import greenlet
def sing():
    print("在唱歌")
    print("唱完歌了")
def dance():
    print("在跳舞")
    print("跳完舞了")
# sing()
# dance()

if __name__ == '__main__':
    #创建协程对象  greenlet(任务名)
    g1 = greenlet(sing)
    g2 = greenlet(dance)
    g1.switch()   #切换到g1中去运行
    g2.switch()

输出结果
在这里插入图片描述

(3)greenlet

①遇到I/O操作时,会进行自动切换,属于主动式切换。

gevent.spawn(函数名) :创建协程对象
gevent.sleep(): 耗时操作
gevent.join(): 阻塞,等待某个协程执行结束
gevent.joinall(): 等待所有协程对象都执行结束再退出,参数是一个协程对象列表

②gevent自带耗时操作

def sing():
    print("在唱歌")
    gevent.sleep(2)
    # time.sleep(1)
    print("唱完歌了")
def dance():
    print("在跳舞")
    gevent.sleep(3)
    print("跳完舞了")
if __name__ == '__main__':
    # 1、创建协程对象
    g1 = gevent.spawn(sing)
    g2 = gevent.spawn(dance)
    # 2、阻塞:等待协程执行结束
    g1.join()     #等待g1对象执行结束
    g2.join()

在这里插入图片描述

在输出结果中,”跳完舞了”会比“唱完歌了”晚一秒输出。
③joinall()
等待所有协程对象都执行结束再退出。

def sing(name):
    for i in range(3):
        gevent.sleep(1)
        print(f"{name}在唱歌,被送走的第{i}次")
if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(sing,'zct'),
        gevent.spawn(sing,'xz')
    ])

输出结果
在这里插入图片描述
④monkey补丁
拥有在运行时替换的功能,即将用到的time.sleep()替换成gevent里面自己实现耗时操作的gevent.sleep()代码。

from gevent import monkey
monkey.patch_all()   
def sing(name):
    for i in range(3):
        time.sleep(1)
        print(f"{name}在唱歌,被送走的第{i}次")
if __name__ == '__main__':
    gevent.joinall([
        gevent.spawn(sing,'zct'),
        gevent.spawn(sing,'xz')
    ])

输出结果
在这里插入图片描述
注意:monkey.patch_all()必须放在被打补丁者的前面。

(4)进程、线程、协程总结

1、线程时CPU调度的基本单位,进程是资源分配的基本单位
2、进程、线程、协程对比
进程:切换需要的资源最大,效率最低
线程:切换需要的资源一般,效率一般
协程:切换需要的资源最小,效率高
3、多线程适合IO密集型操作(文件操作、爬虫),多进程适合CPU密集型操作(科学及计算、对视频进行高清解码、计算圆周率等)
4、进程、线程、协程都是可以完成多任务的,可以根据自己实际开发的需要选择使用。

14、正则基础

(1)正则表达式

需要导入re模块
特点:语法比较复杂,可读性较差。通用性较强,适用于多种编程语言。
步骤:

1、导入re模块
2、使用match方法进行匹配操作
re.match()能匹配出以xxx开头的字符串
如果起始位置没有匹配成功,返回None
3、如果上一步数据匹配成功,使用group()提取数据

import re
res = re.match("z","zct")
print(res.group())

(2)匹配单个字符

1 .匹配任意一个字符,除\n以外
2 [ ]匹配[ ]中列举的字符
3 \d匹配数字0-9
4 \D匹配非数字
5\s匹配空白,即空格和tab键
6 \S匹配非空白
7 \w匹配单词字符,即a-z,A-Z,0-9,_,汉字
8\W匹配非单词字符

(3)匹配多个字符

①*匹配前一个字符出现0次或者无限次,即可有可无

res = re.match(r'\w*','zct')
print(res.group())            #zct

②+匹配前一个字符出现1次或者无限次,即至少一次

res = re.match(r'\d+','12地主家的傻儿子')
print(res.group())              #12

③?匹配前一个字符出现1次或者0次

res = re.match(r'\d?','12地主家的傻儿子')
print(res.group())                #1

④{m}匹配前一个字符出现m次

res = re.match(r'\d{2}','123地主家的傻儿子')
print(res.group())                #12

⑤{m,n}匹配前一个字符出现从m次到n次

res = re.match(r'\d{1,3}','1234地主家的傻儿子')
print(res.group())                 #123

(4)匹配开头和结尾

①^:表示以…开头;表示对…取反

res = re.match(r'^py','python')
print(res.group())           #py

注意:^在[]中表示不匹配

res = re.match('[^py]','thon')  #[^py]表示匹配除了p、y之外的字符
print(res.group())             #t

②$:匹配字符串结尾

res = re.match(r'\w{6}$','python')  #[^py]表示匹配除了p、y之外的字符
print(res.group())              #python

(5)匹配分组

①|匹配左右任意一个表达式 (常用)

res = re.match('abc|def','abc')
print(res.group())              #abc

②(ab)将括号中字符作为一个分组(常用)

res = re.match(r'\w*@(163|qq|126).com','123@163.com')
print(res.group())               #123@163.com

③\num 匹配分组num匹配到的字符串 (经常在匹配标签时被使用)

res = re.match(r'<(\w*)>\w*</\1>','<html>login</html>')
print(res.group())                #<html>login</html>
res = re.match(r'<(\w*)><(\w*)>.*</\2></\1>','<html><body>login</body></html>')
print(res.group())                #<html><body>login</body></html>

④ (?P=name) 引用别名为name分组匹配到的字符串

#(?P<name>)分组起别名
# res = re.match(r'<(?P<L1>\w*)><(?P<L2>\w*)>.*</(?P=L2)></(?P=L1)>','<html><body>login</body></html>')
# print(res.group())

匹配网址 例子:

li = ['www.baidu.com','www.python.org','http.jd.cn','www.py.en','www.abc.cn']
# res = re.match(r'www.\w*.(com|cn|org)','www.baidu.com')
# print(res.group())
for i in li:
    res = re.match(r'www.\w*.(com|cn|org)',i)
    if res:
        print(res.group())
    else:
        print(f"{i}这个网址有错误")

输出结果
在这里插入图片描述

(6)高级用法

①search()
扫描整个字符串并返回第一个成功匹配的对象,如果匹配失败,就返回None。

# res = re.match('th','python')         #报错
res = re.search('th','python')
print(res.group())                      #th

②findall()
从头到尾匹配,找到所有匹配成功的数据,返回一个列表。

res = re.findall('th','python')
print(res,type(res))                    #['th'] <class 'list'>

总结:
1、match()从头开始匹配,匹配成功返回match对象,通过group()进行提取,匹配失败就返回None,只匹配一次
2、search()从头到尾匹配,匹配成功返回第一个成功匹配的对象,通过group()进行提取,匹配失败返回None,只匹配一次
3、findall()从头到尾匹配,匹配成功返回一个列表,匹配所有匹配成功的数据,不需要通过group()进行提取

③sub()
re.sub(pattern,rep1,string,count)
pattern 正则表达式(代表需要被替换的,也就是字符串里面的旧内容)
rep1 新内容
string 字符串
count 指定替换的次数

res = re.sub(r'\d','2','这是这个月的第30天',count=1)
print(res)                       #这是这个月的第20天

④split
split(pattern,string,maxsplit)
pattern 正则表达式
string 字符串
maxsplit 指定最大分割次数

res = re.split(',','hello,python,haha,hehe')   #没有设置次数就默认全部分割
print(res)                            #['hello', 'python', 'haha', 'hehe']

(7)贪婪与非贪婪

# 1、贪婪匹配(默认):在满足匹配时,匹配尽可能长的字符串
res = re.match('em*','emmmmmm...')
print(res.group())    #emmmmmm
# 2、非贪婪匹配:在满足匹配时,匹配尽可能短的字符串,使用?来表示非贪婪匹配
res = re.match('em*?','emmmmmm...')
print(res.group())    #e

(8)原生字符串

r取消转译。
正则表达式中,匹配字符串中的字符\需要\\,加入原生字符串,\代表\

res = re.match(r'\\\\',r'\\\game')
print(res.group())   #\\

15、模块

(1)os模块

import os
作用:用于和操作系统交互

1、os.name ,指示正在使用的工作平台(返回操作系统类型)
2、os.getenv(环境变量名称) ,读取环境变量
3、os.path.split() ,把目录名和文件名分离,以元组的形式接收,第一个元素是目录路径,第二个元素是文件名
4、os.path.dirname ,显示split分割的第一个元素,即目录
5、os.path.basename ,显示split分割的第二个元素,即文件名
6、os.path.exists() ,判断路径(文件或目录)是否存在,存在的话就返回True,不存在就返回False
7、os.path.isfile() ,判断是否存在文件
8、os.path.isdir() ,判断目录是否存在
9、os.path.abspath() ,获取当前路径下的绝对路径
10、os.path.isabs() ,判断是否是绝对路径

(2)sys模块

导入sys模块:import sys
作用:负责程序跟python解释器交互

1、sys.getdefaultencoding() ,获取系统默认编码格式
2、sys.path ,获取环境变量的路径,跟解释器相关
3、sys.platform ,获取操作系统平台名称
4、sys.version ,获取python解释器的版本信息

(3)time模块

导入time模块: import time
三种时间表示:

1、时间戳(timestamp)
2、格式化的时间字符串(format time)
3、时间元组(strut_time)

1、time.sleep(),延时操作,以秒为单位
2、time.time() ,获取到当前的时间戳:以秒计算,从1970年1月1日 00:00:00开始到现在的时间差
3、time.localtime(),将一个时间戳转换为当前时区的struct_time,九个元素
4、time.asctime(), 获取系统当前时间,把struct_time换成固定的字符串表达式
5、time.ctime() ,获取系统当前时间,把时间戳换成固定的字符串表达式
6、time.strftime(格式化字符串,struct time) ,将struct_time转换成时间字符串
7、time.strptime(时间字符串,格式化字符串),将时间字符串转换成struct_time

(4)logging模块

①导入logging模块:import logging
logging模块作用:用于记录日志信息
②日志的作用
1、程序调试
2、了解软件程序运行情况是否正常
3、软件程序运行故障分析与问题定位
③级别排序(从高到低)
CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTEST

logging.debug("我是debug")
logging.info("我是info")
logging.warning("我是warning")
logging.error("我是error")
logging.critical("我是critical")

在这里插入图片描述
logging默认的level就是warning,也就是说Logging只会显示级别大于等于warning的日志信息,如上图结果所示。
④logging.basicConfig() 配置root logger的参数

1、filename:指定日志文件的文件名,所有会显示的日志都会存放到这个文件中去
2、filemode:文件的打开方式,默认是a,追加模式
3、level:指定日志显示级别,默认是警告信息warning
4、format:指定日志信息的输出格式

logging.basicConfig(filename = 'log.log',filemode = 'w',level = logging.NOTSET,format = '%(levelname)s:%(asctime)s\t%(message)s')
logging.debug("debug")
logging.info("info")
logging.warning("warning")
logging.error("error")
logging.critical("critical")

log.log文件中
在这里插入图片描述

(5)random模块

导入random模块:import random
作用:用于生成各种分布的伪随机数生成器,可以根据不同的实数分布来随机生成值

1、random.random() 产生大于0且小于1之间的小数
2、random.uniform() 产生指定范围的随机小数
3、random.randint() 产生指定范围的随机整数,包括开头和结尾
4、random.randrange(start,stop,[step]) 产生start,stop范围内的随机整数,包含开头,不包含结尾

print(random.random())
print(random.uniform(1,3))
print(random.randint(1,3))
print(random.randrange(2,7,2))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值