• 参考地址:https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/12-write-solid-python-codes-part-1.md

一:SOLID设计原则

S:单一职责原则:

  • 原则: 每一个类,应该只有一个职责
  • 解决方案:
    • 1: 拆类
    • 2: 使用函数

O: 开放-关闭原则:

  • 原则: 对拓展开放,对修改封闭。(通过拓展的方式实现别的功能,而不是改变类的内部代码)
  • 解决方案:
    • 1:类继承: 关键点(在父类种找到会变的部分,抽象成方法,或着属性,最终允许子类重写它,以改变他的行为
    • 2: 组合和依赖注入改造代码。在A类中,把变化的东西,抽离到B类中,通过将B类传递到A类内部,来改变A类的功能
    • 3: **数据驱动思想改造代码。**关键点(将经常变动的东西,完全以数据的方式抽离出来。当需求变动时,只改动数据,代码逻辑保持不动。)----传参

L: 里氏替换原则:

  • 原则: 当你使用继承时,子类(派生类)对象应该可以在程序中替代父类(基类)对象使用,而不破坏程序原本的功能。
  • 简言之: 子类的对象,能够完全替换父类对象使用。
  • 解决方案:
    • 如果该特征是子类和父类的和核心特征,则将这个核心特征写入到父类中,子类对该特征进行修改
    • 保证:子类父类的同名方法返回同一类型的数据

I: 接口隔离原则:

  • 一个接口所提供的方法,应该就是使用方所需要的方法,不多不少刚刚好

D: 依赖倒置原则:

  • 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。(难理解)

二: 边界问题

  • https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/15-thinking-in-edge-cases.md

1: 吃感冒药胜过看天气预报:

  • 使用异常捕获性能消耗小于判断语句:
res = {}
for key in my_list:
    if key in res:
        res[key] += 1
    else:
        res[key] = 1
res = {}
for key in my_list:
    try: 
        res[key] += 1
     except keyError:
        result[key] = 1

2: 容器内容不存在

2.1: defaultdict的使用:

  • defaultdict接受一个工厂函数(factory_function)。
  • 这个工厂函数可以是int , set, str等等。
  • 这样我们的defaultdict如果key不存在的时候,默认返回的是工厂函数的默认值。
from collections import defaultdict
my_list = [1, 2, 1, 3]
result = defaultdict(int)
for key in my_list:
    result[key] += 1
print(result)
# defaultdict(<class 'int'>, {1: 2, 2: 1, 3: 1})

2.2: 字典的setdefault方法和pop方法:

  • 我们要修改字典的某个键对应的值,但是我们不确定这个键是否存在。

    my_dict = {'name': 'liangshan', 'age': 23}
    my_dict.setdefault('sex', '男')
    print(my_dict)
    # {'name': 'liangshan', 'age': 23, 'sex': '男'}
    
    
    my_dict2 = {'name': 'liangshan'}
    my_dict2.setdefault('friends', []).append('tom')
    print(my_dict2)
    # {'name': 'liangshan', 'friends': ['tom']}
    
  • 如果我们删除字典的键,我们要确定是否存在这个键,如果存在则删除。

    my_dict = {'name': 'linagshan'}
    my_dict.pop('age', None)
    print(my_dict)
    

2.3:善于运用切片

  • 列表访问超过最大索引则会出现下标越界。

    # 需求: 对列表前index个求和,但是不知道列表长度
    index = 10
    my_list = [1, 2, 3]
    print(sum(my_list[:10]))
    # 6
    

3: 危险的OR

  • 短路求值特点: 不能执行的不执行。

    print(True or (1/0))
    # True
    
  • 经典的or操作:

    # 需求: 合并两个字典,如果字典为空则不合并
    my_dict = {'name': 'liangshan'}
    my_dict2 = None
    my_dict.update(my_dict2 or {})
    print(my_dict)
    # {'name': 'liangshan'}
    
  • 注意: or操作对于None, 0,[], {}, set() , 都会默认是假。

    • 例如: 我们需要用户输入时间,如果用户没有输入我们默认是60秒。
    • 我们就这样写: config.time = time or 60。
    • 但是如果用户输入的是: 0, 则因为or的原因,实际上我们时间变成了60。

4: 避免手动数据校验:

  • 学习pydantic库,三方校验模块代替自己进行校验:

    • 之前的校验逻辑:
    def input_10_to_100(num):
        if not num:
            print("不能输入为空")
        if not num.isdigit():
            print("请输入数字类型")
        if not (10 <= int(num) <= 100):
            print("请输入10到100的数字")
        pass
    
    • 现在的校验逻辑:
    class NumberInput(BaseModel):
        # 使用类型注解 conint 定义 number 属性的取值范围
        number: conint(ge=10, le=100)
            
    def input_10_to_100(number):
        try:
            number = NumberInput(number = number)
        except ValidationError as e:
            print(e)
        pass