一、背景
上一篇整理了 Python 中的模块、包、文件操作与异常处理。
这一篇继续整理 Python 基础学习中剩下的几个部分:
- 面向对象
- 线程
- MySQL 操作
- JSON 编码与解码
- Socket 网络编程
对应的练习代码主要包括:
56类和对象.py
57构造方法.py
58self关键字.py
59类方法静态方法实例方法.py
60类属性和实例属性.py
61内存管理与析构方法.py
62继承.py
63接口和抽象类.py
64类和实例的内建函数.py
65组合关系.py
66对象序列话与反序列化.py
67访问控制.py
68进程、线程、thread模块.py
69threading模块.py
70线程同步.py
71MySQL相关操作.py
72JSON.py
网络编程/
这篇内容跨度比较大,所以不把所有代码逐行展开,而是按照后续实际使用频率,把常用概念和常见写法整理出来。
二、类和对象
类是模板,对象是类创建出来的具体实例。
例如定义一个简单的类:
class Animal:
def eat(self):
print("i can eat something...")
def drink(self):
print("i can drink something...")
animal = Animal()
animal.eat()
animal.drink()
这里 Animal 是类,animal 是对象。
类中定义的函数叫方法,方法的第一个参数通常写成 self。
三、self 是什么
self 表示当前对象本身。
class Cat:
def __init__(self, name, age):
self.name = name
self.age = age
def say(self):
print(self.name, self.age)
cat = Cat("mimi", 2)
cat.say()
调用 cat.say() 时,Python 会自动把 cat 这个对象传给 self。
所以实例方法的第一个参数必须接收当前对象,一般约定写成 self。
虽然技术上可以写成其他名字,但不建议。
四、构造方法
构造方法是 __init__(),对象创建时会自动调用。
class Person:
def __init__(self, name):
self.name = name
tom = Person("tom")
print(tom.name)
__init__() 常用于初始化实例属性。
需要注意:Python 不支持传统意义上的方法重载。如果在同一个类里重复定义 __init__(),后面的定义会覆盖前面的定义。
五、类属性和实例属性
类属性定义在类中、方法外。
实例属性通常定义在 __init__() 中。
class Person:
category = "human" # 类属性
def __init__(self, name):
self.name = name # 实例属性
print(Person.category)
p = Person("xiaoming")
print(p.name)
print(p.category)
区别如下:
| 类型 | 定义位置 | 访问方式 | 常见用途 |
|---|---|---|---|
| 类属性 | 类中、方法外 | 类名或实例对象 | 所有实例共享的数据 |
| 实例属性 | 通常在 __init__() 中 |
实例对象 | 每个对象自己的数据 |
如果数据属于每个对象自己,应该放到实例属性中。
六、实例方法、类方法和静态方法
Python 类中常见三种方法。
1. 实例方法
实例方法第一个参数是 self。
class Cat:
def say(self, name):
print("miaomiao", name)
cat = Cat()
cat.say("xiaoming")
实例方法可以访问实例属性和类属性。
2. 类方法
类方法使用 @classmethod,第一个参数是 cls。
class Dog:
category = "dog"
@classmethod
def say(cls, name):
print(cls.category, name)
Dog.say("wangcai")
类方法常用于访问或修改类级别的数据。
3. 静态方法
静态方法使用 @staticmethod。
class MathUtil:
@staticmethod
def add(x, y):
return x + y
print(MathUtil.add(1, 2))
静态方法更像放在类命名空间中的普通函数,不会自动接收 self 或 cls。
七、继承
继承表示一个类基于另一个类扩展。
class Person:
def speak(self):
print("hello")
class Student(Person):
def study(self):
print("study")
stu = Student()
stu.speak()
stu.study()
Student 继承了 Person,所以可以使用 Person 中的方法。
八、方法覆盖和 super
子类可以定义和父类同名的方法,这叫方法覆盖。
class Person:
def speak(self):
print("hello person")
class Student(Person):
def speak(self):
print("hello student")
stu = Student()
stu.speak()
如果子类中还想调用父类方法,可以使用 super()。
class Person:
def speak(self):
print("hello person")
class Student(Person):
def speak(self):
super().speak()
print("hello student")
stu = Student()
stu.speak()
super() 在继承中非常常用,尤其是子类初始化时调用父类 __init__()。
九、抽象类
Python 可以使用 abc 模块定义抽象类。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def run(self):
pass
class Dog(Animal):
def run(self):
print("dog run")
dog = Dog()
dog.run()
抽象类不能直接实例化,子类必须实现抽象方法。
Python 没有 Java 那种 interface 关键字。通常如果一个类只定义方法规范,不做具体实现,可以把它理解成接口风格的抽象类。
十、继承和组合
类之间常见两种关系:继承和组合。
继承表示 is a。
例如:学生是人。
class Person:
pass
class Student(Person):
pass
组合表示 has a。
例如:电脑有 CPU。
class CPU:
pass
class Computer:
def __init__(self, cpu):
self.cpu = cpu
computer = Computer(CPU())
如果一个类是另一个类的一种,可以考虑继承。如果一个类只是拥有另一个对象,通常使用组合。
十一、常用内置函数
面向对象中有几个常见内置函数。
| 函数 | 作用 |
|---|---|
issubclass() |
判断一个类是否是另一个类的子类 |
isinstance() |
判断一个对象是否是某个类的实例 |
hasattr() |
判断对象是否有某个属性 |
getattr() |
获取对象属性 |
setattr() |
设置对象属性 |
delattr() |
删除对象属性 |
dir() |
查看对象可用属性和方法 |
vars() |
查看对象的 __dict__ |
示例:
class Person:
pass
class Student(Person):
pass
stu = Student()
print(issubclass(Student, Person)) # True
print(isinstance(stu, Student)) # True
setattr(stu, "name", "xiaoming")
print(getattr(stu, "name"))
print(hasattr(stu, "name"))
十二、访问控制
Python 中没有非常严格的访问控制,更多是命名约定。
| 写法 | 说明 |
|---|---|
name |
普通属性 |
_name |
受保护属性,约定内部使用 |
__name |
名称改写,外部不建议直接访问 |
示例:
class Person:
name = "xiaoming"
_age = 18
__gender = "男"
print(Person.name)
print(Person._age)
# print(Person.__gender) # AttributeError
双下划线属性不是绝对私有,Python 会做名称改写。
print(Person._Person__gender)
不过这只是语言机制,不建议业务代码这样访问。
十三、对象序列化
对象序列化就是把对象保存成可持久化的数据。
Python 中可以使用 pickle。
import pickle
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("xiaoming", 18)
with open("person.data", "wb") as f:
pickle.dump(p, f)
with open("person.data", "rb") as f:
p2 = pickle.load(f)
print(p2.name, p2.age)
需要注意:pickle 不适合加载不可信来源的数据,因为反序列化可能带来安全风险。
另外,Python3 中不需要再写 cPickle,直接使用 pickle 即可。
十四、进程和线程
进程是操作系统分配资源的基本单位。
线程是进程内部的执行线路。
简单理解:
- 一个程序运行起来后,通常就是一个进程
- 一个进程中可以有多个线程
- 多个线程可以在同一个进程中并发执行任务
例如 Web 服务同时处理多个请求,就可能使用多线程或其他并发模型。
十五、threading 模块
Python3 中更推荐使用 threading 模块,而不是底层 _thread 模块。
示例:
import threading
from time import sleep, ctime
def func1():
for i in range(5):
print("func1:", i)
sleep(1)
def func2():
for i in range(5):
print("func2:", i)
sleep(1)
print("start:", ctime())
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
print("end:", ctime())
这里需要注意:
target=func1传的是函数对象,不要写成target=func1()start()用于启动线程join()用于等待线程执行结束
十六、继承 Thread
也可以继承 threading.Thread。
import threading
class MyThread(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print("thread run:", self.name)
t = MyThread("t1")
t.start()
t.join()
这种方式适合线程内部逻辑比较复杂的情况。
如果只是简单执行一个函数,直接使用 threading.Thread(target=...) 更简洁。
十七、线程锁
多个线程同时修改共享数据时,可能出现数据不一致。
这时可以使用锁。
import threading
count = 0
lock = threading.Lock()
def add():
global count
for _ in range(10000):
with lock:
count += 1
threads = [threading.Thread(target=add) for _ in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(count)
with lock: 会自动获取锁和释放锁,比手动 acquire()、release() 更稳。
不过 Python 还有 GIL 的概念,所以多线程不一定适合 CPU 密集型任务。对于 I/O 等待类任务,多线程仍然有实际意义。
十八、使用 PyMySQL 操作 MySQL
Python 可以通过 pymysql 操作 MySQL。
安装:
pip install pymysql
连接配置示例:
import pymysql
import pymysql.cursors
config = {
"host": "127.0.0.1",
"port": 3306,
"user": "root",
"password": "root",
"database": "test",
"charset": "utf8mb4",
"cursorclass": pymysql.cursors.DictCursor,
}
查询示例:
conn = pymysql.connect(**config)
try:
with conn.cursor() as cursor:
cursor.execute("select id, name from user where id = %s", (1,))
row = cursor.fetchone()
print(row)
finally:
conn.close()
增删改需要提交事务:
conn = pymysql.connect(**config)
try:
with conn.cursor() as cursor:
cursor.execute(
"insert into user(id, name) values(%s, %s)",
(1, "xiaoming"),
)
conn.commit()
except Exception as e:
conn.rollback()
print(e)
finally:
conn.close()
这里重点记住:
- 查询使用
fetchone()、fetchmany()、fetchall() - 增删改执行后需要
commit() - 出错时要
rollback() - SQL 参数不要使用字符串拼接,使用参数化传参
- 连接和游标用完要关闭
十九、JSON 编码和解码
JSON 是一种轻量级数据交换格式。
Python 中使用 json 模块处理 JSON。
import json
1. Python 对象转 JSON 字符串
import json
data = [
{"a": 1, "b": 2, "c": 3}
]
text = json.dumps(data, ensure_ascii=False)
print(text)
如果想格式化输出:
print(json.dumps(data, ensure_ascii=False, indent=4, sort_keys=True))
2. JSON 字符串转 Python 对象
json_text = '{"a": 1, "b": 2}'
data = json.loads(json_text)
print(data)
print(type(data))
3. 类型对应关系
| Python | JSON |
|---|---|
dict |
object |
list、tuple |
array |
str |
string |
int、float |
number |
True |
true |
False |
false |
None |
null |
实际使用时,如果内容中有中文,通常加上 ensure_ascii=False。
二十、Socket 网络编程
Socket 可以理解为网络通信的端点。
Python 中可以使用 socket 模块写 TCP 或 UDP 程序。
1. TCP 服务端
from socket import socket, AF_INET, SOCK_STREAM
from time import ctime
HOST = "127.0.0.1"
PORT = 1983
BUFFER = 1024
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)
print("等待连接")
client_socket, address = server_socket.accept()
print("连接来自:", address)
while True:
data = client_socket.recv(BUFFER).decode("utf-8")
if not data:
break
message = f"[{ctime()}]>>> {data}"
client_socket.send(message.encode("utf-8"))
client_socket.close()
server_socket.close()
2. TCP 客户端
from socket import socket, AF_INET, SOCK_STREAM
HOST = "127.0.0.1"
PORT = 1983
BUFFER = 1024
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect((HOST, PORT))
while True:
data = input("> ")
if not data:
break
client_socket.send(data.encode("utf-8"))
response = client_socket.recv(BUFFER).decode("utf-8")
print(response)
client_socket.close()
TCP 是面向连接的,客户端需要先连接服务端。
二十一、UDP 示例
UDP 不需要建立连接,直接发送数据。
1. UDP 服务端
from socket import socket, AF_INET, SOCK_DGRAM
from time import ctime
HOST = "127.0.0.1"
PORT = 1983
BUFFER = 1024
server_socket = socket(AF_INET, SOCK_DGRAM)
server_socket.bind((HOST, PORT))
while True:
data, address = server_socket.recvfrom(BUFFER)
text = data.decode("utf-8")
print("收到消息:", text)
response = f"[{ctime()}]>>> {text}"
server_socket.sendto(response.encode("utf-8"), address)
2. UDP 客户端
from socket import socket, AF_INET, SOCK_DGRAM
HOST = "127.0.0.1"
PORT = 1983
BUFFER = 1024
client_socket = socket(AF_INET, SOCK_DGRAM)
while True:
data = input("> ")
if not data:
break
client_socket.sendto(data.encode("utf-8"), (HOST, PORT))
response, address = client_socket.recvfrom(BUFFER)
print(response.decode("utf-8"))
print(address)
client_socket.close()
TCP 和 UDP 的简单区别:
| 协议 | 特点 | 常见场景 |
|---|---|---|
| TCP | 面向连接、可靠传输 | Web、数据库连接、文件传输 |
| UDP | 无连接、开销小、不保证可靠 | DNS、音视频、广播类场景 |
二十二、socketserver
socketserver 是标准库中对 Socket 服务端的进一步封装,可以减少一些模板代码。
from socketserver import TCPServer, StreamRequestHandler
from time import ctime
HOST = "127.0.0.1"
PORT = 1984
class MyRequestHandler(StreamRequestHandler):
def handle(self):
print("连接来自:", self.client_address)
data = self.rfile.readline().strip().decode("utf-8")
response = f"[{ctime()}] {data}"
self.wfile.write(response.encode("utf-8"))
server = TCPServer((HOST, PORT), MyRequestHandler)
print("等待连接")
server.serve_forever()
学习阶段直接使用 socket 更容易理解底层过程。真正写服务端程序时,通常还会使用更高层的框架。
二十三、总结
这一篇把 Python 基础中相对靠后的几个部分做了整理。
需要重点记住:
- 类是模板,对象是实例
self表示当前对象__init__()用于初始化对象- 继承表示
is a,组合表示has a - 多线程推荐使用
threading模块 - 共享数据需要考虑线程锁
- 使用 PyMySQL 操作数据库时,增删改要
commit(),异常时要rollback() - JSON 处理核心函数是
json.dumps()和json.loads() - TCP 面向连接,UDP 无连接
socketserver可以简化服务端代码
到这里,Python 基础学习笔记已经从基本语法、容器、流程控制、函数、模块文件,整理到了面向对象和常见标准库使用。后续如果继续深入,可以再单独展开 Web、爬虫、数据处理或并发编程。