你真的会正确使用断言吗?

什么是断言 断言是作为一种调试工具被发明出来的,用来检查那些“代码写对了就肯定成立”的条件。例如我们要断言一个变量 a 必须要大于 2,就可以这样写: assert a > 2 当条件不满足时,就会抛出 AssertionError 异常,等同于如下代码: if not assert_condition: raise AssertionError 由于断言是一个 debug 工具,Python 的实现也符合这个设计哲学,在 Python 中 assert 语句的执行是依赖于__debug__变量的,当__debug__为 true 时,assert 语句才会被执行。 if __debug__ and not assert_condition: raise AssertionError 默认情况下,当我们执行一个 Python 文件时,__debug__是会被设置为 True 的,只有加参数-O 或-OO 时,__debug__才会被设置为 False。 新建一个 assert.py 文件,写下如下代码: print(__debug__) assert 2 > 5 当使用 python assert.py 运行时,__debug__会输出 True,assert 2 > 5 语句会抛出 AssertionError 异常。 当使用 python -O assert.py 运行时,__debug__会输出 False,assert 2 > 5 语句由于没有执行不会报任何异常。 断言 or 异常 我们思考这几个问题:断言应该用在哪些情境下?异常和断言的区别是什么? 用一句话来概括断言的使用场景和与异常的区别: 检查先验条件使用断言,检查后验条件使用异常 我们定义一个 read_file 函数: ...

五月 7, 2018 · 1 分钟 · Zhiya

用装饰器封装Flask-WTF表单验证逻辑

Don’t repeat yourself 在使用Flask-WTF的时候,常会用下面这样的代码来验证表单数据的合法性: from flask import Flask app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): form = TestForm() # 判断是否合法 if not form.validate_on_submit(): return 'err', 400 # 主要逻辑 对于有很多提交接口的项目来说,需要在每个路由下写相同的的逻辑,造成了大量的代码重复。在Flask-Login中,要把一个路由设置为登录后才能访问,只需要在路由上加一个@login_required装饰器,不需要额外的代码。能不能像Flask-Login一样,用装饰器来封装对表单的验证逻辑呢? 实现表单验证装饰器 由于不同路由使用的表单类不一样,所以需要为装饰器传入一个表单类参数,并且在路由函数中需要用到表单中的值,所以还需要将验证通过的表单传给路由函数。 上代码: def validate_form(self, form_cls): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if not form.validate_on_submit(): return 'error', 400 return fn(form, *args, **kwargs) return wrapper return decorator 使用方式如下: @validate_form(TestForm) # 需要传入要验证的表单类 @app.route('/', methods=['GET', 'POST']) def index(form): # 执行到这里说明表单验证通过 经过在项目中的应用,发现装饰器还是有一些缺陷: 无法自定义处理非法表单的逻辑 不支持get方式提交的表单(查看validate_on_submit()源码可知其只支持对post和put方式提交的表单进行验证) 丰富一下 要自定义处理非法表单的逻辑,需要增加一个可以传入自定义逻辑的接口。表单非法时接口的返回往往是一致的,所以我们为所有应用装饰器的路由传入一个统一的处理逻辑。将装饰器封装在一个类中,在类中添加一个配置处理逻辑的方法。 from functools import wraps from flask import request class FormValidator(object): def __init__(self, error_handler=None): self._error_handler = error_handler def validate_form(self, form_cls): def decorator(fn): @wraps(fn) def wrapper(*args, **kwargs): if not form.validate_on_submit() and self._error_handler: return self._error_handler(form.errors) return fn(form, *args, **kwargs) return wrapper return decorator def error_handler(self, fn): self._error_handler = fn return fn error_handler也是一个装饰器,被它修饰的方法就是处理非法表单的方法。 ...

四月 26, 2018 · 1 分钟 · Zhiya

Python参数传递,既不是传值也不是传引用

面试的时候,有没有被问到 Python 传参是传引用还是传值这种问题?有没有听到过 Python 传参既不是传值也不是传引用这种说法?一个小小的参数默认值也可能让代码出现难以查找的 bug? 如果你也遇到过上面的问题,不妨我们来探究下 Python 函数传递的种种。 万物皆对象 Python 中有一个非常重要的概念——万物皆对象,无论是一个数字、字符串,还是数组、字典,在 Python 中都会以一个对象的形式存在。 a = 123 对于上面这行代码,在 Python 看来就是创建一个 PyObject 对象,值为 123,然后定义一个指针 a,a 指向这个 PyObject 对象。 可变对象和不可变对象 Python 中的对象分为两种类型,可变对象和不可变对象,不可变对象指 tuple、str、int 等类型的对象,可变对象指的是 dict、list、自定义对象等类型的对象,我们用一段代码说明他们的区别。 a = [1, 2, 3] print(id(a)) # 2587116690248 a += [4] print(id(a)) # 2587116690248 b = 1 print(id(b)) # 2006430784 b += 1 print(id(b)) # 2006430816 上面代码中我们分别定义了一个可变对象和一个不可变对象,并且对他们进行修改,打印修改前后的对象标识可以发现,对可变对象进行修改,变量对其引用不会发生变化,对不可变对象进行修改,变量引用发生了变化。 上图是一个可变对象,当修改对象时,例如删除数组中的一个元素,实际上把其中一个元素从对象中移除,对象本身的标识是不发生变化的。 改变一个不可变对象时,例如给一个 int 型加 2,语法上看上去是直接修改了 i 这个对象,但是如前面所说,i 只是一个指向对象 73 的一个变量,Python 会将这个变量指向的对象加 2 后,生成一个新的对象,然后再让 i 指向这个新的对象。 参数传递时的表现 了解了对象的原理后,我们就可以来尝试理解一下参数传递时他们的不同表现了。 a = [1, 2, 3] print(id(a)) # 1437494204232 def mutable(a): print(id(a)) # 1437494204232 a += [4] print(id(a)) # 1437494204232 mutable(a) b = 1 print(id(b)) # 2006430784 def immutable(b): print(id(b)) # 2006430784 b += 1 print(id(b)) # 2006430816 immutable(b) 通过上面的代码可以看出,修改传进的可变参数时,会对外部对象产生影响,修改不可变参数时则不会影响。 ...

四月 22, 2018 · 1 分钟 · Zhiya

实战 | 用aiohttp和uvloop实现一个高性能爬虫

asyncio 于 Python3.4 引入标准库,增加了对异步 I/O 的支持,asyncio 基于事件循环,可以轻松实现异步 I/O 操作。接下来,我们用基于 asyncio 的库实现一个高性能爬虫。 准备工作 Earth View from Google Earth是一款 Chrome 插件,会在打开新标签页时自动加载一张来自 Google Earth 的背景图片。 使用 Chrome 开发者工具观察插件的网络请求,我们发现插件会请求一个地址如https://www.gstatic.com/prettyearth/assets/data/v2/1234.json的 JSON 文件,文件中包含了经过 Base64 的图片内容,观察发现,图片的 ID 范围大致在 1000-8000 之间,我们的爬虫就要来爬取这些精美的背景图片。 实现主要逻辑 由于爬取目标是 JSON 文件,爬虫的主要逻辑就变成了爬取 JSON–>提取图片–>保存图片。 requests 是一个常用的 http 请求库,但是由于 requests 的请求都是同步的,我们使用aiohttp这个异步 http 请求库来代替。 async def fetch_image_by_id(item_id): url = f'https://www.gstatic.com/prettyearth/assets/data/v2/{item_id}.json' # 由于URL是https的,所以选择不验证SSL async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.get(url) as response: # 获取后需要将JSON字符串转为对象 try: json_obj = json.loads(await response.text()) except json.decoder.JSONDecodeError as e: print(f'Download failed - {item_id}.jpg') return # 获取JSON中的图片内容字段,经过Base64解码成二进制内容 image_str = json_obj['dataUri'].replace('data:image/jpeg;base64,', '') image_data = base64.b64decode(image_str) save_folder = dir_path = os.path.dirname( os.path.realpath(__file__)) + '/google_earth/' with open(f'{save_folder}{item_id}.jpg', 'wb') as f: f.write(image_data) print(f'Download complete - {item_id}.jpg') aiohttp 基于 asyncio,所以在调用时需要使用 async/await 语法糖,可以看到,由于 aiohttp 中提供了一个 ClientSession 上下文,代码中使用了 async with 的语法糖。 ...

四月 10, 2018 · 1 分钟 · Zhiya

与面试官谈笑风生 | Python面向对象之访问控制

Python 从设计之初就是一门面向对象的语言,面向对象思想的第一个要素就是封装。所谓封装,通俗的讲就是类中的属性和方法,分为公有和私有,公有可以被外界访问,私有不能被外界访问,这就是封装中最关键的概念——访问控制。 访问控制有三种级别:私有、受保护、公有 私有(Private):只有类自身可以访问 受保护(Protected):只有类自身和子类可以访问 公有(Public):任何类都可以访问 由于 Python 不像 Java,有访问控制符(private / public / protected),所以 Python 的访问控制也是容易被应聘者忽视和搞错的。 公有(Public) 在 Python 的类中,默认情况下定义的属性都是公有的。 class Foo(object): bar = 123 def __init__(self, bob): self.bob = bob print(Foo.bar) # 123 foo = Foo(456) print(foo.bob) # 456 上面类Foo中的bar属性就是类属性,__init__方法中定义的 bob 是实例属性,bar和bob都是公有的属性,外部可以访问,分别 print 类中的bar和实例中的bob,输出了对应的值。 受保护(Protected) 在 Python 中定义一个受保护的属性,只需要在其名字前加一个下划线_,我们将 Foo 方法中的bob和bar改为_bob和_bar,他们就变成了受保护的属性了,代码如下: class Foo(object): _bar = 123 def __init__(self, bob): self._bob = bob class Son(Foo): def print_bob(self): print(self._bob) @classmethod def print_bar(cls): print(cls._bar) Son.print_bar() # 123 son = Son(456) son.print_bob() # 456 定义一个类Son继承自Foo,由于受保护的对象只能在类的内部和子类中被访问,不能直接调用print(Son._bar)或print(son._bob)来输出这两个属性的值,所以定义了print_bar和print_bob方法,实现在子类中输出,这段代码也正常的输出了_bar和_bob的值。 ...

三月 30, 2018 · 1 分钟 · Zhiya