Python学习

测试中常用Python函数

字符串常用API之join

# 列表转换为字符串
list = ['a', 'b', 'c']
str = ' '.join(list)    # a b c

字符串常用API之split

# 数据切分操作
str = 'a b c'
list = str.split(' ')    # ['a', 'b', 'c']

字符串常用API之replace

# 替换字符串
str = 'a b c'
str1 = str.replace(' ', '|')    # a|b|c

字符串常用API之strip

# 去掉首尾的空格
str = ' a b c '
str1 = str.strip()    # a b c

列表推导式

# 单分支if
list1 = [i for i in range(4) if i%2==0]    # [0, 2]
# 双分支if else
list2 = [i if i%2==0 else 'odd' for i in range(4)]    # [0, 'odd', 2, 'odd']

字典推导式

dc = {'a': 1, 'b': 2, 'c': 3}
d_new = {k : v ** 2 for k,v in dc.items() if v > 1 }    # {'a': 1, 'b': 4, 'c': 9}
# items()方法得到的是字典的键值对元组

定义函数的注意事项

  • pycharm 自动格式化快捷键:ctrl+alt+L
  • 定义空函数
    • 使用 pass 语句占位
    • 写函数注释 comments

为参数设置默认值

  • 定义函数时可以指定形式参数的默认值
  • 指定默认值的形式参数必须放在所有参数的最后,否则会产生语法错误
  • param=default_value:可选,指定参数并且为该参数设置默认值为 default_value
def function_name(..., [param=default_value]):
    [function_body]

可变参数

  • 可变参数也称为不定长参数
  • 传入函数中实际参数可以是任意多个
  • 常见形式
    • *args
    • **kwargs

*args

  • 接收任意多个实际参数,并将其放到一个元组中
  • 使用已经存在的列表元组作为函数的可变参数,可以在列表的名称前加*
def print_language(*args):
    print(args)

print_language("python", "java", "php", "go")

params = ["python", "java", "php", "go"]
print_language(*params)

**kwargs

  • 接收任意多个类似关键字参数一样显式赋值的实际参数,并将其放到一个字典中
  • 使用已经存在字典作为函数的可变参数,可以在字典的名称前加**
def print_info(**kwargs):
    print(kwargs)

print_info(Tom=18, Jim=20, Lily=12)

params = {'Tom':18, 'Jim':20, 'Lily':12}
print_language(**params)

类和对象

类的定义

  • 语法
class 类名(父类名):
    """类的帮助信息"""
    属性
    方法

类名我们一般采用大驼峰命名法,每个单词的首字母都需大写。

# class_def.py

# 类的声明
class Human(object):
    """人类"""

    # 定义属性(类属性)
    message = "这是类属性"


# 通过类访问类属性
print(Human.message)

类的方法

  • 实例方法(最常用)
    • 构造方法(通常用来进行实例化操作)
  • 类方法
  • 静态方法

构造方法与实例化

  • 作用:实例化对象
  • 语法:def __init__(self, 参数列表)—固定名称
  • 访问:类名(参数列表)
# constructor_method.py

class Human:

    # 定义属性(类属性)
    message = "这是类属性"

    # 构造方法(用于构造并直接返回该类的对象)
    def __init__(self, name, age):
        # 实例变量(用self装饰的变量,也叫实例属性)
        self.name = name
        self.age = age
        print("这是构造方法")


# 实例化对象
person = Human("哈利波特", 12)

# 通过实例访问类属性
print(person.message)

# 通过实例访问实例属性
print(person.name)
print(person.age)

实例方法

  • 作用:提供每个类的实例共享的方法
  • 语法:def 方法名(self, 参数列表)
  • 访问:实例.方法名(参数列表)
# instance_method.py
class Human:

    # 实例方法
    def study(self, course):
        print(f"正在学习{course}")

# 实例化
person = Human()    # 由于类里面没有显示地声明__init__方法,因此python默认会提供一个无参的构造方法给我们

# 通过实例访问实例方法
person.study("python")

类方法

  • 作用:可以操作类的详细信息
  • 语法:@classmethod
  • 访问:类名.类方法名(参数列表)
# class_method.py
class Human:

    # 类属性
    population = 0

    # 类方法
    @classmethod    # 首先定义一个实例方法,然后用@classmethod装饰成一个类方法
    def born(cls):
        print("这是类方法")
        cls.population += 1    # 通过cls.可以访问到自身的类 属性


# 通过类名访问类方法
Human.born()
print(Human.population)

注意:

  • 必须使用@classmethod装饰器来声明这是个类方法,而不是一个普通的实例方法
  • 类方法接收一个默认的参数,也是必填的参数cls,该参数指向类本身Human,其中最好写成cls,虽然写其他也不会报错

静态方法

  • @staticmethod

静态方法不能访问类的数据,换句话说,他们不需要访问类的数据,他们没有class这样的关键字,可以自己独立工作,且他们不能设置类的状态和实例状态。

# static_method.py

class Human:

    # 静态方法
    @staticmethod    # 使用@staticmethod将普通实例方法装饰为静态方法后,就不需要self默认参了,即不需要绑定到类class了
    def grow_up():
        print("这是静态方法")


# 通过类名访问静态方法
Human.grow_up()    # 可以在不创建类的情况下直接使用类名访问到静态方法

静态方法是用于创建通用的工具函数,这样就可以把常用函数放在一个类中进行管理。
我们使用类方法和静态方法的时候都是通过类名直接访问的,省去实例化的步骤

作业

学生信息管理系统

Python三大特性:封装、继承、多态

Python三大特性之封装(encap)

封装的概念:

  • 隐藏:属性和实现细节,不允许外部直接访问
  • 暴露:公开方法,实现对内部信息的操作和访问

封装的作用

  • 限制安全的访问和操作,提高数据安全性
  • 可进行数据检查,从而有利于保证对象信息的完整性

封装的实现:隐藏

python本身没什么隐藏的方法,我们可以通过以下技巧来实现。
为了保证类的属性或者方法不被外部任意访问,可以在类的属性或者方法名前加"_“或”__"来限制访问权限

  • 保护属性:_属性名
  • 私有属性:__属性名
    • 被视作 _类名_属性名
class Account:

    # 普通属性
    bank = "BOC"
    # 保护属性(内部属性)
    _username = "Hogwarts"
    # 私有属性
    __password = "888"


# 通过类名访问类属性
print(Account.bank)  # 将会打印 BOC
print(Account._username)  # 将会打印 Hogwarts
print(Account.__password)  # 将会引发 AttributeError

print(Account.__dict__) 

# 实例化
obj = Account()

# 实例访问类属性
print(obj.bank)  # 将会打印 BOC
print(obj._username)  # 将会打印 Hogwarts
print(obj.__username)  # 将会引发AttributeError

打印_username的时候代码代码提示中没有这个属性,说明并不建议做此操作,但是强行使用会有波浪线,不报错也能输出------尽量不要在类外部使用
私有属性相当于给属性匿名隐藏了,外部使用会报错,因为根本找不到
__dict__可以看到类中真正具有哪些属性,因此实际上__password 属性已经被匿名为_Account__password

封装的实现:暴露

获取私有属性值-> getter
修改私有属性值->setter

  • 提供数据访问功能(getter)(通过此方法可以在类外部访问私有属性)
    • 通常称为计算属性
    • 语法:使用@property装饰器
    • 调用:实例.方法名
      使用@property合成的属性叫计算属性,这种属性不真正存储任何的状态,它的值是通过某种算法计算得到的
class Account:
    # 普通属性
    bank = "BOC"
    # 内部属性
    _username = "Hogwarts"
    # 私有属性
    __password = "888"

    @property
    def password(self):
        return self.__password

# 实例化对象
obj = Account()

# 访问实例的私有属性
print(obj.password)  # 将会打印 888

解释一下:因为password必须要通过构造方法来访问,因此需要实例化对象后才能访问,而其他属性还可以通过类直接访问

  • 提供数据操作功能(setter)
    • 语法:使用@计算属性名.setter装饰器
    • 调用:实例.方法名
class Account:
    # 普通属性
    bank = "BOC"
    # 内部属性
    _username = "Hogwarts"
    # 私有属性
    __password = "888"

    @property
    def password(self):
        return self.__password

    @password.setter
    def password(self, value):
        # 增加数据的校验
        if len(value) >= 8:
            self.__password = value
        else:
            print("密码长度最少要有8位!")


# 实例化对象
obj = Account()

# 修改私有属性(满足校验条件)
obj.password = "hogwarts666"  # 修改成功
print(obj.password)  # 将会打印 hogwarts666

# 修改私有属性(不满足校验条件)
obj.password = "123"  # 修改不会生效
print(obj.password)  # 将会打印 888

解释一下:必须要先能够访问私有属性才能修改私有属性,obj.password有赋值就是修改,没赋值就是属性本身的值

Python三大特性之继承

继承的概念

继承

  • 复用父类的公开属性和方法
  • 拓展出新的属性和方法

继承的实现(inheritant)

  • 语法:class 类名(父类列表)
  • 默认父类是object
  • Python支持多继承
# inheritance_demo.py

class Human:
    """人类"""

    # 类属性
    message = "这是Human的类属性"

    # 构造方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        self.age = age

    # 实例方法
    def live(self):
        print("住在地球上")


class Student(Human):
    """学生类"""

    def study(self):
        print("正在学习")


# 实例化子类对象
stu = Student("哈利波特", 12)
# 访问类属性(继承)
print(stu.message)
# 访问实例属性(继承)
print(stu.name, stu.age)
# 访问实例方法(继承)
stu.live()
# 访问实例方法(扩展)
stu.study()

类型检查

  • isinstance(实例名, 类名)
    • 检查对象是否是某个类及其派生类的实例
  • issubclass(类名1, 类名2)
    • 检查类名1是否是类名2的子类
# relation_demo.py

# 人类
class Human:
    pass

# 学生类
class Student(Human):
    pass

# 老师类
class Teacher(Human):
    pass

# 检查实例与类的关系
stu = Student()
print(isinstance(stu, Human))  # 将会打印 True

# 检查类与类的关系
print(issubclass(Student, Human))  # 将会打印 True
print(issubclass(Student, Teacher))  # 将会打印 False

Python三大特性之多态(Polymorphism)

多态的概念

同名方法呈现多种行为

运算符的多态表现

  • +号
    • 加法:数字+数字
    • 拼接:字符串+字符串
    • 合并:列表+列表
# 加法:数字+数字
print(1 + 1)  # 打印 2

# 拼接:字符串+字符串
print("Hello" + "World")  # 打印 Hello World

# 合并:列表+列表
print([1, 2] + [3])  # 打印 [1, 2, 3]

函数的多态表现

  • len()函数
    • 可以接收字符串
    • 可以接收列表
# 参数是字符串
print(len("Hogwarts"))

# 参数是列表
print(len([1, 3, 5]))

函数的多态表现

  • 同名变量调用同名方法呈现多种行为
    (同名变量x调用同名方法speak)
# poly_method.py

class China:
    def speak(self):
        print("汉语")

class Usa:
    def speak(self):
        print("英语")

# 实例化对象
cn = China()
us = Usa()

for x in (cn, us):
    # 同一个变量在调用同名方法时,呈现不同的行为
    # 具体呈现哪种行为,由该变量所引用的对象决定
    x.speak()

多态与继承

  • 方法重写(Override):子类的方法名称与父类的相同
  • 重写构造方法
    • super().init()
    • 父类名.init(self)
      子类方法重写的原则:当子类实例访问父类的同名方法时会优先访问自己的方法
# override_demo.py

class Human:
    """人类"""

    message = "这是Human的类属性"

    # 构造方法
    def __init__(self, name, age):
        # 实例属性
        self.name = name
        self.age = age

    # 实例方法
    def live(self):
        print(f"住在地球上")


class Student(Human):
    """学生类"""

    # 重写父类的构造方法
    def __init__(self, name, age, school):
        # 访问父类的构造方法
        super().__init__(name, age)
        # super(Student, self).__init__(name, age)
        # Human.__init__(self, name, age)
        # 子类实例属性(个性)
        self.school = school

    # 重写父类的实例方法
    def live(self):
        print(f"住在{self.school}")


# 实例化子类对象
stu = Student("哈利波特", 12, "Hogwarts")

# 访问实例方法
stu.live()  # 将会打印 住在Hogwarts

Python中要求:如果子类重写了父类的构造方法,那么子类的构造方法必须调用父类的构造方法
子类构造方法调用父类构造方法的两种方式:

  • super().init(name, age)
  • Human.init(self, name, age)

如果在上面的子类构造方法中重写name和age属性也可以,打印出来将是子类重写后的内容

        self.name = "lucy"
        self.age = "20"

Python模块

Python的程序结构

  • 组成
    • package包
    • module模块
    • function方法
      1691231368267

模块

  • 包含Python定义和语句的文件
  • .py文件
  • 作为脚本运行

模块导入

  • import 模块名
  • from <模块名> import <方法/变量/类>
  • from <模块名> import *(导入模块里的所有)
    • 注意:
      • 同一个模块写多次,只被导入一次
      • import应该放在代码的顶端

模块分类

  • 系统内置模块,如re,sys,json等,直接import就能导入
  • 第三方的开源模块,如numpy,pandas等,在file->settings->Python Interpreter直接安装,pycharm能帮我们完成pip install的操作
  • 自定义模块
    注意:标准用法是每个模块分开导入,不要一次性导入;一个模块中的方法或类可以一次性导入

模块的调用

  • 调用另一个模块的方法
# xfy.py

def xfy_method():
    print("调用xfy_method方法!")

class XfyClass:
    message = "调用XfyClass类!"
# demo.py

from xfy import xfy_method, XfyClass
# from xfy import *
# 调用方法
xfy_method() # 调用xfy_method方法!
# 调用类
print(XfyClass.message) # 调用XfyClass类!

常用方法

  • dir()找出当前模块定义的对象(想知道当前模块有哪些对象可以调用)
  • dir(sys)找出参数模块定义的对象

搜索路径

Python解释器对模块位置的搜索顺序是:

  • 1、包含输入脚本的目录(如果未指定文件,则为当前目录)
  • 2、PYTHONPATH(目录名称列表,语法与shell变量相同PATH)
  • 3、安装的默认路径
    注意:函数名可重复,但是避免与系统重复

Python错误与异常

错误

  • 语法错误(语法:syntax)
  • 逻辑错误
  • 系统错误(内存泄漏等)

异常

  • 程序执行过程中出现的未知错误
  • 语法和逻辑都是正常的
  • 程序业务逻辑不完善引起的程序漏洞(bug)
    如两数相除,除数为0的情况

异常与错误

  • 异常可以被捕获和处理
  • 错误一般是编码错误,逻辑错误,系统错误

常见的异常类型

除零异常、名称异常、索引异常(下标越界)、键异常、值异常、属性异常等

异常捕获与异常处理

try:
    """执行代码"""
except:
    """发生异常时执行的代码"""
"""后面的可选"""
else:
    """没有异常时执行的代码"""
finally:
    """不管有没有异常都会执行的代码"""

finally可以用在文件的操作中,最后关闭文件f.close()的动作放在finally中。如果后面还想执行代码一定要放在finally中。

使用raise抛出异常:(对于开发人员会使用)

def set_age(num):
    if num <= 0 or num > 200:
        raise ValueError
    else:
        print(f"设置的年龄为: {num}")
set_age(-1) # ValueError

也可以在ValueError中加参数打印错误情况

def set_age(num):
    if num <= 0 or num > 200:
        raise ValueError(f"值错误: {num}")
    else:
        print(f"设置的年龄为: {num}")
set_age(-1) # ValueError

自定义异常

class MyError(Exception):
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)

def set_age(num):
    if num <= 0 or num > 200:
        raise MyError(f"值错误: {num}")
    else:
        print(f"设置的年龄为: {num}")
set_age(-1) # __main__.MyError: '值错误: -1'

类型提示

想指定参数是string型,返回的值是string型,但是传入的是int,就会有提示信息,不需要去源码里分析
1691240478602
以及可以联想
1691240748364

类型别名

如果类型需要在别的地方用到

Vector = list[float] # 指定是列表,列表里的每一个元素都是浮点数型的
def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]
print(scale(2.0, [2.0, 3.0, 4.0])) # [4.0, 6.0, 8.0]

注意:类型提示并不具有检查功能,如果scale里传入的参数是字符串,也可以正常运行

设置提示类型

Settings->Edits->Inspections->Python->type check

自定义类型

class Student:
    name: str
    age: int

    def __init__(self):
        self.name = "lucy"
        self.age = 12

def get_stu(name: str)->Student:
    return Student()
print(get_stu("harry").name)  # --> 有相应的提示
print(get_stu("harry").age)  # --> 有相应的提示

静态代码检查功能

首先需要安装第三方工具mypy包
pip install mypy
mypy可以检查代码中的类型,前提是一定要添加类型提示

a : list[int] = []
a = [1, 2, 3]

image

a : list[int] = []
a = [1, 2, "1"]

pycharm中不能使用shell的解决方法

1691242849424

dataclass

dataclass是Python内置的模块
优势:

  • 可读性强
  • 操作灵活
  • 轻量
    应用场景:
  • 创建对象
  • 完美融合平台开发ORM框架

案例

  • 场景:如果创建一只猫,信息包括猫的名字、体重、颜色。同时打印这个对象的时候,希望能打印出一个字符串(包含猫的各种信息)应该如何编写代码
    • 问题:
      • 数据修改不方便
      • 代码冗余
    • 解决方案:
      • 使用自定义类实现数据类
class Cat:
   name: str
   color: str
   weight: int

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

   def __str__(self):
       return f"喵星人姓名:{self.name}, 年龄:{self.weight},颜色:{self.color}"
   # repr方法:返回一个对象的 string 格式
   def __repr__(self):
       return f"===>>>>> 喵星人姓名:{self.name}, 年龄:{self.weight},颜色:{self.color}"


if __name__ == '__main__':
   cat = Cat("大橘", 10, "橘色")
   print(cat) # 喵星人姓名:大橘, 年龄:10,颜色:橘色

数据类更优雅的实现方案

  • 使用dataclass创建数据类
  • 实例化的时候自动生成构造函数(实例化的时候只要传入变量就可以)
from dataclasses import dataclass
# 1. 加装饰器@dataclass
# 2. 为变量添加类型提示
@dataclass
class Cat:
    name: str
    color: str
    weight: int

if __name__ == '__main__':
    cat = Cat("菠萝", "橘猫", 9)
    print(cat) # Cat(name='菠萝', color='橘猫', weight=9)

注意:必须要指定类型,否则不会添加到init方法中

field的使用

作用:指定参数的默认值
如果是不可变类型,那么直接定义和使用filed定义都是一样的

from dataclasses import dataclass, field
# 1. 加装饰器@dataclass
# 2. 为变量添加类型提示
@dataclass
class Cat:
    name: str
    color: str
    # weight: int = 2
    weight: int = field(default=2)
# 错误写法,执行报错
@dataclass
class Cat:

    name: str
    color: str
    weight: int
    children: list=[1,2,3]


# 正确写法,可变类型必须使用field
from dataclasses import dataclass, field

@dataclass
class Cat:
    name: str
    color: str
    weight: int = 2
    # 可变参数 list,dict,需要通过default_factory来指定类型或者默认值
    children: list = field(default_factory=list)

if __name__ == '__main__':
    cat = Cat("菠萝", "橘猫", 9, [1,2,3])

filed常用参数

参数名 参数功能
default 字段的默认值
default_factory 定义可变量参数的默认值,default 和 default_factory 不能同时存在
init 如果为 true(默认值),该字段作为参数包含在生成的 init() 方法中。
repr 如果为 true(默认值),该字段包含在生成的 repr() 方法返回的字符串中。

  • field default 参数
    字段的默认值
import dataclasses
@dataclasses.dataclass
class Cat:
    name: str
    color: str
    weight: str = dataclasses.field(default=5)
    children: list = dataclasses.field(default_factory=list)
    children1: list = dataclasses.field(default_factory=lambda:[1,2,3])
    children2: dict = dataclasses.field(default_factory=lambda: {"name":"喵"})
  • field init 参数
    如果为 True(默认值),该字段作为参数包含在生成的 init() 方法中。
    如果为 False,该字段不会包含 init() 方法参数中。但是前提是要有默认值。
import dataclasses
@dataclasses.dataclass
class Cat:
    name: str
    color: str
    weight: str = dataclasses.field(default=5)
    children: list = dataclasses.field(default_factory=list,init=False)
  • field repr 参数
    如果为 True(默认值),该字段包含在生成的 repr() 方法返回的字符串中。
    如果为 False,该字段不会包含在生成的 repr() 方法返回的字符串中。
import dataclasses
@dataclasses.dataclass
class Cat:
    name: str
    color: str
    weight: str = dataclasses.field(default=5)
    children: list = dataclasses.field(default_factory=list,repr=False)

常用方法

asdict() 转化实例对象为字典格式

from dataclasses import dataclass, field, asdict
@dataclass
class Cat:
    name: str
    color: str
    weight: int = field(default=5)
    # 无参的函数
    children: list = field(default_factory=lambda:[1,2,3])
    children1: dict = field(default_factory=lambda: {"name":"喵"})

cat = Cat("aa","red",10,[1,3],{"name":"喵喵"})
asdict(cat)

Python内置装饰器

内置装饰器

  • 不用实例化、直接调用
  • 提升代码的可读性

内置装饰器 含义
classmethod 类方法
staticmethod 静态方法

普通方法

  • 定义:
    第一个参数为self,代表 实例本身
  • 调用:
    要有实例化的过程,通过 实例对象.方法名 调用
# 1. 定义
class MethodsDemo:
    param_a = 0 #类变量
    def normal_demo(self): # 定义一个类方法,第一个参数必须为self
        """
        普通方法
        :return:
        """
        print("这是一个普通方法", self.param_a)
# 2. 调用
md = MethodsDemo()
md.normal_demo()

类方法

  • 定义:
    使用 @classmethod 装饰器,第一个参数为类本身,所以通常使用cls命名做区分(非强制)
    在类内可以直接使用类方法或类变量,无法直接使用实例变量或方法
  • 调用:
    无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用
# 1. 类的定义
class MethodsDemo:
    param_a = 0
    # 定义类方法必须加 classmethod装饰器
    @classmethod
    def classmethod_demo(cls):
        """
        类方法,第一个参数需要改为cls
        :return:
        """
        print("这是一个类方法", cls.param_a)

# 2. 类的调用
MethodsDemo.classmethod_demo() # 无需实例化,直接调用
# 1. 类的定义
class MethodsDemo:
    param_a = 0
    def __init__(self):
        self.b = "cba"

    def demo_method(self):
        print("这是一个普通方法")

    def demo_method2(self):
        self.demo_method()
        self.a = "abc"
        print("这是一个普通方法")

    # 定义类方法必须加 classmethod装饰器
    @classmethod
    def classmethod_demo(cls):
        cls.demo_method() # 类方法内,不可以直接调用实例变量与实例方法
        cls.a # type object 'MethodsDemo' has no attribute 'a'
        cls.b # type object 'MethodsDemo' has no attribute 'b'
        cls.classmethod_demo2() # 类方法内,可以直接调用类变量与类方法
        print("这是一个类方法", cls.param_a)

    @classmethod
    def classmethod_demo2(cls):
        print("这是一个类方法", cls.param_a)
# 2. 类的调用
MethodsDemo.classmethod_demo() # 无需实例化,直接调用

实例变量、实例方法与类变量、类方法的辨析

实例方法:不加@classmethod装饰器
实例变量:定义在实例方法里面
类方法:加@classmethod装饰器
类变量:定义在类里,方法外面

静态方法

  • 定义:
    使用 @staticmethod 装饰器,没有和类本身有关的参数
    无法直接使用任何类变量、类方法或者实例方法、实例变量
  • 调用:
    无需实例化,直接通过 类.方法名 调用,也可以通过 实例.方法名 调用

普通方法、类方法、静态方法总结

名称 定义 调用 关键字 使用场景
普通方法 至少需要一个参数self 实例名.方法名() 方法内部涉及到实例对象属性的操作
类方法 至少需要一个cls参数 类名.方法名() 或者实例名.方法名() @classmethod 如果需要对类属性,即静态变量进行限制性操作
静态方法 无默认参数 类名.方法名() 或者实例名.方法名() @staticmethod 无需类或实例参与

实际案例

右边的代码实现的需求是格式化输出时间
如果现在需求变更,输入 年、月、日 没法保证格式统一,可能是json,可能是其他格式的字符串,在不修改构造函数的前提下,如何更改代码

最普通的方法

class DateFormat:
    def __init__(self, year=0, month=0, day=0):
        self.year = year
        self.month = month
        self.day = day

    def out_date(self):
        return f"输入的时间为{self.year}年,{self.month}月,{self.day}日"

def json_format(json_data):
    year, month, day = json_data["year"], json_data["month"], json_data["day"]
    return year, month, day

year, month, day = json_format({"year":2021, "month":12, "day":24})

demo = DateFormat(year, month, day)
print(demo.out_date())  

但是这样的改法,使用这个类的人还要去找到这个转换函数,使用起来不够方便
—>可以使用类方法,代码的可读性更强,可维护性也更高

class DateFormat:
    def __init__(self, year=0, month=0, day=0):
        self.year = year
        self.month = month
        self.day = day

    def out_date(self):
        return f"输入的时间为{self.year}年,{self.month}月,{self.day}日"

    @classmethod
    def json_format(cls, json_data):
        """
        输入一个字典格式的数据信息,返回(2021, 1, 2)
        :return:
        """
        year, month, day = json_data["year"], json_data["month"], json_data["day"]
        # return cls(year, month, day)
        return DateFormat(year, month, day)


demo = DateFormat.json_format({"year":2021, "month":12, "day":24})
print(demo.out_date())
# demo = DateFormat(year, month, day)
# print(demo.out_date())

静态方法实际案例

常用在:

  • 此方法没有任何和实例、类相关的部分,可以作为一个独立函数使用
  • 某些场景下,从业务逻辑来说又属于类的一部分
    例子:简单工厂方法(可以当学到设计模式那章节再看)
class HeroFactory:
    # staticmethod 使用场景,
    # 方法所有涉及到的逻辑都没有使用实例方法或者实例变量的时候
    # 伪代码
    @staticmethod
    def create_hero(hero):
        if hero == "ez":
            return EZ()
        elif hero == "jinx":
            return Jinx()
        elif hero == "timo":
            return Timo()
        else:
            raise Exception("此英雄不在英雄工厂当中")

另一个案例:

# static 使用场景
"""
多轮比赛,每轮由两个不同的英雄对打
"""
class Game:

    def __init__(self, first_hero, second_hero):
        self.first_hero = first_hero
        self.second_hero = second_hero
    # fight方法有和实例变量交互的部分,所以需要定义为一个普通方法
    def fight(self):
        print(f"本轮比赛开始,由{self.first_hero}VS{self.second_hero}")
    # start方法没有用到任何和实例、类相关的部分,那么就可以当作静态方法使用
    @staticmethod
    def start():
        print("游戏开始")

Game.start()
game1 = Game("Bob", "Merry")
game2 = Game("Mike", "Henry")

Python内置库

os库

import os

# 查看os模块说明文档
help(os)

# 查看os模块的属性和方法
print(dir(os))

os库常用方法

  • 系统相关
  • 操作目录
  • 操作路径

os操作系统相关

import os

# os.name:获取系统名称 返回nt代表window,posix代表linux
print(os.name)

# os.environ:获取字典格式的系统环境变量信息
print(os.environ)

# os.getenv('PATH'):获取指定名称的环境变量信息
print(os.getenv('PATH'))

# os.system():执行系统指令,模拟在终端执行指令
os.system('pwd')  # linux系统
print(os.system('dir'))  # windows系统

os目录相关

"""目录相关"""
# 获取当前所在目录 get current directory
print(os.getcwd())
# 切换目录 change directory,相当于cd
os.chdir('..')
# 列出当前目录下的所有文件
print(os.listdir())
# 创建空目录
os.mkdir('new')
# 递归创建多级空目录
os.makedirs('a/b/c')
# 删除空目录(注意不能删除非空目录)
os.rmdir('new')
# 重命名目录
os.rename('a', 'a1')
# 删除文件(注意不是目录)
os.remove('demo.txt')

os路径相关

# 返回绝对路径
print(os.path.abspath("./os_demo.py"))
# 返回文件名
print(os.path.basename("/Users/xiaofo/coding/pythonProject/course/os_demo.py"))
# 返回文件路径
print(os.path.dirname("/Users/xiaofo/coding/pythonProject/course/os_demo.py"))
# 分割路径,将绝对路径分割为dirname和basename
print(os.path.split("/Users/xiaofo/coding/pythonProject/course/os_demo.py"))
# 拼接路径,拼接dirname和basename为绝对路径
print(os.path.join("/Users/xiaofo/coding/pythonProject/course", "os_demo.py"))
# 判断路径是否存在
print(os.path.exists("/Users/xiaofo/coding/pythonProject/course/os_demo.py")) # 绝对路径
print(os.path.exists("./os_demo.py")) # 相对路径
# 判断是否是目录
print(os.path.isdir("../demos"))
# 判断是否是文件
print(os.path.isfile("./hello.py"))
# 获取文件大小,单位是字节
print(os.path.getsize("/Users/xiaofo/coding/pythonProject/course/os_demo.py"))

sys库

sys概述

  • 是 Python 自带的内置模块
  • 是与 Python 解释器交互的桥梁

sys模块的使用

# 导入sys模块
import sys

# 查看sys模块帮助文档
help(sys)

# 查看sys模块的属性和方法
print(dir(sys))

sys常用属性

"""sys模块常用属性"""
# 返回Python 解释器版本
print(sys.version)
# 返回操作系统平台名称(对于linux系统通常返回的是linux,对于windows系统通常返回的是win32,对于mac系统通常返回的是darwin)
print(sys.platform)
# 返回外部向程序传递的参数,就是动态地收集传递给命令行的参数列表
print(sys.argv)
# 返回已导入的模块信息
print(sys.modules)
print(sys.modules.keys()) # 模块名
# 返回导包的搜索路径列表
print(sys.path)
# 添加自定义路径到导包路径列表中
from path_demo import hello
hello()
# 当我们在一个非默认导包路径的hello文件夹中创建了一个文件,pycharm会自动地帮我们把path_demo换成上级目录.hello.path_demo,但是如果我们不想修改,就可以将这个路径添加到导包路径列表中
import os.path
import sys
my_dir = os.path.dirname(os.path.abspath(__file__)) + "/hello"
sys.path.append(my_dir)

注意,这种用append实现的结果并不是永久的,只有在脚本运行时才有效
1691587347359

sys常用方法

"""sys模块常用方法"""
# 获取系统当前编码
print(sys.getdefaultencoding())

# 运行时退出
sys.exit() # 默认传0 

文件处理

文件操作步骤

  • 打开文件
  • 操作文件:读/写内容
  • 关闭文件(读写完成,要及时的关闭)

open方法

def open(file, mode='r', buffering=None, 
encoding=None, errors=None, newline=None, 
closefd=True):
# mode是读写模式
# buffering是缓冲区的大小
文件读写方式

实战1

# 第一步:(以只读模式)打开文件
f = open('data.txt', 'r', encoding='utf-8')  

# 第二步:读取文件内容
print(f.read())
 
# 第三步:关闭文件
f.close()

读操作


注意:如果读过以后再执行其他读操作,将会返回空,因为read()读完后光标会停在末尾。可以通过seek(0)让光标再次回到开头

忘记关闭文件的危害

  • 打开文件达到一定数量, 将会导致打开失败
  • 占用内存空间,非常浪费资源
  • 会导致系统自动回收资源,而丢失数据

因此,我们可以使用with关键字来避免我们忘记关闭文件

with用法

with open('data.txt', 'r', encoding='utf-8') as f:
    print(f.read())
print(f.closed)   ## 查看关闭状态

写操作实战

  • mode=“w+”, 读写权限,会新建文件,清空内容再写入
  • mode=“r+”, 读写权限,替换原来的内容(一一对应替换,注意换行符)
  • mode=“a+”, 读写权限,追加内容

增加字节:
open(‘./filename.txt’,‘a’).write(‘a’)
截取字节:
open(‘./filename.txt’,‘r+’).truncate(5)

总结

  • 尽量使用 with 方法,会自动完成关闭操作
  • 通过 python 封装的 API,可以实现读,写,追加操作
  • 文件打开要使用utf-8的编码格式(以免中文出现乱码)

##科学计算
math 函数,python 提供的内置数学类函数库,包含了很多数学公式。
比如幂函数运算,三角函数,高等函数运算等。

math函数操作

import math

  • 数字常数
  • 数论与表示函数
  • 幂对数函数
  • 三角对数函数
  • 高等特殊函数

数字常量

常数 数学表示 描述
math.pi π 圆周率,值为3.1415926535…
math.e e 自然对数,值为2.7182818…
math.inf ∞ 正无穷大,负无穷-math.inf
math.nan NAN 非数字 NAN(Not a Number)

数论与表示函数

幂函数与对数函数

三角函数

高等特殊函数

实战练习

  • 常量
  • 数论与表示函数(ceil, floor)
  • 幂函数与对数函数 (pow(), sqrt())

实例

天天向上的力量

一年365天,以第1天的能力值为基数,记为1.0,
当努力学习时,能力值相比前一天提高1%,
当没有学习时能力值相比前一天下降1%。
(每天努力和每天放任,一年下来的能力值相差多少呢? )

import math
print(math.pow((1.0 + 0.01), 365) - math.pow((1.0 - 0.01), 365))

Python日期与时间处理

工作中的应用

  • 作为日志信息的内容输出
  • 计算某个功能的执行时间
  • 用日期命名一个日志文件的名称
  • 生成随机数(时间是不会重复的)

python中处理时间的模块

  • time
  • datetime
  • calendar

常见的时间表示形式

  • 时间戳
  • 格式化的时间字符串

datetime 常用的类

datetime (from datetime import datetime) 时间日期相关
timedelta (from datetime import timedelta) 计算两个时间的时间差
timezone (from datetime import timezone) 时区相关

练习1 - 获取当前日期和时间

import datetime
now = datetime.datetime.now()
print(now)
print(now.day) # 获取天
print(now.month) # 获取月
print(now.year) # 获取年
# 转成时间戳
print(now.timestamp())
# 获取指定时间
print(datetime.datetime(2021, 10, 10)) # 2021-10-10 00:00:00

练习2 - 字符串与时间互转

s="2021-09-27 06:47:06"
# 将字符串 转换为datetime实例
s1=datetime.datetime.strptime(s,'%Y-%m-%d %H:%M:%S')

# 时间转成字符串
now = datetime.datetime.now()
result = now.strftime('%a, %b %d %H:%M')

# 参考链接:https://docs.python.org/3/library/datetime.html?highlight=strftime

练习3 - 时间戳 与时间互转

import datetime
mtimestamp = 1632725226.129461
# 将时间戳转成时间 
s = datetime.datetime.fromtimestamp(mtimestamp)
# 将时间转成时间戳
print(s.timestamp())

实例

写一段代码,生成一个以时间命名的日志文件。
并向日志文件中写入日志数据。

import datetime
now = datetime.datetime.now()
# 转成字符串
str_time = now.strftime('%Y-%m-%dT%H-%M-%S')
filename = str_time + '.log'
with open(filename, 'w+', encoding='utf-8') as f:
    message = f"{now} [info] line:7 this is a log message"
    f.write(message)

json

  • JSON 是用于存储和交换数据的语法,是一种轻量级的数据交换格式。
  • 使用场景
    • 接口数据传输
    • 序列化
    • 配置文件

json结构

  • 键值对形式
  • 数组形式
{
    "language": [
        {
            "name": "python",
            "url": "https://www.python.org/"
        },
        {
            "name": "java",
            "url": "https://www.java.com/zh-CN/"
        }
    ]
}

Python 与 JSON 数据类型对应

image
因此python中的类型不能直接拿来在json中用,需要做转换

json库的作用

  • 可以从字符串或文件中解析 JSON
  • 该库解析 JSON 后将其转为 Python 字典或者列表

常用方法

import json

# 定义 python 结构
data = [{'a': 1, 'b': '2', 'c': True, 'd': False, 'e': None }]   

# 将 python 对象编码为 JSON 字符串
json_data = json.dumps(data) # [{"a": 1, "b": "2", "c": true, "d": false, "e": null}]
# 将 JSON 字符串解码为 python 对象
python_data = json.loads(json_data) # [{'a': 1, 'b': '2', 'c': True, 'd': False, 'e': None}]
# 写入 JSON 数据,在代码当前目录生成一个 data.json 的文件
with open('data.json', 'w') as f:    
     json.dump(data, f)   
# 读取数据,读取 json 文件并解码成 python 对象
with open('data.json', 'r') as f:
     data = json.load(f)

注意:json中是使用双引号的
json模块中的4个基本方法:dump和dumps,load和loads

  • dumps():将 Python 对象编码成 JSON 字符串
  • loads():解码 JSON 数据,该函数返回 Python 对象
  • dump(): Python 对象编码,并将数据写入 json 文件中
  • load():从 json 文件中读取数据并解码为 Python 对象

####dumps 常用参数
indent:根据数据格式缩进显示,默认为 None,没有缩进
ensure_ascii:对中文使用 ASCII 编码,默认为 True

dumps中的ensure_ascii参数。默认为True,如果之后要给中文编码,那需要关掉,防止出现乱码
indent参数,是根据我们的数据进行缩进,格式美化

import json

# 定义 python 结构
data = [{'a': 1, 'b': '霍格沃兹', 'c': True, 'd': False, 'e': None }]

# 将 python 对象编码为 JSON 字符串
json_data = json.dumps(data)
print(json_data) # [{"a": 1, "b": "\u970d\u683c\u6c83\u5179", "c": true, "d": false, "e": null}]
json_data = json.dumps(data, ensure_ascii=False)
print(json_data) # [{"a": 1, "b": "霍格沃兹", "c": true, "d": false, "e": null}]
json_data = json.dumps(data, ensure_ascii=False, indent=4)
print(json_data)
"""
[
    {
        "a": 1,
        "b": "霍格沃兹",
        "c": true,
        "d": false,
        "e": null
    }
]
"""

re

在Python中使用正则表达式

  • 把正则表达式作为模式字符串
  • 正则表达式避免过多的转义,可以使用原生字符串来表示,原生字符串需要在字符串前方加上 r’string’
# 匹配字符串是否以 hogwarts_ 开头

r'hogwart_\w+'

正则表达式对象转换

compile():将字符串转换为正则表达式对象
需要多次使用这个正则表达式的场景
match():从字符串的开始处进行匹配
search():在整个字符串中搜索第一个匹配的值
findall():在整个字符串中搜索所有符合正则表达式的字符串,返回列表

import re

'''
prog:正则对象,可以直接调用匹配、替换、分割的方法,不需要再传入正则表达式
pattern:正则表达式
'''

prog = re.compile(pattern)
import re

'''
pattern: 正则表达式
string: 要匹配的字符串
flags: 可选,控制匹配方式
    - A:只进行 ASCII 匹配
    - I:不区分大小写
    - M:将 ^ 和 $ 用于包括整个字符串的开始和结尾的每一行
    - S:使用 (.) 字符匹配所有字符(包括换行符)
    - X:忽略模式字符串中未转义的空格和注释
    re.match(pattern, string, [flags])
    re.search(pattern, string, [flags])
    re.findall(pattern, string, [flags])
'''

# 匹配以hog开头的字符串
pattern = r"hog\w+"

s1 = "Hogwarts is a magic school"
match1 = re.match(pattern, s1, re.I)
print(match1)
print(f"匹配值的起始位置为:{match1.start()}")
print(f"匹配值的结束位置为:{match1.end()}")
print(f"匹配位置的元组为:{match1.span()}")
print(f"要匹配的字符串为:{match1.string}")
print(f"匹配的数据为:{match1.group()}")

s2 = "I like hogwarts hogwarts"
match2 = re.search(pattern, s2, re.I)
print(match2)

match3 = re.findall(pattern, s2, re.I)

替换字符串

sub():实现字符串替换

import re

'''
pattern:正则表达式
repl:要替换的字符串
string:要被查找替换的原始字符串
count:可选,表示替换的最大次数,默认值为 0,表示替换所有匹配
flags:可选,控制匹配方式
re.sub(pattern, repl, string, [count], [flags])
'''
# 匹配手机号码
pattern = r"1[34578]\d{9}"

s1 = "中奖号码 123456, 联系电话15611111111"
result = re.sub(pattern, '1xxxxxxxxxx', s1)
print(result) # 中奖号码 123456, 联系电话1xxxxxxxxxx

分割字符串

split():根据正则表达式分割字符串,返回列表

import re

'''
pattern:正则表达式
string:要匹配的字符串
maxsplit:可选,表示最大拆分次数
flags:可选,控制匹配方式
re.split(pattern, string, [maxsplit], [flags])
'''

pattern = r"[?|&]"
url = "https://www.baidu.com/s?wd=%E9%9C%8D%E6%A0%BC%E6%B2%83%E5%85%B9%E6%B5%8B%E8%AF%95%E5%BC%80%E5%8F%91&rsv_spt=1&rsv_iqid=0xfff2a232002fd619&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_dl=ib&rsv_sug3=21&rsv_sug1=16&rsv_sug7=101"
result = re.split(pattern, url)
print(result)

threding多线程

线程基本使用

def main():
    print("在扔一个苹果")

if __name__ == "__main__":
    main()

小丑扔3个苹果
有三个线程执行,其中一个是主线程

import threading
import time
def apple_1():
    print("苹果1")
    time.sleep(1)
def apple_2():
    print("苹果2")
    time.sleep(1)
def main():
    thread = threading.Thread(target=apple_1)
    thread2 = threading.Thread(target=apple_2)
    # 开启第二个线程
    thread.start()
    # 开启第三个线程
    thread2.start()
    print("苹果3")
    print("有多少小小丑? ", threading.active_count())
    print("这些小丑是谁呢?", threading.enumerate())

if __name__ == "__main__":
    main()

GIL锁

在同一时刻,只能有一个线程在运行

import threading
import time

def task():
    a = 0
    while a < 9999*9999:
        a += 1


def main():
    start_time = time.time()
    thread = threading.Thread(target=task)
    thread2 = threading.Thread(target=task)
    thread2.start()
    thread.start()
    # 由于执行thread后执行thread2然后马上执行task,并不会等待第一个线程是否执行完毕
    # 因此可以用join,让其他线程等待自己执行完成
    thread.join()
    thread2.join()
    # 只有thread和thread2两个线程执行完毕才会接下去执行
    task()
    print("all time: ", time.time() - start_time)


if __name__ == "__main__":
    main()

注意:
多线程:空中只出现一个苹果(并发)
多进程:空中出现三个苹果(并行)
而python中只能实现多线程

pythonlogging

日志的作用

  • 调试
  • 辅助定位问题
  • 数据分析

日志的级别


debug是最低级别,展示了最详细的信息

日志的用法

官网:日志常用指引 — Python 3.11.4 文档


如果打印的是debug日志,那会打印出所有的日志
如果打印的是waring日志,那会打印出当前级别及以上级别的日志(waring、error、critical)

import logging
logging.warning("warings!")
logging.info("log!")
logging.error("errors!")
# 只输出高级别的日志,低级别的日志会被忽略
# WARNING:root:warings!
# ERROR:root:errors!

且logging模块默认设置的级别是waring级别

设置日志的级别

需要在最开头设置

logging.basicConfig(level=logging.INFO)
import logging
logging.basicConfig(level=logging.INFO)
logging.debug("debug!")
logging.warning("warings!")
logging.info("log!")
logging.error("errors!")
# info及以上级别的日志都会输出
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("debug!")
logging.warning("warings!")
logging.info("log!")
logging.error("errors!")
# debug及以上级别的日志都会输出

保存日志到文件

logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)

会保存当前级别及以上的日志,而且保存方式是追加模式

设置时间格式

logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')

具体还有很多时间格式,可以参照https://docs.python.org/zh-cn/3/library/time.html#time.strftime
另外我们可以把多个功能放在一起设置

logging.basicConfig(filename='myapp.log', 
                    level=logging.INFO,
                    format='%(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)', 
                    datefmt='%m/%d/%Y %I:%M:%S %p')

pythonlogging高级使用(还没学习)

环境管理

venv环境管理(只是简单了解)

pip环境管理

pip是什么?

  • pip 是 Python 包管理工具
    • python2 的 2.7.9 版本开始自带
    • python3 的 3.4 版本开始自带
  • https://pypi.org/ 托管了大量流行的 Python 包

pip 常用命令

功能 指令
查看 pip 版本 pip -V
查看帮助文档 pip help
查看包列表 pip list
导出包列表 pip freeze
安装 pip install 包名
升级 pip install -U 包名
卸载 pip uninstall 包名

pip安装包

  • 普通安装
  • 指定版本
  • 从文件中安装
# 默认安装最新版本
$ pip install pytest

# 执行版本
$ pip install pytest==6.2.0

# 从文件清单中批量安装
$ pip install -r requirments.txt

# 文件格式
pytest==6.2.0
Faker==9.3.1
selenium==3.14.1

pip 升级包

  • 升级已安装的 Python 包
$ pip install -U pytest

pip 卸载包

  • 卸载 Python 包
$ pip uninstall pytest

pip 使用镜像加速

pip install pytest -i https://pypi.douban.com/simple

常用第三方库(还没学习)

yaml

pymysql

urllib3