这是《精通python》一书中的一段代码:
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
借这段代码,我们讨论一下几个问题:
(1)Python 中的数据模型;
(2)名字前单下划线和双下划线的含义;
(3)列表推导式和生成器表达式;
(4)python 中的特殊方法;
第一个问题: Python 中的数据模型
初次见到collections中 那花样繁多的数据模型时,我的内心是震撼的。它们的存在使得人们在使用python进行开发时能够享受到极大的便利。
常见的有:'abc','AsyncIterable', 'AsyncIterator', 'ByteString', 'Callable', 'ChainMap', 'Container', 'Counter', 'Generator', 'Hashable', 'ItemsView', 'KeysView', 'Mapping', 'MappingView', 'MutableMapping', 'MutableSequence', 'OrderedDict', 'Sequence', 'Set', 'defaultdict', 'deque', 'namedtuple' 。
就说说上面代码中用到的namedtuple吧。 collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和一个有名字的类——这个带名字的类对调试程序有很大帮助。
创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。存放在对应字段里的数据要以一串参数的形式传入到构造函数中(注意,元组的构造函数却只接受单一的可迭代对象)。你可以通过字段名或者位置来获取一个字段的信息。除了从普通元组那里继承来的属性之外,具名元组还有一些自己专有的属性:_fields 类属性、类方法_make(iterable) 和实例方法 _asdict() 等。
_fields 属性是一个包含这个类所有字段名称的元组。
用 _make() 通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟 City(*delhi_data) 是一样的。
_asdict() 把具名元组以 collections.OrderedDict 的形式返回,我们可以利用它来把元组里的信息友好地呈现出来。
元组是一种很强大的可以当作记录来用的数据类型。
第二个问题:名字前单下划线和双下划线的含义
单下划线:在一个类中的方法或属性用单下划线开头就是告诉别的程序这个属性或方法是私有的。然而对于这个名字来说并没有什么特别的。
双下划线:任何__spam
形式(至少两个下划线开头,至多一个下划线结尾)都是_classname__spam
的代替,其中classname是当前类的名字.
看一个例子:
>>> class MyClass():
def __init__(self):
self.__superprivate = "Hi!"
self._semiprivate = "Wuming!"
>>> obj = MyClass()
>>> print(obj.__superprivate)
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
print(obj.__superprivate)
AttributeError: 'MyClass' object has no attribute '__superprivate'
>>> print(obj._semiprivate)
Wuming!
>>> print(_MyClass__superprivate)
Traceback (most recent call last):
File "<pyshell#11>", line 1, in <module>
print(_MyClass__superprivate)
NameError: name '_MyClass__superprivate' is not defined
>>> print(obj._MyClass__superprivate)
Hi!
>>>
从中我们看到单下划线开头的_semiprivate可以直接输出,但是当我们直接输出双下划线开头的__superprivate就会报错,而输出obj._MyClass__superprivate是没问题的,严格来讲既然用双下划线开头就是不想被调用,但是python总是会给我们留个后门。
总结:
__foo__
: 一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突;
_foo
: 一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式;
__foo
: 这个有真正的意义:解析器用_classname__foo
来代替这个名字,以区别和其他类相同的命名;
在Python中没有其他形式的下划线了。
第三个问题:列表推导式和生成器表达式
列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列。
看一个例子:
# 把一个元组嵌套元组的数据变成一个包含每个元组第二个元素的列表
t = ((1, 'haha'), (2, 'hehe'), (3, 'heihei'))
# 方法一
l = []
for x in t:
l.append(x[1])
print(l)
# 方法二
l1 = [i[1] for i in t]
print(l1)
['haha', 'hehe', 'heihei']
['haha', 'hehe', 'heihei']
我们看到输出是一样的,第一种方法更类似于其他语言,虽说任何学过一点 Python 的人应该都能看懂方法一,但是我觉得如果学会了列表推导的话,方法二读起来更方便,因为这段代码的功能从字面上就能轻松地看出来,并且这会让你的代码看起来更加pythonic。
正如开篇纸牌类中:
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
它的作用是生成一个按花色分组的 52 张牌的列表,其中每个花色各有 13 张不同点数的牌。
这里其实是列表推导式的另一个很好的运用:使用列表推导计算笛卡儿积。这里的笛卡儿积是一个列表,列表里的元素是由输入的可迭代类型的元素对构成的元组,因此笛卡儿积列表的长度等于输入变量的长度的乘积。
列表推导的作用只有一个:生成列表。如果想生成其他类型的序列,生成器表达式就派上了用场。
虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。前面那种方式显然能够节省内存。
生成器表达式的语法跟列表推导差不多,只不过把方括号换成圆括号而已。
看一个列子:
import array
symbols = 'Hello, the python world!'
x = array.array('I', (ord(symbol) for symbol in symbols))
print(x)
array('I', [72, 101, 108, 108, 111, 44, 32, 116, 104, 101, 32, 112, 121, 116, 104, 111, 110, 32, 119, 111, 114, 108, 100, 33])
需要注意的是:生成器表达式逐个产出元素,当元素量很大的时候节省的内存是非常可观的,因此提升了效率。
第四个问题:python 中的特殊方法
首先明确一点,特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们。也就是说没有 my_object.__len__() 这种写法,而应该使用 len(my_object)。在执行 len(my_object) 的时候,如果my_object 是一个自定义类的对象,那么 Python 会自己去调用其中由你实现的 __len__ 方法。 很多时候,特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是 iter(x),而这个函数的背后则是 x.__iter__() 方法。当然前提是这个方法在 x 中被实现了。通常你的代码无需直接使用特殊方法。除非有大量的元编程存在,直接调用特殊方法的频率应该远远低于你去实现它们的次数。唯一的例外可能是 __init__ 方法,你的代码里可能经常会用到它,目的是在你自己的子类的 __init__ 方法中调用超类的构造器。
常见的特殊方法:
__init__ 、__dict__、__str__、__repr__、__getitem__、__setitem__、__delitem__、__iter__、__len__、__call__、__name__、__moudle__、__class__、__doc__、__abs__、__add__、__mul__、__bool__ 、__missing__ 等。
明晰他们所代表的意义,会使你对python的理解更加深刻,也会更有助于你理解python的类和面向对象。