Python开发经验--从问题入手

简介:

上下文管理

如果你要做一件事情do_sth(),在这之前,需要连接数据库,需要持有文件锁。那么你很可能这么写:

try:
    db.connect()
    getfilelock()
    do_sth()
except:
    ...
finally:
    releasefilelock()
    db.close()

如果do_sth在 N 个地方出现呢?如果我策略修改了,不再需要filelock 了呢?是不是需要去改这 N 个地方?
花了一下午去修改了 N 个地方,测试了几次终于没有遗漏后,你发现这个做法多么低效,于是你终于想起来用一个函数去做这件事情。
你把代码写成了这样

try:
    prepare()
    do_sth()
except:
    ...
finally:
    close()

do_sth所在的 N 个地方,都写满了 prepareclose 这一系列和业务逻辑无关的代码, 这使得我们的代码变得冗长。 即使你不在乎自己过了两个月后是否还愿意看这段代码,至少你要照顾下帮你 review 代码的人,一个函数写了500行还能 review 吗? 我认为一个程序员写代码的过程,就是从复杂的逻辑中进行抽象的过程,如果不进行抽象,那么就不是写代码,而是在做翻译。我们来看看这里有什么可以抽象的。
prepareclose 这些是在做什么?是在预备和释放一个可以do_sth的环境,也就是我们常说的上下文管理。
Python 提供了with语句来帮助我们管理上下文。代码变成了这样:

class A():
    def do_sth(self):
        pass

    def __enter__(self):
        db.connect()
        getfilelock()
        return self

    def __exit__(self, type, value, traceback):
      releasefilelock()
      db.close()

with A() as a:
    a.do_sth()

现在帮你 review 代码的人,只会在那 N 个do_sth 存在的地方,看到

  with A() as a:
    a.do_sth()

很显示的使用了上下文进行管理,分离了业务逻辑和具体实现。

Explicit is better than implicit. Simple is better than complex.

另一个常见的做法是使用__del__

class A():
    def __init__(self):
        db.connect()
        getfilelock()

    def __del__(self, type, value, traceback):
        releasefilelock()
        db.close()

a =  A()
a.do_sth()

这个做法的优点是不需要写with 语句,缺点是这不是明显的上下文管理,对于 Python/Java 这种带 GC 的语言来说,是不能手动调用一个对象的析构函数的,即使对象离开了作用域,它依然会因为还没有被GC而存活。
所以对于锁这样的,我们需要很快去释放的资源,使用with 更加的可控。 可能会有人去对比 C++ 中的RAII 规则,Python中是做不到 RAII 的, 因为以上原因。

生成器

一个函数如果在代码段中有 yield 那么它就从一个函数变成了一个 generator。生成器的好处是保留了一个上下文在需要时去运算。
同样从一个实际例子出发:

  for i in range(100000):
    do_sth()

这段代码,在什么都没做之前,就产生了一个1-100000这些数字的数组,占用了内存。

  for i in xrange(100000):
    do_sth()

这才是我们知道的C++中的

  for(int i = 0; i < 100000; i++) {
    do_sth()
  }

执行一次 do_sth,再生成一次 i 值。 
所以xrange会比range更省内存。(在 Py2 是这样。Py3中,range 就是 Py2 的 xrange。 另外据我所知,不存在 xrange 是 C 实现这么一回事)。 再来一个例子:

def fetchmany(self, *args, **kwargs):
    '''
       A generator to fetch data.
       prams: numRows -> the number of rows to fetch.
    '''
    cur = self._conn.cursor()  
    numRows = 200
    try:
        cur.execute(*args, **kwargs)
        self._conn.commit()
    except Exception as e:
        self.logger.error(str(e))
        self._conn.rollback()
        return
    while True:
        rows = cur.fetchmany(numRows)
        if rows:
            yield rows
        else:
            cur.close()
            return

for rows in fetchmany(sql):
  do_sth_with_rows(rows)

这就实现了每次取出200条,消费完再取200条。而不是一次性取到内存中。同时也封装了commitrollback等操作。

生成器也可以用来构造对称式协程

import random

def get_data():
    """Return 3 random integers between 0 and 9"""
    return random.sample(range(10), 3)

def consume():
    """Displays a running average across lists of integers sent to it"""
    running_sum = 0
    data_items_seen = 0

    while True:
        data = yield
        data_items_seen += len(data)
        running_sum += sum(data)
        print('The running average is {}'.format(running_sum / float(data_items_seen)))

def produce(consumer):
    """Produces a set of values and forwards them to the pre-defined consumer
    function"""
    while True:
        data = get_data()
        print('Produced {}'.format(data))
        consumer.send(data)
        yield

if __name__ == '__main__':
    consumer = consume()
    consumer.send(None)
    producer = produce(consumer)

    for _ in range(10):
        print('Producing...')
        next(producer)

在这个例子中, 不需要队列,就做到了一个一对一的生产与消费。当然这个1对1的并没有什么实际意义。对于协程和生成器的作用,如果感兴趣,可以看看Tornado的实现。

Exception

这块Python和其他语言没什么不同的,拿出来讲是因为看到很多代码里充满了魔数。
比如 return 'unknown'. 既然是unknown就应该抛出异常。而不是用一堆的常量字符串在各个函数直接传来传去。
类似的例子还有return 'ok'return 'mysql'return 2
另外尽可能抛出和接收特定的异常。

Functional Programming

Python 其实是不怎么 FP 的,不过一些基本的 FP 支持还是有的。
比如高阶函数,map reduce filter
map reduce filter 可以更好的做抽象。
reduce(lambda x, y: x + y, query_res)
很简单就能看出,虽然我不知道具体 x,y 是什么,但是我知道,这是把 query_res 中的每个值加到一起。
不用去管xy的类型,只需要他们支持+操作。这种编程思维可以再次把业务逻辑和实现分开。

Decorator

装饰器算是 Python 的一大亮点。看下面这个例子:

  begin = time.time()
  do_sth()
  end = time.time()
  print 'do sth used %s second' % (end-begin)

业务逻辑里混杂着统计时间的逻辑。添加要加三行,什么时候不要了,又要删三行。 如果用上装饰器

def logtime(func):
    @wraps(func)
    def wrapper(*args, **kwds):
        start_time = time.time()
        ret = func(*args, **kwds)
        use_time = time.time() - start_time
        if use_time > 1.0:
            logging.info("%s use %s s", func.__name__, use_time)
        return ret
    return wrapper

@logtime
def do_sth():
  ...



do_sth()

代码被调用的时候,不需要知道做了时间的统计,也不需要把统计代码混杂在业务逻辑中。 
考虑下面的代码,用 Java 应该怎么实现?

def make_spin(spin_style=Default, words=""):
    spinner = Spinner(spin_style)
    queue = Queue()

    def add_queue(func):
        @wraps(func)
        def wrapper():
            func()
            queue.put_nowait(1)
        return wrapper

    def decorator(func):
        @wraps(func)
        def wrapper():
            process = Process(target=add_queue(func))
            process.start()
            while queue.empty():
                print(text_type("\r{0}    {1}").format(spinner.next(), words),
                      end="")
                sys.stdout.flush()
                time.sleep(0.1)
            print('')
        return wrapper
    return decorator

Java中同样也有@字符的运用,不同的是,Java中那个叫annotation, 也就是注解。 注解是编译期的,它是用来做反射的,也就是提供给外部一些关于我本身信息的。和 Python 的用法没有关系。

Unicode

下面直接用一些例子来显示 Python2中的 Unicode 和 Str 的关系。

>>> a = '我'
>>> a
'\xe6\x88\x91'
>>> type(a)
<type 'str'>

>>> b = '我'.decode('utf-8')
>>> b
u'\u6211'
>>> type(b)
<type 'unicode'>

>>> b.encode('utf-8')
'\xe6\x88\x91'
>>> b.encode('gbk')
'\xce\xd2'

很明显可以看出,Python2中,str 类型其实是 byte 字节流。而 Unicode 类型则是表示了 Unicode 字符号。


目录
相关文章
|
5月前
|
API C++ 开发者
PySide vs PyQt:Python GUI开发史诗级对决,谁才是王者?
PySide 和 PyQt 是 Python GUI 开发领域的两大利器,各有特色。PySide 采用 LGPL 协议,更灵活;PyQt 默认 GPL,商业使用需授权。两者背后团队实力雄厚,PySide 得到 Qt 官方支持,PyQt 由 Riverbank Computing 打造。API 设计上,PySide 简洁直观,贴近原生 Qt;PyQt 增加 Pythonic 接口,操作更高效。性能方面,两者表现优异,适合不同需求的项目开发。选择时可根据项目特点与开源要求决定。
342 20
|
7月前
|
前端开发 JavaScript 关系型数据库
基于Python+Vue开发的商城管理系统源码+运行步骤
基于Python+Vue开发的商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的网上商城管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
212 7
|
3月前
|
数据采集 存储 数据库
Python爬虫开发:Cookie池与定期清除的代码实现
Python爬虫开发:Cookie池与定期清除的代码实现
|
4月前
|
人工智能 搜索推荐 数据可视化
用 Python 制作简单小游戏教程:手把手教你开发猜数字游戏
本教程详细讲解了用Python实现经典猜数字游戏的完整流程,涵盖从基础规则到高级功能的全方位开发。内容包括游戏逻辑设计、输入验证与错误处理、猜测次数统计、难度选择、彩色输出等核心功能,并提供完整代码示例。同时,介绍了开发环境搭建及调试方法,帮助初学者快速上手。最后还提出了图形界面、网络对战、成就系统等扩展方向,鼓励读者自主创新,打造个性化游戏版本。适合Python入门者实践与进阶学习。
335 1
|
4月前
|
存储 算法 数据可视化
用Python开发猜数字游戏:从零开始的手把手教程
猜数字游戏是编程入门经典项目,涵盖变量、循环、条件判断等核心概念。玩家通过输入猜测电脑生成的随机数,程序给出提示直至猜中。项目从基础实现到功能扩展,逐步提升难度,适合各阶段Python学习者。
195 0
|
6月前
|
程序员 测试技术 开发工具
怎么开发Python第三方库?手把手教你参与开源项目!
大家好,我是程序员晚枫。本文将分享如何开发Python第三方库,并以我维护的开源项目 **popdf** 为例,指导参与开源贡献。Popdf是一个PDF操作库,支持PDF转Word、转图片、合并与加密等功能。文章涵盖从fork项目、本地开发、单元测试到提交PR的全流程,适合想了解开源贡献的开发者。欢迎访问[popdf](https://gitcode.com/python4office/popdf),一起交流学习!
209 21
怎么开发Python第三方库?手把手教你参与开源项目!
|
4月前
|
数据采集 存储 监控
抖音直播间采集提取工具,直播间匿名截流获客软件,Python开发【仅供学习】
这是一套基于Python开发的抖音直播间数据采集与分析系统,包含观众信息获取、弹幕监控及数据存储等功能。代码采用requests、websockets和sqlite3等...
|
6月前
|
前端开发 JavaScript 关系型数据库
基于Python+Vue开发的美容预约管理系统源码+运行
基于Python+Vue开发的美容预约管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的美容诊所预约管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
92 9
|
6月前
|
JavaScript 前端开发 关系型数据库
基于Python+Vue开发的体育场馆预约管理系统源码+运行
本项目为大学生课程设计作业,采用Python和Vue技术构建了一个体育场馆预约管理系统(实现前后端分离)。系统的主要目标在于帮助学生理解和掌握Python编程知识,同时培养其项目规划和开发能力。参与该项目的学习过程,学生能够在实际操作中锻炼技能,为未来的职业发展奠定良好的基础。
136 3
|
6月前
|
前端开发 JavaScript 关系型数据库
基于Python+Vue开发的摄影网上预约管理系统源码+运行
基于Python+Vue开发的摄影网上预约管理系统(前后端分离),影楼婚纱摄影,这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的在线摄影预约管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
99 8

推荐镜像

更多