原书参考: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》(四):字典、集合、元组

翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器的更多相关文章

  1. Python深入02 上下文管理器

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 上下文管理器(context manager)是Python2.5开始支持的一种语 ...

  2. Python中的上下文管理器和with语句

    Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...

  3. python上下文管理器ContextLib及with语句

    http://blog.csdn.net/pipisorry/article/details/50444736 with语句 with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能 ...

  4. python with语句上下文管理的两种实现方法

    在编程中会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作:当语句块执行完成后,需要继续执行一些收尾动作.例如,文件读写后需要关闭,数据库读写完毕需要关闭连接,资源的加 ...

  5. 《Writing Idiomatic Python》前两部分的中文翻译

    汇总了一下这本小书前两部分的内容: 翻译<Writing Idiomatic Python>(一):if语句.for循环 翻译<Writing Idiomatic Python> ...

  6. 翻译《Writing Idiomatic Python》(四):字典、集合、元组

    原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...

  7. 翻译《Writing Idiomatic Python》(二):函数、异常

    原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...

  8. 翻译《Writing Idiomatic Python》(三):变量、字符串、列表

    原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic ...

  9. 翻译《Writing Idiomatic Python》(一):if语句、for循环

    开篇废话 这是在美国Amazon上评价很不错的一本书,其实严格来说这可能不算书,而是一本小册子.就像书名一样,里面的内容主要是用一些例子讲述地道的Python的代码是怎样写的.书中把很多例子用不良风格 ...

随机推荐

  1. 用SSH访问内网主机的方法

    如今的互联网公司通常不会直接自己直接配主机搭建服务器了,而是采用了类似阿里云的这种云主机,当应用变得越来越大了之后,就不可避免地增加主机,而出于成本考虑,不可能给每一台主机都分配公网带宽,所以实际的情 ...

  2. See you~_树状数组

    Problem Description Now I am leaving hust acm. In the past two and half years, I learned so many kno ...

  3. PHP内核的学习--PHP生命周期

    一切的开始: SAPI接口 SAPI(Server Application Programming Interface)指的是PHP具体应用的编程接口, 就像PC一样,无论安装哪些操作系统,只要满足了 ...

  4. WindowXP与WIN7环境安装、破解、配置AppScan8.0

    ---------------------------------------------------------------------------------------------------- ...

  5. awk处理之案例一:awk 处理百分比的问题

    编译环境 本系列文章所提供的算法均在以下环境下编译通过. [脚本编译环境]Federa 8,linux 2.6.35.6-45.fc14.i686 [处理器] Intel(R) Core(TM)2 Q ...

  6. Python [Leetcode 342]Power of Four

    题目描述: Given an integer (signed 32 bits), write a function to check whether it is a power of 4. Examp ...

  7. Linux 下C++编写

    今天搞了一天Linux下C++编程,还没有什么成效.好烦躁好心焦,想砸电脑的冲动.抽根烟理下思路一定要把它拿下!! ===搞了两天,真是搞到生无可恋,试了共享文件, 试了网络配置,各种博客就是各种行不 ...

  8. oc-27-@property的参数

    //01加强-10 @property .4前 ) @property + 手动实现 ) @property int age; + @synthesize age;//get和set方法的声明和实现都 ...

  9. 复杂 Listview 显示 多个样式

    三种方式 目前为止有三种方法让Listview现实多个样式 最简单最常用的,通过addHeaderView或addFooterView,但是只能在首尾添加 较麻烦但正规的方式,通过getViewTyp ...

  10. poj3581Sequence(后缀数组)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Sequence Time Limit: 5000MS   Memory Limi ...