Python 学习笔记 8:面向对象、线程、数据库、JSON 与网络编程


一、背景

上一篇整理了 Python 中的模块、包、文件操作与异常处理。

这一篇继续整理 Python 基础学习中剩下的几个部分:

  1. 面向对象
  2. 线程
  3. MySQL 操作
  4. JSON 编码与解码
  5. 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))

静态方法更像放在类命名空间中的普通函数,不会自动接收 selfcls

七、继承

继承表示一个类基于另一个类扩展。

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 即可。

十四、进程和线程

进程是操作系统分配资源的基本单位。

线程是进程内部的执行线路。

简单理解:

  1. 一个程序运行起来后,通常就是一个进程
  2. 一个进程中可以有多个线程
  3. 多个线程可以在同一个进程中并发执行任务

例如 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())

这里需要注意:

  1. target=func1 传的是函数对象,不要写成 target=func1()
  2. start() 用于启动线程
  3. 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()

这里重点记住:

  1. 查询使用 fetchone()fetchmany()fetchall()
  2. 增删改执行后需要 commit()
  3. 出错时要 rollback()
  4. SQL 参数不要使用字符串拼接,使用参数化传参
  5. 连接和游标用完要关闭

十九、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
listtuple array
str string
intfloat 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 基础中相对靠后的几个部分做了整理。

需要重点记住:

  1. 类是模板,对象是实例
  2. self 表示当前对象
  3. __init__() 用于初始化对象
  4. 继承表示 is a,组合表示 has a
  5. 多线程推荐使用 threading 模块
  6. 共享数据需要考虑线程锁
  7. 使用 PyMySQL 操作数据库时,增删改要 commit(),异常时要 rollback()
  8. JSON 处理核心函数是 json.dumps()json.loads()
  9. TCP 面向连接,UDP 无连接
  10. socketserver 可以简化服务端代码

到这里,Python 基础学习笔记已经从基本语法、容器、流程控制、函数、模块文件,整理到了面向对象和常见标准库使用。后续如果继续深入,可以再单独展开 Web、爬虫、数据处理或并发编程。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录