如何写出优雅的代码?

背景介绍

以下这些代码片段是我工作多年,总结的一些 如何写出地道的python代码
参考书籍《代码整洁之道》,《写好Python代码的91个建议》

正文开始

代码段1

def get_environment(self, environment_id: int = None, environment_name: str = ""):
    if environment_id: 
        environment = self.session.query(
            models.Environment).filter_by(
                id=environment_id).first() 
    else: 
        environment = self.session.query(
            models.Environment).filter_by( 
                name=environment_name).first() 

    return environment 

这段代码的意思是,我们从调用方获取参数,如果是拿到了environment_id,我们就执行if代码块,如果是我们拿到了environment_name, 我们就执行else代码块。
不知道大家看到这段代码,有没有发现什么问题?


⏱️一分钟思考时间。。。


在这段代码中,我们在进行session.query时,多处使用了重复的代码,这样虽然没有任何程序上的问题,但是看起来非常不优雅。
于是我修改之后,如下

def get_environment(self, environment_id: int = None, environment_name: str = ""): 
    condition = {'id': environment_id} if environment_id else {'name': environment_name} 

    return self.session.query(models.Environment).filter_by(**condition).first() 

思考

通过分析发现,我们可以将要查询的变量组装成一个字典形式,然后在session.query中使用双星号解包使用。
这样很巧妙的去掉了冗余不好看的代码结构。

代码段2

if testcase_id and account_type:
    stmt = (
        select(
            models.Accounts).where(
            exists_testcase_id,
            exists_account_type,
            exists_account_status))
elif testcase_id:
    stmt = (
        select(
            models.Accounts).where(
            exists_testcase_id,
            exists_account_status))
elif account_type:
    stmt = (
        select(
            models.Accounts).where(
            exists_account_type,
            exists_account_status))
elif status:
    stmt = (
        select(
            models.Accounts).where(exists_account_status))
else:
    stmt = (select(models.Accounts))
return self.session.execute(stmt).all()

在这段代码中,我们最终是通过参数的组合来查询Accounts数据,在这个函数种我们接受了多个参数,testcase_id,account_type,status。这几个参数又可以产生多种组合。
如果是按照上面的编码方式,那么我们写出来的代码很长而且冗余。
那么如何重构它呢???


⏱️一分钟思考时间。。。


conditions = []

if testcase_id:
    conditions.append(models.Accounts.testcase_object.has(
        models.Testcase.testcase_id == testcase_id))
if account_type:
    conditions.append(models.Accounts.account_type_object.has(
        models.AccountType.name == account_type))
if status:
    conditions.append(models.Accounts.status == status)

stmt = (select(models.Accounts).where(*conditions))

return self.session.execute(stmt).all()

思考

我们通过分析发现,由于多个参数存在多种组合,那么我们首先需要一个列表来存放这些查询条件,然后通过if-else来添加要查询的条件放入列表中。
最后在倒第二行的时候,统一解包。
这样重构的好处就是,我们不需要再去关注他们有多少种组合的情况,我们只需把要查询的条件,统一存放起来。然后再进行查询操作。

代码片段3

def calculate_scheduling_time(interval: int) -> Dict[str, Union[int, list]]:
    time_mapping = {
        'time': ['00:00:00']
    }

    interval_time = str(datetime.timedelta(seconds=interval))

    # when the interval is greater than 1 day, time_delta='1 day, 0:00:00'
    if 'day' in interval_time:
        time_mapping['day'] = int(interval_time.split(' ')[0])
        return time_mapping

    hour = int(interval_time.split(':')[0])

    if hour > 1 and hour <= 8:
        time_mapping['time'].extend(['08:00:00', '16:00:00'])
    elif hour > 8 and hour <= 12:
        time_mapping['time'].append('12:00:00')
    elif hour > 12:
        time_mapping['day'] = 1

    # time_mapping = {'day': 1, 'time': ['00:00:00']}
    return time_mapping

此函数是为执行定时任务,实现了根据间隔秒数,切割成时间点来执行测试。大于1小时,小于等于8小时,执行时间为每一天的0,8,16。 大于8小时,小于等于12小时,执行时间为每一天的0,12。大于12小时,就每一天0点执行。
问题1:interval参数代表什么意思?不明确
问题2:使用datetime.timedelta 将秒数转化成为了天数+时间之后,又去做字符串处理来获取想要的值。可以不去处理字符串来切割时间吗?
问题3:创建了一个time_mapping的空字典来存储时间点。 如果这段给团队的其他人使用,那就必须加上注释,对方才能知道time_mapping 里面装入的数据到底长什么样?


⏱️一分钟思考时间。。。


def calculate_scheduling_time(
        interval_seconds: int) -> Dict[str, Union[int, list]]:
    time_delta = datetime.timedelta(seconds=interval_seconds)

    if time_delta > datetime.timedelta(days=1):
        return {
            'day': time_delta.days,
            'time': ['00:00:00']
        }

    interval_hour = interval_seconds / 3600
    less_than_8_hour = {'time': ['00:00:00']}
    between_8_and_12_hour = {'time': ['00:00:00']}
    between_12_and_24_hour = {'day': 1, 'time': ['00:00:00']}

    if 1 < interval_hour <= 8:
        less_than_8_hour['time'].extend(['08:00:00', '16:00:00'])
        return less_than_8_hour
    elif 8 < interval_hour <= 12:
        between_8_and_12_hour['time'].append('12:00:00')
        return between_8_and_12_hour
    elif 12 < interval_hour <= 24:
        return between_12_and_24_hour
    else:
        raise ValueError('The input value must be greater than 1')

思考

修改之后,分别为三种情况的时间点。各自创建一个新的空字典。这样好处就是代码更清晰,更易读。
去掉了对字符串的操作,而使用datetime 内置的方法,优雅处理。

Tips

未完待续。关注我,获取最新的更新状态。

posted @ 2022-05-05 21:02  烟熏柿子学编程  阅读(614)  评论(0编辑  收藏  举报