python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python异常,规则,函数返回值

python代码规范之异常,规则,函数返回值使用解读

作者:StanwenRen

文章主要介绍了Python的集合、自定义哈希对象、数据类、字符串格式化、容器、异常处理、函数返回建议以及Django框架中的AnonymousUser等知识点

一:python的规则

1:集合的规则

案例:

给定两个列表套字典的数据,数据内容是人员(姓名,电话相同,则默认就是同一个人),我们要求是找出存在A列表,但是不存在B列表的人员。

list_a = [
    {'first_name': 'Ren', 'last_name': 'shanshan', 'phone_num': '12345678'},
  {'first_name': 'Wang', 'last_name': 'gang', 'phone_num': '12323338'},
    {'first_name': 'Li', 'last_name': 'ruixue', 'phone_num': '123389278'},
]

list_b = [
    {'first_name': 'Ren', 'last_name': 'shanshan', 'phone_num': '12345678'},
    {'first_name': 'Wang', 'last_name': 'gang', 'phone_num': '12323338'},
    {'first_name': 'Xu', 'last_name': 'daer', 'phone_num': '125545678'},
]


class People(object):

    def __init__(self, first_name, last_name, phone_num):
        self.first_name = first_name
        self.last_name = last_name
        self.phone_num = phone_num

    def __hash__(self):
        # 默认是内存地址的哈希,这里我们改成名字+电话这样的一个元组的哈希值。
        return hash(
            (self.first_name, self.last_name, self.phone_num)
        )

    def __eq__(self, other):
        # 如果传入的对象是当前类的对象,并且哈希值两者相等,则返回True。
        if isinstance(other, People) and hash(other) == hash(self):
            return True
        return False

# 将字典构造成哈希对象,然后加入集合中
set_1 = set(People(**p) for p in list_a)
set_2 = set(People(**p) for p in list_b)
# 使用集合的差
end_set = set_1 - set_2

for item in end_set:
    print(item.first_name, item.last_name, item.phone_num)
    # Li ruixue 123389278

使用python3.7版本之后的数据类: dataclasses。

from dataclasses import dataclass

@dataclass(unsafe_hash=True)
class People(object):
    first_name: str
    last_name: str
    phone_num: str


set_1 = set(People(**p) for p in list_a)
set_2 = set(People(**p) for p in list_b)

end_set = set_1 - set_2

for item in end_set:
    print(item.first_name, item.last_name, item.phone_num)
    # Li ruixue 123389278

2:__format__对象字符串格式化

1: 需求: 对于自己定义的People对象,打印出不同的信息。

class People(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __format__(self, format_spec):
        if format_spec == 'long':
            return f'{self.name} is {self.age} years old!'
        elif format_spec == 'simple':
            return f'{self.name} ({self.age})'
        raise ValueError('invalid format spec')


p = People(name='liangshan', age=23)
print('{0:simple}'.format(p))
# liangshan (23)
print('{0:long}'.format(p))
# liangshan is 23 years old!

3:__getitem__方法定义容器

1: 如果我想定义一个容器,那么肯定要考虑的就是容器取长度的方法,容器取第几个元素方法。

class Events:
    def __init__(self, events):
        self.events = events

    def __len__(self):
        """自定义长度,将会被用来做布尔判断"""
        return len(self.events)

    def __getitem__(self, index):
        """自定义切片方法"""
        # 直接将 slice 切片对象透传给 events 处理
        return self.events[index]


events = Events([
    'computer started',
    'os launched',
    'docker started',
    'os stopped',
])

# 理解: if events 对调用里面的__len__方法,通过返回是否是0来判断是否是None
# events使用切片的时候,会调用events的__getitem__方法,
# 而我们直接调用self的切片操作,相当于又调用了list中的__getitem__方法。也就是说
# 只要是实例化Events的时候,是可以使用切片的,则这个类的对象也是可以使用切片的。
if events:
    print(events[1:3])

二:python异常处理三个习惯

1: 只做最精准的异常捕获,每个异常捕获只处理一个容易错的地方。

2:避免抛出高于当前抽象级别的异常外,我们同样应该避免泄露低于当前抽象级别的异常。

3:连续异常,注意使用上下文管理器。

def upload_avatar(request):
    """用户上传新头像"""
    try:
        avatar_file = request.FILES['avatar']
    except KeyError:
        raise error_codes.AVATAR_FILE_NOT_PROVIDED

    try:
       resized_avatar_file = resize_avatar(avatar_file)
    except FileTooLargeError as e:
        raise error_codes.AVATAR_FILE_TOO_LARGE
    except ResizeAvatarError as e:
        raise error_codes.AVATAR_FILE_INVALID

    try:
        request.user.avatar = resized_avatar_file
        request.user.save()
    except Exception:
        raise error_codes.INTERNAL_SERVER_ERROR
    return HttpResponse({})

下面代码连续的异常,让我们看到眼花缭乱。

使用上下文管理器管理异常:

class Resource(object):

    def __enter__(self):
        print("连接资源")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("关闭资源")
        return True

    def operate(self):
        1 / 0


with Resource() as res:
    res.operate()
# 不会报错

三:函数返回建议

1: 单个函数,返回值不要有多种类型

2: partial函数构造函数

def multiply(x, y):
    return x * y
def double(value):
    # 返回另一个函数调用结果
    return multiply(2, value)
from functools import partial
# partial函数是专门用来基于一个函数来构造一个新的函数的。
# 参数1: 函数名
# 其余参数就是被基于的函数的参数。
def multiply(x, y):
    return x * y


double = partial(multiply, 2)

3: 不要返回结果和错误,而是选择抛出异常

4:谨慎使用None返回

5:合理返回空对象

原理:

就是使用一个符合正常结果接口的“空类型”来替代空值返回/抛出异常,以此来降低调用方处理结果的成本。

案例:

class Account:
    # def __init__ 已省略... ...
    
    @classmethod
    def from_string(cls, s):
        """从字符串初始化一个账号

        :returns: 如果输入合法,返回 Account object,否则返回 NullAccount
        """
        try:
            username, balance = s.split()
            balance = decimal.Decimal(float(balance))
        except ValueError:
            return NullAccount()

        if balance < 0:
            return NullAccount()
        return cls(username=username, balance=balance)


class NullAccount:
    username = ''
    balance = 0

    @classmethod
    def from_string(cls, s):
        raise NotImplementedError

6:返回生成器,而不是返回列表

# 返回列表
def test01(my_list):
    res_list = []
    for item in my_list:
        res_list.append(item * 2)
    return res_list


# 返回生成器
def test02(my_list):
    for item in my_list:
        yield item * 2


my_list = [1, 2, 3, 4, 5]
print(test01(my_list))
# [2, 4, 6, 8, 10]
print(test02(my_list))
# <generator object test02 at 0x000001A6C693A2A0>
for item in test02(my_list):
    print(item)

7:尽量不要使用递归

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文