目录
在C++中我们学习了面向对象的编程思想,引入了类和对象的概念,我们将具有相同属性和行为的对象进行抽象形成了类,而对象是依据类产生的一个实例。
类:
1.类是同一类对象的属性和行为的抽象和总结
2.类描述同一类对象应包含的数据
3.类描述同一类对象的行为特征
4.类是抽象的,是一个概念模型
5.一个类可以找到多个对象
6.类是对象的模板,对象是该类的实例
类的定义:
class 类名:
类的属性
类的方法(用 def声明)
- class Hero:
- # 魔法方法,类似于构造函数,用来初始化对象,在定义类对象的时候系统自动调用该函数
- def __init__(self, name, skill, perm):
- # 成员函数中必须通过self点号访问其他成员
- # 设置该类的成员属性,并且赋初值
- self.name = name
- self.skill = skill
- self.perm = perm
-
- def __del__(self):
- print("销毁释放")
-
- def info(self):
- print("英雄【{}】使用了【{}】技能,造成了【{}%】的伤害".format(self.name, self.skill, self.perm))
-
- # __name__魔法方法,表示指定该文件中的代码入口位置
- # 用于当前文件的测试代码段,在其他.py文件中,是不会访问到该代码段
- if __name__ == "__main__":
- # 定义初始化类对象
- luban = Hero("鲁班", "大炮", 30)
- luban.info()
'运行
魔法方法:
1.在python中以两个下划线开头的方法叫做魔法方法,比如__init__,__str__等。
2.魔法方法在类或对象的某些事件触发后会自动执行,当然你也可以根据自己的需求来进行重写。
3.在定义类方法时,除了魔法方法,建议不要以双下划线__为前缀
下面我把列出一些常见的魔法方法:
self
类对象访问成员,形参self等价于this指针,是不需要给实参的,指向当前调用的类对象,某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可
和C++中的继承概念一样,python中也有继承,子类会获取父类已有的所有属性和成员函数。这里我们就不过多介绍它的语法细节了,在C++的继承那章节我详细介绍过。我们直接来看一段代码进行介绍:
- # 基类
- class Luban:
- def __init__(self, name):
- self.name = name
- self.skill = "嘴炮"
- self.level = 20
-
- def attack(self):
- print("英雄{}使用了最强技能{},有{}%概率暴击".format(self.name, self.skill, self.level))
-
- # 定义一个类,继承于Luban类,单继承
-
- class luban(Luban):
- # pass关键字,通过,不做任何事情,只是代码行的占位
- pass
-
- def __init__(self, name, skill, perm):
- self.name = name
- self.skill = skill
- self.perm = perm
-
- # 定义基类同名函数,基类该函数被隐藏
-
- def attack(self):
- print("英雄{}使用了普通技能{},有{}无敌".format(self.name, self.skill, self.perm))
-
- # 设计一个成员函数,调用基类的成员函数,必须将self子类对象地址传递进去
-
- def oldAttack(self):
-
- # 基类名调用基类的构造函数用来初始化基类部分
- Luban.__init__(self, self.name)
- Luban.attack(self)
-
- if __name__ == "__main__":
- lu7 = luban("鲁班七号", "星空炮", 50)
- lu7.attack()
- lu7.oldAttack()
可以看到父类和子类由同名的函数,这时候子类的同名函数会将继承的父类的同名函数隐藏,所以我们在调用该函数的时候,会调用子类的该函数,如果我们要调用父类的同名函数,我们就需要在子类中再设计一个成员函数,并且使用基类名调用基类的构造函数来初始化基类部分,且需要将self子对象地址传参过去。
来看看运行结果:
既然有单继承,那么肯定也有多重继承,在多继承中,如果出现同名函数情况,在基类初始化顺序中,先初始化的将被后初始化的基类成员覆盖,与继承顺序无关。
- class Luban:
- def __init__(self, name):
- self.name = name
- self.skill = "嘴炮"
- self.level = 20
-
- def attack(self):
- print("英雄{}使用了最强技能{},有{}%概率暴击".format(self.name, self.skill, self.level))
-
-
- class Space:
- def __init__(self, name):
- self.name = name
- self.skill = "火箭嘴炮"
- self.level = 40
-
- def attack(self):
- print("英雄{}使用了普通技能{},有{}%概率无敌".format(self.name, self.skill, self.level))
-
- class luban(Space, Luban):
- def __init__(self):
- Space.__init__(self, "鲁班")
- Luban.__init__(self, "鲁班大师")
-
- if __name__ == "__main__":
- lu7 = luban()
- lu7.attack()
'运行
和C++一样,python的类中也有私有成员这样的属性来使一些成员数据更加安全,私有成员总是在类体中,以__开头,表示不能被对象.号访问,只能通过在类中设计一个成员函数来进行访问。
- class luban:
- def __init__(self):
- # 私有成员总是在类体中,以__开头,表示不能被对象.号访问
- self.__name = "123"
-
- def setValue(self, name):
- self.__name = name
-
- def getValue(self):
- return self.__name
-
-
- if __name__ == "__main__":
- lu7 = luban()
- # 此处不报错,因为在给lu7对象新增一个__name公有成员属性
- # lu7.__name = "1234"
- # 此处会报错
- # print("name: ", lu7.__name)
- print(lu7.getValue())
- lu7.setValue("鲁班")
- print(lu7.getValue())
'运行
当python检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的异常,比如当我们打开了一个不存在的文件。程序停止执行并且提示错误信息这个动作,我们通常称之为:抛出异常,通过异常捕获可以针对突发事件做集中的处理,而保证程序的稳定性和健壮性。
语法格式:
try:
可能会出现异常的语句
except (异常类型1...........)
也可以获得异常的具体内容将其打印出来
except 异常类型 as error
print(error)
- def func(x, y):
- if y == 0:
- # 主动抛出异常
- raise ZeroDivisionError("分母为零")
- return x/y
-
- if __name__ == "__main__":
- x = int(input("x: "))
- y = int(input("y: "))
- try: # 可能抛出异常的代码段
- ret = func(x, y)
- print("ret: ", ret)
- # except (ZeroDivisionError,): # 接收异常,使用元组类型,元组中必须使用逗号表示
- # print("..................")
-
- except (ZeroDivisionError, ) as fd: # as表示将异常信息保存在后面的变量中
- print("*******************", fd)
'运行
在设置类成员变量时,虽然写来很简单,但是没办法检查参数,且对外开放,任何人都可随意修改变量不安全,但是我们调用方法对私有成员进行访问又太复杂,那么我们就可以使用property。下面我们来看看代码
@property:说明下面的函数可以像成员变量一样.号访问,不需要()调用
@ .setter:说明下面的函数可以像成员变量一样,=号赋值,而不需要调用传参
- class luban:
- def __init__(self):
- # 私有成员总是在类体中,以__开头,表示不能被对象.号访问
- self.__name = "123"
-
- # 说明下面的函数可以像成员变量一样.号访问,不需要()调用
- @property
- def Value(self):
- return self.__name
-
- # 说明下面的函数可以像成员变量一样,=号赋值,而不需要调用传参
- @Value.setter
- def Value(self, name):
- self.__name = name
-
- @property
- def getValue(self):
- return self.__name
-
- # 错误,在此代码之前的类体中没有对setValue函数做过定义,因此@ *.setter只能是前面定义过的函数名
- # @setValue.setter错误
- @getValue.setter # 正确
- def setValue(self, name):
- self.__name = name
-
- if __name__ == "__main__":
- lu7 = luban()
- # 相当于调用Value()函数,返回成员变量的内容
- print(lu7.Value)
- lu7.Value = "鲁班" # 相当于调用Value("鲁班") 函数,设置私有成员的内容
- print(lu7.Value)
- print(lu7.getValue)
- lu7.setValue = "鲁班大师"
- print(lu7.getValue)
-
-
open()方法用于打开一个文件,并返回文件对象,在对文件进行处理过程都需要使用到这个函数,若文件无法打开,会抛出OSError
常规打开文件,使用 with open ... as fd, 打开文件,保存open返回值到fd中
- with open("file.txt", "r+", encoding="utf-8") as fd:
- print("open")
- str1 = input("请输入:")
- # 将 str1中的数据写入到文件中
- fd.write(str1)
-
- list1 = ["\none\n", "teo\n", "three\n"]
- # 一次操作一行数据,参数要求是个列表类型,列表中元素必须是str类型
- fd.writelines(list1)
-
- # 指定从文件中读取相应字节的数据,返回读取到的数据
- str2 = fd.read(23)
- print(str2)
-
- # seek函数以某个基准点将文件指针进行偏移
- fd.seek(0, SEEK_SET)
- # readline(n)默认读取一行完整数据,n表示读取一行的前n个字节
- str1 = fd.readline(2)
- print(str1)
-
- # readlines(n)默认读取文件所有行的数据
- # n为0或非零: 0表示读取所有行,非零:表示只读取一行,关闭读取所有行数据的能力
- list1 = fd.readlines()
- print(list1)
-
- # 关闭打开的文件
- fd.close()
'运行
我们先回顾一下什么是线程?线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程的全部资源,一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
多线程的优势?
线程在程序中是独立的,并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,他们共享内存,文件,句柄和其他进程应有的状态。并且多线程的程序并发现高。进程在执行过程之中拥有独立的内存单元,而多个线程共享内存,极大提升了程序的运行效率,并且线程之间容易实现通信。
那么在python中我们如何创建线程呢?
通过threading库中的thread类就可以创建线程
- from time import ctime, sleep
- import threading
-
- def music(name):
- for i in range(5):
- print("I was listening to music <{}>,{}".format(name, ctime()))
- sleep(1)
- def study(name):
- for i in range(5):
- print("I was study from {}, {}".format(name, ctime()))
- sleep(1)
-
- if __name__ == "__main__":
- # 通过threading库中的thread类,创建线程,指定线程的执行函数,通过args元组传递参数
- t1 = threading.Thread(target=music, args=("
" , )) - t2 = threading.Thread(target=study, args=("
" , )) - # 启动线程
- t1.start()
- t2.start()
- # 阻塞等待回收资源
- t1.join()
- t2.join()
- print("now time: {}".format(ctime()))
说到多线程那么自然要提到线程间的同步和互斥,为了保护临界资源,我们常常要在多线程编程中使用到线程锁和信号量等来实现同步和互斥。
通过threading库中的lock类即可创建线程锁来达到互斥保护临界资源的效果
- from time import ctime, sleep
- import threading
-
- def music(name, lock):
- while True:
- with lock: # with lock:添加自动锁,使用前加锁用完后解锁
- print("I was listening to music <{}>,{}".format(name, ctime()))
- sleep(1)
- def study(name, lock):
- while True:
- with lock:
- print("I was study from {}, {}".format(name, ctime()))
- sleep(1)
-
- if __name__ == "__main__":
- # 定义1把线程锁
- lock = threading.Lock()
- # 通过threading库中的thread类,创建你线程,指定线程的执行函数,通过args元组传递参数
- t1 = threading.Thread(target=music, args=("
" , lock)) - t2 = threading.Thread(target=study, args=("
" , lock)) - # 启动线程
- t1.start()
- t2.start()
- # 阻塞等待回收资源
- t1.join()
- t2.join()
- print("now time: {}".format(ctime()))
'运行
多线程无法利用多核优势,如果想要充分使用多核CPU的资源,就需要使用到多进程,python提供了multiprocessing模块,与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限该进程内,也就是相对独立。
通过库中的Process类就可以创建进程,并且指定进程的执行函数
同样也有进程锁,multiprocessing.lock()
- from time import ctime, sleep
-
- # 导入进程类
- from multiprocessing import Process
- def music(name):
- for i in range(5):
- print("I was listening to music <{}>,{}".format(name, ctime()))
- sleep(1)
- def study(name):
- for i in range(5):
- print("I was study from {}, {}".format(name, ctime()))
- sleep(1)
-
- if __name__ == "__main__":
- # 通过库中的process类,创建多进程,指定进程的执行函数,通过args元组传递参数
- t1 = Process(target=music, args=("
" , )) - t2 = Process(target=study, args=("
" , )) - # 启动进程
- t1.start()
- t2.start()
- # 阻塞等待回收资源
- t1.join()
- t2.join()
- print("now time: {}".format(ctime()))
pipe()返回两个连接对象代表pipe的读写两端,每个连接对象都有send()方法和recv()方法。但是如果两个进程或线程对象同时读取或写入管道两端的数据时,管道中的数据有可能会所坏,所以只能同时各使用读端写端。
- from time import ctime, sleep
- from multiprocessing import Process, Pipe
-
- def music(name, wfd):
- for i in range(5):
- # send()函数可以将任意类型的数据发送到读端
- wfd.send("ssssssssss")
-
- def study(name, rfd):
- for i in range(5):
- # recv()函数从读端读取数据,读端无数据就阻塞等待
- str1 = rfd.recv()
- print("recv: ", str1)
-
- if __name__ == "__main__":
- # 创建管道,得到读端和写端
- rfd, wfd = Pipe()
- t1 = Process(target=music, args=("
" , wfd)) - t2 = Process(target=study, args=("
" , rfd)) - t1.start()
- t2.start()
- t1.join()
- t2.join()
- print("now time: {}".format(ctime()))
'运行
在内存中开辟一个队列模型,用来存放消息,任何拥有队列对象的进程都可以进行消息的存取和取出。
- from time import ctime, sleep
- from multiprocessing import Process, Queue
-
- def music(name, q):
- for i in range(5):
- sleep(2)
- q.put("hello")
- q.put([1, 2, 3, 4])
-
-
- def study(name, q):
- for i in range(5):
- str1 = q.get()
- print("recv: ", str1)
-
-
- if __name__ == "__main__":
- # 创建消息队列
- q = Queue()
- t1 = Process(target=music, args=("
" , q)) - t2 = Process(target=study, args=("
" , q)) - t1.start()
- t2.start()
- t1.join()
- t2.join()
- print("now time: {}".format(ctime()))
'运行
有两种方法:value将一个值存放在内存中;array:将多个数据存放在内存中但要求数据类型一致
- from multiprocessing import Process,Value,Array
- from time import sleep
- def funV(mem):
- mem.Value = 200 # 修改共享内存值
- def funC(mem, n):
- for i in range(n):
- mem[i] = i #修改共享内存值
-
- if __name__ == "__main__":
- mem0 = Value('i', 100)
- mem1 = Array('i', 6)
- p1 = Process(target=funV, args=(mem0,))
- p1.start()
- p2 = Process(target=funC, args=(mem1, 6))
- p2.start()
- sleep(2)
- print(mem0.value)
- for i in mem1:
- print(i)
- p1.join()
- p2.join()
网络通信架构主要有下面两种:
C/S架构:
client/server,典型的两层架构,整个过程就是客户端服务器架构,客户端包含一个或多个运行在用户的计算机程序上,服务器有两个分别是数据库服务器和Socket服务器,数据库服务器主要是通过数据库连接客户端访问服务器端数据,Socket服务器是用于与客户端通信的
B/S架构:
Browser与Server,是浏览器/服务器架构,浏览器指的是web浏览器主要处理少数业务逻辑,服务器的主要作用是处理业务逻辑,由三层架构组成,它的使用简单不需要安装直接可在web浏览器中运行,此架构的客户端包含的逻辑少
通信端点与套接字
在服务器响应客户端的请求之前,双方需要进行一系列的准备工作,首先要创建一个通信端点,服务器通过该通信端点监听客户端的请求,我们常用的一种通信端点为套接字,在通信开始之前,网络应用程序都需要创建套接字。
在socket通信中我们通过主机-端口对找到通信的对象,通过url或ip地址,我们可以在互联网中找到特定的主机,主机的不同的端口号有不同的通信作用,只要确定主机和端口号,就可在网络中确认唯一一个通信端点(端口号范围0-65535,小于1024的端口预留给了系统)
在之前的高级编程中我们已经讲过套接字编程的主要流程,这里也就不再赘述,用一张图简单概括一下:
socket套接字接口总结:
和我们在c高级编程中学习的步骤一样,在python中更加简化了一些操作,但总体步骤还是一样,首先将socket模块引入,随后创建套接字,绑定端口ip,监听,等待连接收发消息,这里我们就简单将tcp服务器的代码示例展示出来,tcp客户端,udp服务器和客户端的代码这里就不再进行展示了。直接将代码图片贴在下方
- import socket
-
- # 创建基于IPV4协议的tcp套接字,定义socket类型的对象
- sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
-
- # 设置端口重用
- sockfd.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
-
- # 绑定本地ip和端口
- sockfd.bind(("0.0.0.0", 9522))
-
- # 设置监听套接字
- sockfd.listen(5)
-
- while True:
- print("wait for a client")
- # 阻塞等待,接受客户端连接
- connfd, addr = sockfd.accept()
- print("client: ", addr)
- while True:
- # recv() 返回bytes类型
- str1 = connfd.recv(12)
- print("recv: ", str1)
- str2 = "hello"
- # send():将数据发送出去,参数必须为bytes,所以要对str进行编码->bytes
- connfd.send(str2.encode())
- if str1.decode() == "quit":
- break
'运行
上面的编程流程还是过于复杂了一点,于是python给我们提供了一个简化后的高级模块,我们只需要从这个模板中引入相应的类就可以直接跳过上面创建,绑定,监听等操作,只需要继承对应的类然后重写handler方法,当客户端连接成功后,系统便自动调用该函数处理客户端请求
- # 从socketserver模块导入Tcpserver服务器类,StreamRequestHandler处理客户端请求的类
- from socketserver import TCPServer, StreamRequestHandler, ThreadingTCPServer
-
- # 该封装好的TCP服务器处理客户端请求,必须由StreamRequsetHandler类中的handler方法进行处理
- class MyRequest(StreamRequestHandler):
- # 重写基类的handler方法,当客户端连接成功,系统会自动调用该函数处理客户端请求
- # 当handler函数处理完客户端的数据请求,会自动断开与客户端的连接
- def handle(self) -> None:
- while True:
- buf = self.request.recv(1024)
- print(buf)
- if buf.decode() == "quit":
- break
-
- if __name__ == "__main__":
- # 创建Tcpsever对象,就是在创建tcp的服务器,指定服务器地址和处理请求的类
- # server = TCPServer(("0.0.0.0", 8888), MyRequest)
- # 创建基于TCP的多线程服务器
- server = ThreadingTCPServer(("127.0.0.1", 8888), MyRequest)
- print("server init success")
- # 让服务器永久阻塞等待新的客户端连接,当客户端的请求处理完成,会自动断开与客户端的连接
- server.serve_forever()
上面我们介绍的都是C/S架构下的,下面我们简单介绍一下B/S架构下的服务器编程
- # 从http包中的server模块中导入,httpserver服务器类和CGIHTTPRequesthandler处理浏览器请求类
- from http.server import HTTPServer, CGIHTTPRequestHandler
- # 如果要定制自己的网页http服务器,那么必须设计类继承于CGIHTTPRequesthandler,重写run_cpi
-
- if __name__ == "__main__":
- http = HTTPServer(("", 8888), CGIHTTPRequestHandler)
- print("starting: ", str(http.server_port))
- http.serve_forever()
串口通信是指外设和计算机间,通过数据信号线,地线,控制线等,按位进行传输数据的一种通信方式,这种方式使用的数据线少,在远距离通信中可以节约通信成本,但是传输速度比并行传输低,是一种非常通用的设备通信协议,python中的pyserial模块封装了python对串口的访问。
- import serial.tools.list_ports
- import serial
-
-
- def findValiable():
- ports = list(serial.tools.list_ports.comports())
- if len(ports) == 0:
- print("系统无可用串口")
- else:
- for i in ports:
- print(ports)
-
-
- if __name__ == "__main__":
- # findValiable()
- # 创建串口对象,设置串口协议
- s = serial.Serial(port="COM1", baudrate=115200)
- while True:
- # 指定读取数据,那么只有数据满足读取的字节数,read函数才能返回
- list1 = s.read(20)
- print("recv: ", list1)
- # readlines(0):读取0行,表示不读数据,参数不为0,就可以读取所有数据
- # list1 = s.readlines(1)
- #for i in list1:
- # print(i.decode(), end="")
- # buf = input("请输入:")
- # s.write(buf.encode())
- # s.flush()