翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器
原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/
上一篇:翻译《Writing Idiomatic Python》(四):字典、集合、元组
2.7 类
2.7.1 用isinstance函数检查一个对象的类型
许多新手在接触Python之后会产生一种“Python中没有类型”的错觉。当然Python的对象是有类型的,并且还会发生类型错误。比如,对一个int型对象和一个string型的对象使用+操作就会产生TypeError。如果你在写一些需要基于某些变量类型来做出相应操作的代码的话,那么isinstance函数就是你需要的。
isinstance(object, class-or-object-or-tuple)是Python的内建函数,如果第一个object参数和第二个参数,或者其子类型一致,那么返回值为真。如果第二个参数是一个元组的情况,那么当第一个参数的类型是元组中的某一个类型或者其子类型时返回真。需要注意的是尽管在大部分情况下你看到的第二个参数都是内建类型,但是这个函数可以用于任何类型,包括用户创建的类。
// 原书里写的就是class-or-object-or-tuple,其实比较容易混淆,写成class-or-type-or-tuple也许更合适,另外和用type比较的用法比起来,其实两种方法一般情况并无优劣之分,作者这里有些主观了,主要的差别是isinstance会把子类也返回真。比如在Py2里一般的Python字符串和unicode字符串,如果用isinstance比较basestring则会返回真,但是如果用type则可以分辨出他们的区别,根据情况需要才能决定是用isinstance还是type
不良风格:
def get_size(some_object):
"""Return the "size" of *some_object*, where size = len(some_object) for
sequences, size = some_object for integers and floats, and size = 1 for
True, False, or None."""
try:
return len(some_object)
except TypeError:
if some_object in (True, False, type(None)):
return 1
else:
return int(some_object) print(get_size('hello'))
print(get_size([1, 2, 3, 4, 5]))
print(get_size(10.0))
地道Python:
def get_size(some_object):
if isinstance(some_object, (list, dict, str, tuple)):
return len(some_object)
elif isinstance(some_object, (bool, type(None))):
return 1
elif isinstance(some_object, (int, float)):
return int(some_object) print(get_size('hello'))
print(get_size([1, 2, 3, 4, 5]))
print(get_size(10.0))
2.7.2 使用下划线作为开头命名的变量和函数表明私有性
在Python的一个类中,无论是变量还是函数,都是共有的。用户可以自由地在一个类已经定义之后添加新的属性。除此以外,当继承一个类的时候,因为这种自由性,用户还可能无意中改变基类的属性。最后,虽然所有的变量/属性都可以被访问,但是在逻辑上表明哪些变量是是公有的,哪些是私有或是受保护的还是非常有用的。
所以在Python中有一些被广泛使用的命名上的传统用来表明一个类作者(关于私有性公有性)的意图,比如接下来要介绍的两种用法。对于这两种用法,虽然普遍认为是惯用法,但是事实上在使用中会使编译器也产生不同的行为。
第一个,用单下划线开始命名的表明是受保护的属性,用户不应该直接访问。第二个,用两个连续地下划线开头的属性,表明是私有的,即使子类都不应该访问。当然了,这并不能像其他一些语言中那样真正阻止用户访问到这些属性,但这都是在整个Python社区中被广泛使用的传统,从某种角度上来说这也是Python里用一种办法完成一件事情哲学的体现。
前面曾提到用一个或两个下划线命名的方式不仅仅是传统。一些开发者意识到这种写法是有实际作用的。以单下划线开头的变量在import *时不会被导入。以双下划线开头的变量则会触发Python中的变量名扎压(name mangling),比如如果Foo是一个类,那么在Foo中定义的一个名字会被展开成_classname__attributename.
不良风格:
class Foo(object):
def __init__(self):
self.id = 8
self.value = self.get_value() def get_value(self):
pass def should_destroy_earth(self):
return self.id == 42 class Baz(Foo):
def get_value(self, some_new_parameter):
"""Since 'get_value' is called from the base class's
__init__ method and the base class definition doesn't
take a parameter, trying to create a Baz instance will
fail
"""
pass class Qux(Foo):
"""We aren't aware of Foo's internals, and we innocently
create an instance attribute named 'id' and set it to 42.
This overwrites Foo's id attribute and we inadvertently
blow up the earth.
"""
def __init__(self):
super(Qux, self).__init__()
self.id = 42
# No relation to Foo's id, purely coincidental q = Qux()
b = Baz() # Raises 'TypeError'
q.should_destroy_earth() # returns True
q.id == 42 # returns True
地道Python:
class Foo(object):
def __init__(self):
"""Since 'id' is of vital importance to us, we don't
want a derived class accidentally overwriting it. We'll
prepend with double underscores to introduce name
mangling.
"""
self.__id = 8
self.value = self.__get_value() # Call our 'private copy' def get_value(self):
pass def should_destroy_earth(self):
return self.__id == 42 # Here, we're storing a 'private copy' of get_value,
# and assigning it to '__get_value'. Even if a derived
# class overrides get_value in a way incompatible with
# ours, we're fine
__get_value = get_value class Baz(Foo):
def get_value(self, some_new_parameter):
pass class Qux(Foo):
def __init__(self):
"""Now when we set 'id' to 42, it's not the same 'id'
that 'should_destroy_earth' is concerned with. In fact,
if you inspect a Qux object, you'll find it doesn't
have an __id attribute. So we can't mistakenly change
Foo's __id attribute even if we wanted to.
"""
self.id = 42
# No relation to Foo's id, purely coincidental
super(Qux, self).__init__() q = Qux()
b = Baz() # Works fine now
q.should_destroy_earth() # returns False
q.id == 42 # returns True
2.7.3 使用properties来获得更好的兼容性
许多时候提供直接访问类数据的属性会让类更方便使用。比如一个Point类,直接使用x和y的属性回避使用'getter'和'setter'这样的函数更加好用。然而'getters'和'setters'的存在也并不是没有原因的:你并不能确定有的时候某个属性会不会需要(比如在子类中)被某个计算所替代。假设我们有一个Product类,这个类会被产品的名字和价格初始化。我们可以简单地直接设置产品名称和价格的成员变量,然而如果我们在稍后的需求中需要自动计算并将产品的税也加到价格中的话,那么我们就会需要对所有的价格变量进行修改。而避免这样做的办法就是将价格设置为一个属性(property)。
不良风格:
class Product(object):
def __init__(self, name, price):
self.name = name
# We could try to apply the tax rate here, but the object's price
# may be modified later, which erases the tax
self.price = price
地道Python:
class Product(object):
def __init__(self, name, price):
self.name = name
self._price = price @property
def price(self):
# now if we need to change how price is calculated, we can do it
# here (or in the "setter" and __init__)
return self._price * TAX_RATE @price.setter
def price(self, value):
# The "setter" function must have the same name as the property
self._price = value
2.7.4 使用__repr__生成机器可读的类的表示
在一个类中__str__用来输出对于人可读性好的字符串,__repr__用来输出机器可求值的字符串。Python默认的一个类的__repr__实现没有任何作用,并且要实现一个对所有Python类都有效的默认的__repr__是很困难的。__repr__需要包含所有的用于重建该对象的信息,并且需要尽可能地能够区分两个不同的实例。一个简单地原则是,如果可能的话,eval(repr(instance))==instance。在进行日志记录的时候__repr__尤其重要,因为日志中打印的信息基本上来说都是来源于__repr__而不是__str__。
不良风格:
class Foo(object):
def __init__(self, bar=10, baz=12, cache=None):
self.bar = bar
self.baz = baz
self._cache = cache or {} def __str__(self):
return 'Bar is {}, Baz is {}'.format(self.bar, self.baz) def log_to_console(instance):
print(instance) log_to_console([Foo(), Foo(cache={'x': 'y'})])
地道Python:
class Foo(object):
def __init__(self, bar=10, baz=12, cache=None):
self.bar = bar
self.baz = baz
self._cache = cache or {} def __str__(self):
return '{}, {}'.format(self.bar, self.baz) def __repr__(self):
return 'Foo({}, {}, {})'.format(self.bar, self.baz, self._cache) def log_to_console(instance):
print(instance) log_to_console([Foo(), Foo(cache={'x': 'y'})])
2.7.5 使用__str__生成人可读的类的表示
当定义一个很有可能会被print()用到的类的时候,默认的Python表示就不是那么有用了。定义一个__str__方法可以让print()函数输出想要的信息。
不良风格:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y p = Point(1, 2)
print(p) # Prints '<__main__.Point object at 0x91ebd0>'
地道Python:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y def __str__(self):
return '{0}, {1}'.format(self.x, self.y) p = Point(1, 2)
print(p) # Prints '1, 2'
2.8 上下文管理器
2.8.1 利用上下文管理器确保资源的合理管理
和C++中的RAII(Resource Acquisition Is Initialization,资源获取就是初始化)原则相似,上下文管理器(和with语句一起使用)可以让资源的管理更加安全和清楚。一个典型的例子是文件IO操作。
首先来看不良风格的代码,如果发生了异常,会怎么样?因为在这个例子中我们并没有抓住异常,所以发生异常后会向上传递,则代码会在无法关闭已打开文件的情况下退出。
标准库中有许多的类支持或使用上下文管理器。除此以外,用户自定义的类也可以通过定义__enter__和__exit__方法来支持上下文管理器。如果是函数,也可以通过contextlib来进行封装。
不良风格:
file_handle = open(path_to_file, 'r')
for line in file_handle.readlines():
if raise_exception(line):
print('No! An Exception!')
地道Python:
with open(path_to_file, 'r') as file_handle:
for line in file_handle:
if raise_exception(line):
print('No! An Exception!')
2.9 生成器
2.9.1 对于简单的循环优先使用生成器表达式而不是列表解析
当处理一个序列时,一种很常见的情况是需要每次遍历一个有微小改动的版本的序列。比如,需要打印出所有用户的名字的大写形式。
第一反应当然是用一个即时的表达式实现这种遍历,自然而然地就容易想到列表解析,然而在Python中事实上有更好的内建实现方式:生成器表达式。
那么这两种方式的主要区别在哪里呢?列表解析会产生一个列表对象并且立即产生列表里所有的元素。对于一些大的列表,这通常会带来昂贵的甚至是不可接受的开销。而生成器则返回一个生成器表达式,只有在调用的时候,才产生元素。对于上面提到的例子,也许列表解析还是可以接受的,但是如果我们要打印的不再是大写的名字而是国会图书馆里所有图书的名字的话,产生这个列表可能就已经导致内存溢出了,而生成器表达式则不会这样。
不良风格:
for uppercase_name in [name.upper() for name in get_all_usernames()]:
process_normalized_username(uppercase_name)
地道Python:
for uppercase_name in (name.upper() for name in get_all_usernames()):
process_normalized_username(uppercase_name)
2.9.2 使用生成器延迟加载无限的序列
很多情况下,为一个无限长的序列提供一种方式来遍历是非常有用的。否则你会需要提供一个异常昂贵开销的接口来实现,而用户还需要为此等待很长的时间用于生成进行遍历的列表。
面临这些情况,生成器就是理想的选择当元组作为某,来看下面的例子:
不良风格:
def get_twitter_stream_for_keyword(keyword):
"""Get's the 'live stream', but only at the moment
the function is initially called. To get more entries,
the client code needs to keep calling
'get_twitter_livestream_for_user'. Not ideal.
""" imaginary_twitter_api = ImaginaryTwitterAPI()
if imaginary_twitter_api.can_get_stream_data(keyword):
return imaginary_twitter_api.get_stream(keyword) current_stream = get_twitter_stream_for_keyword('#jeffknupp')
for tweet in current_stream:
process_tweet(tweet) # Uh, I want to keep showing tweets until the program is quit.
# What do I do now? Just keep calling
# get_twitter_stream_for_keyword? That seems stupid. def get_list_of_incredibly_complex_calculation_results(data):
return [first_incredibly_long_calculation(data),
second_incredibly_long_calculation(data),
third_incredibly_long_calculation(data),
]
地道Python:
def get_twitter_stream_for_keyword(keyword):
"""Now, 'get_twitter_stream_for_keyword' is a generator
and will continue to generate Iterable pieces of data
one at a time until 'can_get_stream_data(user)' is
False (which may be never).
""" imaginary_twitter_api = ImaginaryTwitterAPI()
while imaginary_twitter_api.can_get_stream_data(keyword):
yield imaginary_twitter_api.get_stream(keyword) # Because it's a generator, I can sit in this loop until
# the client wants to break out
for tweet in get_twitter_stream_for_keyword('#jeffknupp'):
if got_stop_signal:
break
process_tweet(tweet) def get_list_of_incredibly_complex_calculation_results(data):
"""A simple example to be sure, but now when the client
code iterates over the call to
'get_list_of_incredibly_complex_calculation_results',
we only do as much work as necessary to generate the
current item.
""" yield first_incredibly_long_calculation(data)
yield second_incredibly_long_calculation(data)
yield third_incredibly_long_calculation(data)
转载请注明出处:達聞西@博客园
翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器的更多相关文章
- (转)Python中的上下文管理器和Tornado对其的巧妙应用
原文:https://www.binss.me/blog/the-context-manager-of-python-and-the-applications-in-tornado/ 上下文是什么? ...
- 流畅的python第十五章上下文管理器和else块学习记录
with 语句和上下文管理器for.while 和 try 语句的 else 子句 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文.这么做能避免错误并减少样板代码, ...
- 深入理解 Python 中的上下文管理器
提示:前面的内容较为基础,重点知识在后半段. with 这个关键字,对于每一学习Python的人,都不会陌生. 操作文本对象的时候,几乎所有的人都会让我们要用 with open ,这就是一个上下文管 ...
- 【Python】【上下文管理器】
"""#[备注]#1⃣️try :仅当try块中没有异常抛出时才运行else块.#2⃣️for:仅当for循环运行完毕(即for循环没有被break语句终止)才运行els ...
- Python异常处理与上下文管理器
Python异常处理 异常与错误 错误 可以通过IDE或者解释器给出提示的错误opentxt('a.jpg','r') 语法层面没有问题,但是自己代码的逻辑有问题if age>18: print ...
- Python深入02 上下文管理器
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 上下文管理器(context manager)是Python2.5开始支持的一种语 ...
- Python中的上下文管理器和with语句
Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...
- python中实现上下文管理器的两种方法
上下文管理器: python中实现了__enter__和__exit__方法的对象就可以称之为上下文管理器 实现方法一举例: def File(object): def __init__(self, ...
- Python中的上下文管理器(contextlib模块)
上下文管理器的任务是:代码块执行前准备,代码块执行后收拾 1 如何使用上下文管理器: 打开一个文件,并写入"hello world" filename="my.txt&q ...
- Python - Context Manager 上下文管理器
什么是上下文管理器 官方解释... 上下文管理器是一个对象 它定义了在执行 with 语句时要建立的运行时上下文 上下文管理器处理进入和退出所需的运行时上下文以执行代码块 上下文管理器通常使用 wit ...
随机推荐
- jQuery.extend()方法和jQuery.fn.extend()方法源码分析
这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ...
- jquery实现内容滚动
HTML代码: <div class="scollNews"> <ul> <li><a href="#">1&l ...
- 程序新能优化-SQL优化
- SharePoint Error - An unrecognized HTTP response was received when attempting to crawl this item
SharePoint 2013爬网报错 An unrecognized HTTP response was received when attempting to crawl this item. V ...
- openssh/ntp/ftp漏洞
这3种漏洞常规加固都要对应操作系统打官方漏洞升级包.既然这么说那下面就是不常规的: Openssh: 改ssh版本:whereis ssh //查看ssh目录cd 到该目录cp ssh ssh.bak ...
- Android 视频播放器,在线播放
1. Bilibili https://github.com/Bilibili/ijkplayer 1.测试的时候总是崩溃,不知道是我不会用还是怎么回事. 2016-04-15 2.AndroidVi ...
- Android 动态创建Fragment
Fragment是activity的界面中的一部分或一种行为.可以把多个Fragment组合到一个activity中来创建一个多界面并且可以在多个activity中重用一个Fragment.可以把Fr ...
- 【代码笔记】iOS-判断有无网络
一,工程图. 二,代码. RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController : UIVi ...
- C语言中的循环结构与选择结构
1. 为什么使用循环? 重复执行某段代码 2. while(条件){ 循环体: } 当条件成立的时候就执行循环体,条件不成立,就退出循环,继续执行while后面的语句 3. for ( 初始表达式 : ...
- Ubuntu下面su初始密码设置
rcm@rcm:~$ sudo passwd 输入新的 UNIX 密码: 重新输入新的 UNIX 密码: passwd:已成功更新密码