类的定义和使用

类的定义语法格式如下:

1
2
3
4
class ClassName:
类的属性,即定义在类中的变量(成员变量

类的行为,即定义在类中的函数(成员方法)

类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。

创建类对象的语法:对象 = 类名称()


成员变量和成员方法

类中定义的属性(变量),称之为:成员变量。
类中定义的行为(函数),称之为:成员方法。

在类的内部,使用def关键字来定义一个方法,与定义在类外的一般函数不同,类方法必须包含参数self,且为第一个参数,self代表的是类的实例。但是self不是python关键字,把它换成a也是可以正常执行的。

1
2
3
4
5
6
7
8
9
10
class 类名称:
成员变量

def 成员方法(self ,参数列表):
成员方法体


对象 = 类名称()
对象.成员变量 = 值
对象.成员方法(参数列表)

self的作用:

  • 表示类对象本身的意思
  • 只有通过self,成员方法才能访问类的成员变量
  • self出现在形参列表中,但是不占用参数位置,无需理会

构造方法

Python类可以使用__init__()方法,称之为构造方法。
可以实现:

  • 在创建类对象(构造类)的时候,会自动执行。
  • 在创建类对象(构造类)的时候,将传入参数自动传递给__init__方法使用,借此特性可以给成员变量赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
# name = None # 可以省略
# age = None

def __init__(self, name, age, phone):
self.name = name
self.age = age
self.phone = phone

def say(a):
print(f"大家好,我叫{a.name},今年{a.age}岁。")

student = Student('张三', 23, 110)
student.say() # 大家好,我叫张三,今年23岁。

构造方法注意事项:

  • 构造方法名称:__init__
  • 构造方法也是成员方法,在参数列表中需提供self
  • 在构造方法内定义成员变量,需要使用self,这是因为变量是定义在构造方法内部,如果要成为成员变量,需要用self来表示。

常用的内置方法

__init__构造方法,是Python类内置的方法之一。
这些双下划线__包起来的内置的类方法,各自有各自特殊的功能,这些内置方法可称之为:魔术方法

str字符串方法

当打印类对象或类对象被转换为字符串时,会输出对象的内存地址,如<__main__.Student object at 0x00000158C73BFC90>
内存地址没有多大作用,可通过__str__方法,控制印类对象或类转换为字符串的行为。

  • 方法名:__str__
  • 返回值:字符串
  • 内容:自行定义
1
2
3
4
5
6
7
8
9
10
11
12
13
class Student:
def __init__(self, name, age, phone):
self.name = name
self.age = age
self.phone = phone

def __str__(self):
return f"Student对象的name属性值{self.name},age属性值{self.age}"


student = Student('张三', 23, 110)
print(student) # Student对象的name属性值张三,age属性值23
print(str(student)) # Student对象的name属性值张三,age属性值23

lt小于符号比较方法

直接对两个对象进行比较是不可以的,但可在类中实现__lt__方法,即可同时完成小于符号和大于符号两种比较。

  • 方法名:__lt__
  • 传入参数:other,另一个类对象
  • 返回值:True 或 False
  • 内容:自行定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student:
def __init__(self, name, age, phone):
self.name = name
self.age = age
self.phone = phone

def __lt__(self, other):
return self.age < other.age


student1 = Student('张三', 23, 110)
student2 = Student('李四', 24, 120)

print(student1 < student2) # True
print(student1 > student2) # False

le小于等于比较符号方法

__le__可用于:小于等于符号和大于等于符号两种比较运算符上。

  • 方法名:__le__
  • 传入参数:other,另一个类对象
  • 返回值:True 或 False
  • 内容:自行定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student:
def __init__(self, name, age, phone):
self.name = name
self.age = age
self.phone = phone

def __le__(self, other):
return self.age <= other.age


student1 = Student('张三', 23, 110)
student2 = Student('李四', 24, 120)

print(student1 <= student2) # True
print(student1 >= student2) # False

eq比较运算符实现方法

不实现__eq__方法,对象之间可以比较,但是是比较内存地址,也即是:对象 == 另一个对象比较一定是False结果。

实现了__eq__方法,就可以按照自行定义的内容来决定两个对象是否相等。

  • 方法名:__eq__
  • 传入参数:other,另一个类对象
  • 返回值:True 或 False
  • 内容:自行定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
def __init__(self, name, age, phone):
self.name = name
self.age = age
self.phone = phone

def __eq__(self, other):
return self.age == other.age


student1 = Student('张三', 23, 110)
student2 = Student('李四', 24, 120)

print(student1 == student2) # False

封装

把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
封装是面向对象的特征之一,是对象和类概念的主要特性。

私有成员

定义私有成员:

  • 私有成员变量:变量名以__(2个下划线)开头。
  • 私有成员方法:方法名以__(2个下划线)开头。

使用私有成员:

  • 私有变量无法赋值,也无法获取值。
  • 私有方法无法直接被类对象使用。
  • 私有成员无法被类对象使用,但是可以被其它的成员使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import random
class Student:
# 私有成员变量
__luck = None

def __init__(self, name):
self.name = name

def today(self):
print(f"{self.name}的今天幸运值为{self.__check_luck()}")

# 私有成员方法
def __check_luck(self):
self.__luck = random.randint(1, 100)
return self.__luck

student1 = Student('张三', 23, 110)
student1.today() # 张三的今天幸运值为6

继承

继承是指可以让某个类型的对象获得另一个类型的对象的成员变量和成员方法(不含私有)。
继承可使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。
子类构建的类对象,可以有自己的成员变量和成员方法,也可使用父类的成员变量和成员方法。

继承分为:单继承和多继承

  • 单继承:一个类继承另一个类
  • 多继承:一个类继承多个类,按照顺序从左向右依次继承
  • 多继承中,如果父类有同名方法或属性,先继承的优先级高于后继承,即先继承的保留,后继承的被覆盖

继承的语句如下:

1
2
3
4
5
6
7
8
9
10
class CPU:
producer = "amd"
def run(self):
print("cpu工作中")

class Disk:
pass

class Computer(CPU, Disk):
pass

pass关键字是占位语句,用来保证函数(方法)或类定义的完整性,表示无内容,空的意思。

复写和使用父类成员

子类继承父类的成员属性和成员方法后,如果对其“不满意”,那么可以进行复写,即在子类中重新定义同名的属性或方法即可。
一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员,如果需要使用被复写的父类的成员,需要特殊的调用方式。
只能在子类内调用父类的同名成员,子类的类对象直接调用会调用子类复写的成员。

  • 使用父类名调用父类成员
    • 使用成员变量:父类名.成员变量
    • 使用成员方法:父类名.成员方法(self)
  • 使用super()调用父类成员
    • 使用成员变量:super().成员变量
    • 使用成员方法:super().成员方法()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CPU:
producer = "amd"
def run(self):
print("cpu工作中")

class Memory:
producer = "intel"
size = "16G"

class Disk:
pass

class Computer(CPU, Memory, Disk):
producer = "laptop"
def run(self):
print(f"父类品牌是:{Memory.producer}") # 父类品牌是:intel
CPU.run(self) # cpu工作中
print(f"父类品牌是:{super().producer}") # 父类品牌是:amd
super().run() # cpu工作中

computer = Computer()
computer.run()

类型注解

Python在3.5版本的时候引入了类型注解,用于在代码中涉及数据交互之时,对数据类型进行显式的说明。
类型注解只是提示性的,并非决定性的。数据类型和注解类型无法对应也不会导致错误。

主要功能:

  • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示
  • 帮助开发者自身对变量进行类型注释

支持:

  • 变量的类型注解
  • 函数(方法)形参列表和返回值的类型注解

变量的类型注解

显式的变量定义,一般无需注解,因为就算不写注解,也明确的知晓变量的类型。
一般,无法直接看出变量类型之时会添加变量的类型注解。

  1. 语法1: 变量: 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 基础数据类型注解
var_1: int = 10
var_2: float = 3.14
var_3: str = "Hello"
# 类对象类型注解
class Student:
pass

stu: Student = Student()
# 基础容器类型注解
my_list: list = ["1", 2]
my_tuple: tuple = ("1", 2)
my_dict: dict = {"key": 2}
# 容器类型详细注解
my_list: list[int] = [1, 2]
my_tuple: tuple[str, int] = ("1", 2) # 元组类型设置类型详细注解,需将每个元素都标记出来
my_dict: dict[str, int] = {"key": 2} # 字典类型设置类型详细注解,需两个类型,第一个是key第二个是value
  1. 语法2: 在注释中,# type: 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 基础数据类型注解
var_1 = 10 # type: int
var_2 = 3.14 # type: float
var_3 = "Hello" # type: str
# 类对象类型注解
class Student:
def fun_1(self):
print("方法执行")

def fun_2(self):
return 1

s = Student() # type: Student
var_f1 = s.fun_1() # type: None
var_f2 = s.fun_2() # type: int

函数(方法)形参的类型注解

函数和方法的形参类型注解语法如下:

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

函数(方法)返回值的类型注解

函数和方法的返回值类型注解语法如下:

1
2
def 函数方法名(形参名: 类型, 形参名: 类型,..., 形参名: 类型)->返回值类型:
方法体

联合类型注解

使用Union[类型, ......, 类型],可以定义联合类型注解。

Union的使用方式:

  • 导包:from typing import Union
  • 使用:Union[类型, ......, 类型]
1
2
3
4
5
my_list: list[Union[int, str]] = ['0', 1, 2, '3', '4']
my_tuple: tuple[Union[int, str]] = ("1", 2, '2')
my_dict: dict[str, Union[int, str]] = {"id": 2, 'password': 'abcd'}
def func(data: Union[int,str]) -> Union[int, str]:
pass

多态

多态指的是:多种状态,即完成同一个行为,使用不同的对象获得不同的状态。
如,定义函数(方法),通过类型注解声明需要父类对象,实际传入子类对象进行工作,从而获得不同的工作状态。

多态常作用在继承关系上。
比如:

  • 函数(方法)形参声明接收父类对象
  • 实际传入父类的子类对象进行工作

即:以父类做定义声明,以子类做实际工作,用以获得同一行为,不同状态。

抽象类(接口)

父类用来确定有哪些方法,具体的方法实现由子类自行决定,这种写法,就叫做抽象类(也可以称之为接口)。
包含抽象方法的类,称之为抽象类。
抽象方法是指:没有具体实现的方法(pass)称之为抽象方法。

抽象类的作用:

  • 多用于做顶层设计(设计标准),以便子类做具体实现。
  • 也是对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法并配合多态使用,获得不同的工作状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Phone:
def talk(self):
pass

class Harmony(Phone):
def talk(self):
print("我用鸿蒙打电话")

class IOS(Phone):
def talk(self):
print("我要苹果打电话")

def talk(phone: Phone):
phone.talk()

harmony = Harmony
harmony.talk(harmony)
ios = IOS
ios.talk(ios)

闭包

定义双层嵌套函数, 内层函数可以访问外层函数的变量
将内存函数作为外层函数的返回,此内层函数就是闭包函数

1
2
3
4
5
6
7
8
9
10
11
12
13
# 外部函数
def outer(args1):
# 內部函数
def inner(args2):
nonlocal args1
args1 += 1
print(f"{args1}--{args2}") # 2--参数2

return inner # 返回内部函数


f1 = outer(1) # f1为返回的inner方法
f1("参数2")

闭包的优点:

  • 不定义全局变量,也可以让函数持续访问和修改一个外部变量
  • 闭包函数引用的外部变量,是外层函数的内部变量。作用域封闭难以被误操作修改

闭包的缺点:

  • 由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存

nonlocal关键字的作用:

在闭包函数(内部函数中)想要修改外部函数的变量值,需要用nonlocal声明该外部变量


装饰器

装饰器就是使用创建一个闭包函数,在闭包函数内调用目标函数。
可以达到不改动目标函数的同时,增加额外的功能。

装饰器的一般写法(闭包写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def sleep():
import random
import time
randint = random.randint(1, 5)
print(f"睡{randint}秒")
time.sleep(randint)

def outer(func) :
def inner():
print("睡前")
func()
print("睡后")

return inner

f = outer(sleep)
f()

装饰器的语法糖写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def outer1(func) :
def inner():
print("睡前")
func()
print("睡后")

return inner

@outer1
def sleep():
import random
import time
randint = random.randint(1, 5)
print(f"睡{randint}秒")
time.sleep(randint)

sleep()

设计模式

设计模式就是一种编程套路,使用特定的套路得到特定的效果。

单例设计模式

单例模式的主要目的是确保某一个类只有一个实例存在。

  • 节省内存
  • 节省创建对象的开销

在一个文件中定义

1
2
3
4
class StrTools:
pass

str_tool = StrTools()

另一个文件中使用

1
2
3
4
5
from test import str_tool
s1 = str_tool
s2 = str_tool
print(s1) # <test.StrTools object at 0x000001F06612F810>
print(s2) # <test.StrTools object at 0x000001F06612F810>

工厂模式

将对象的创建由使用原生类本身创建转换到由特定的工厂方法来创建

优点:

  • 大批量创建对象的时候有统一的入口,易于代码维护
  • 当发生修改,仅修改工厂类的创建方法即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person:
pass
class Man(Person):
pass
class Woman(Person):
pass

class Factory:
def get_person(self,p_type):
if p_type=='w':
return Woman()
else:
return Man()


factory = Factory()
w = factory.get_person('w')
h = factory.get_person('h')
print(w) # <__main__.Woman object at 0x00000239DDECD490>
print(h) # <__main__.Man object at 0x00000239DDECD610>

多线程

进程: 一个程序,运行在系统之上,则称该程序为一个运行进程,并分配进程ID方便系统管理。
线程:线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位。
操作系统中可以运行多个进程,即多任务运行,而一个进程内可以运行多个线程,即多线程运行。

进程之间是内存隔离的, 即不同的进程拥有各自的内存空间。
线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的。

并行执行指的是同一时间做不同的工作。
多个进程同时在运行,即不同的程序同时运行,称之为:多任务并行执行
一个进程内的多个线程同时在运行,称之为:多线程并行执行

Python的多线程可以通过threading模块来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import threading
from time import sleep

def sing():
# 获取当前线程
print("当前线程为", threading.current_thread()) # 当前线程为 <_MainThread(MainThread, started 12120)>
for i in range(3):
print("唱歌")
sleep(1)

def dance(msg):
print("当前线程为", threading.current_thread()) # 当前线程为 <_MainThread(MainThread, started 12120)>
for i in range(3):
print(f"{msg}在跳舞")
sleep(1)

"""
group:暂时无用,未来功能的预置参数
target:执行的目标任务名
args:以元组的形式给执行任务传参
kwargs:以字典形式给执行任务传参
name:线程名,一般不用设置
"""
thread1_1 = threading.Thread(target=dance, args=("张三",))
thread1_2 = threading.Thread(target=dance, kwargs={"msg": "李四"})
thread2 = threading.Thread(group=None, target=sing, name=2, args=(), kwargs=None, daemon=None)
# 启动线程
thread1_1.start()
thread1_2.start()
thread2.start()

网络编程

socket(套接字)是进程之间通信一个工具,进程之间想要进行网络通信需要socket。

Socket服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import socket
# 创建socket对象
server = socket.socket()
# 绑定socket_server到指定地址(host,port),以元组(host,port)的形式表示地址
server.bind(('localhost', 8088))
# 服务端开始监听端口,backlog为int,表示允许连接的数量,超出的会等待
server.listen(3)
# 接收客户端连接,获得连接对象,返回元组信息(sock,addr)
sock, addr = server.accept()
print(f"接收到的消息来自:{addr}")
# 客户端连接后,通过recv方法,接收客户端发送的消息
while True:
# recv参数为字节,返回值为字节数组Bytes,可通过decode使用UTF-8编码转为字符串
data = sock.recv(1024).decode("UTF-8")
print(f"接收到的消息是:{data}")
if data == 'bye':
sock.close()
break
# 通过conn(客户端当次连接对象),调用send方法可以回复消息
sock.send(input("输入:").encode("UTF-8"))

# 关闭连接
sock.close()
server.close()

Socket客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket
# 创建socket对象
client = socket.socket()
# 连接服务端到指定地址(host,port),以元组(host,port)的形式表示地址
client.connect(('localhost', 8088))
# 发送消息
while True:
data = input("输入:").encode("UTF-8")
if data == 'bye':
client.close()
break
client.send(data)
recv = client.recv(1024).decode("UTF-8")
print(f"接收到服务端消息是:{recv}")

# 关闭连接
client.close()

正则表达式

正则表达式,又称规则表达式(Regular Expression),是使用单个字符串来描述、匹配某个句法规则的字符串,常被用来检索、替换那些符合某个模式(规则)的文本。

基本匹配

Python正则表达式,使用re模块,并基于re模块中三个基础方法来做正则匹配。
分别是:matchsearchfindall三个基础方法。

  • re.match(匹配规则, 被匹配字符串,标志位)
    • 从被匹配字符串开头进行匹配, 匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空None。
  • search(匹配规则, 被匹配字符串,标志位)
    • 搜索整个字符串,找出匹配的。从前向后,找到第一个后,就停止,不会继续向后,整个字符串都找不到,返回None
  • findall(匹配规则, 被匹配字符串,标志位)
    • 匹配整个字符串,找出全部匹配项,找不到返回空list: []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re

str = "hello world!!!"
result1 = re.match("hello world", str, re.I or re.L)
print(result1) # <re.Match object; span=(0, 11), match='hello world'>
print(result1.span()) # (0, 11)
print(result1.group()) # hello world

result2 = re.search("o", str)
print(result2) # <re.Match object; span=(4, 5), match='o'>
print(result2.span()) # (4, 5)
print(result2.group()) # o

result3 = re.findall("l", str)
print(result3) # ['l', 'l', 'l']

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位or(|) 来指定。

标志位修饰符 描述
re.I 使匹配对大小写不敏感
re.L 做本地化识别(locale-aware)匹配
re.M 多行匹配,影响^和$
re.S 使.匹配包括换行在内的所有字符
re.U 根据Unicode字符集解析字符。这个标志影响\w,\W,\b,\B.

元字符匹配

单字符匹配:

字符 功能
. 匹配任意字符,除了换行符
[…] 用来表示一组字符
[^…] 不在[]中的字符
\d 匹配任意数字,等价于 [0-9]
\D 匹配任意非数字
\s 匹配任意空白字符,等价于 [\t\n\r\f]
\S 匹配任意非空字符
\w 匹配数字字母下划线
\W 匹配非数字字母下划线

数量匹配:

字符 功能
* 匹配0个或多个的表达式
+ 匹配1个或多个的表达式
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n} 匹配n个前面表达式
{n,} 精确匹配n个前面表达式
{n, m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式

边界匹配:

字符 功能
^ 匹配字符串的开头
$ 匹配字符串的末尾
\b 匹配一个单词边界,指单词和空格间的位置
\B 匹配非单词边界

分组匹配:

字符 功能
a|b 匹配a或b任意一个表达式
() 匹配括号内的表达式,也表示一个组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import re

# 匹配账户 只能有数字或者英文字母组成 长度6-16位
rule = '^[0-9a-zA-z]{6,16}$'
str = "1234abcd"
result = re.match(rule, str)
print("匹配结果", result)

# 匹配QQ号 要求5~10位数字 第一位不是0
rule = '^[1-9][0-9]{4,9}$' # 第一位数字1-9,剩余数字0-9并且长度是9
str = "12345"
result = re.match(rule, str)
print("匹配结果", result)

# 匹配邮箱地址 5~10位数字 + @ + qq或者163 + .com
rule = '^[0-9]{5,10}[@](qq|163){1}(.com){1}$'
str = "1234567890@qq.com"
result = re.match(rule, str)
print("匹配结果", result)

递归

递归: 即方法(函数)自己调用自己的一种特殊编程写法。

注意:

  • 注意退出的条件,否则容易变成无限递归
  • 注意返回值的传递,确保从最内层,层层传递到最外层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os

def test_os():
"""演示os模块的3个基础方法"""
print(os.listdir("D:/test")) # 列出路径下的内容
# print(os.path.isdir("D:/test/a")) # 判断指定路径是不是文件夹
# print(os.path.exists("D:/test")) # 判断指定路径是否存在

def get_files_recursion_from_dir(path):
"""
从指定的文件夹中使用递归的方式,获取全部的文件列表
:param path: 被判断的文件夹
:return: list,包含全部的文件,如果目录不存在或者无文件就返回一个空list
"""
print(f"当前判断的文件夹是:{path}")
file_list = []
if os.path.exists(path):
for f in os.listdir(path):
new_path = path + "/" + f
if os.path.isdir(new_path):
# 进入到这里,表明这个目录是文件夹不是文件
file_list += get_files_recursion_from_dir(new_path)
else:
file_list.append(new_path)
else:
print(f"指定的目录{path},不存在")
return []

return file_list

if __name__ == '__main__':
print(get_files_recursion_from_dir("D:/app"))