目录 | 上一节 (4.4 异样) | [下一节 (5.2 封装)]()
5.1 再谈字典
Python 对象零碎次要基于字典实现。本节将对此进行探讨。
字典
字典是命名值(named values)的汇合。
stock = {
'name' : 'GOOG',
'shares' : 100,
'price' : 490.1
}
尽管字典罕用于简略的数据结构,然而字典也用于解释器的要害局部。字典可能是 Python 中最重要的数据类型。
字典和模块
在模块内,字典存储所有的全局变量和函数。
# foo.py
x = 42
def bar():
...
def spam():
...
能够通过 foo.__dict__
或 globals()
查看该字典。
{
'x' : 42,
'bar' : <function bar>,
'spam' : <function spam>
}
字典和对象
用户定义对象的时候也应用到了实例字典和类字典。事实上,整个对象零碎次要是基于字典实现的。
字典存储实例数据,如 __dict__
:
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name' : 'GOOG', 'shares' : 100, 'price': 490.1}
当给 self
赋值的时候,你将填充该字典(和实例)。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
实例数据 self.__dict__
看起来像上面这样:
{
'name': 'GOOG',
'shares': 100,
'price': 490.1
}
每一个实例都领有本人的公有字典。
s = Stock('GOOG', 100, 490.1) # {'name' : 'GOOG','shares' : 100, 'price': 490.1}
t = Stock('AAPL', 50, 123.45) # {'name' : 'AAPL','shares' : 50, 'price': 123.45}
如果你创立了某个类的 100 个实例,那么就会有 100 个存储数据的字典。
类成员
一个独自的字典也存储办法:
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
应用 Stock.__dict__
能够查看该字典:
{
'cost': <function>,
'sell': <function>,
'__init__': <function>
}
实例和类
实例和类是链接在一起的。实例通过 __class__
属性指向类。
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1}
>>> s.__class__
<class '__main__.Stock'>
>>>
实例字典存储的数据对每个实例而言是惟一的。然而,类字典存储的数据被该类的所有实例共享。
属性拜访
应用对象时,能够通过 .
运算符拜访数据和办法。
x = obj.name # Getting
obj.name = value # Setting
del obj.name # Deleting
这些操作间接与字典绑定到一起。
批改实例
批改对象的操作会更新底层字典:
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name':'GOOG', 'shares': 100, 'price': 490.1}
>>> s.shares = 50 # Setting
>>> s.date = '6/7/2007' # Setting
>>> s.__dict__
{'name': 'GOOG', 'shares': 50, 'price': 490.1, 'date': '6/7/2007'}
>>> del s.shares # Deleting
>>> s.__dict__
{'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007'}
>>>
读取属性
假如你要读取实例上的属性:
x = obj.name
该属性可能位于两个中央:
- 部分实例字典
- 类字典
两种字典都会被查看到。首先,查看部分实例字典 __dict__
。如果没有找到,通过 __class__
查找类字典 __dict__
。
>>> s = Stock(...)
>>> s.name
'GOOG'
>>> s.cost()
49010.0
>>>
通过这样的查找模式,类成员被所有实例共享。
继承的工作原理
一个类可能继承自其它类:
class A(B, C):
...
在每个类中,父类存储在一个元组中:
>>> A.__bases__
(<class '__main__.B'>, <class '__main__.C'>)
>>>
子类通过 __bases__
属性能够链接到父类。
多继承中的属性查找
从逻辑上讲,查找属性的过程如下:首先,查看部分字典 __dict__
。如果没有找到,查看类字典 __dict__
。如果在类中还是没有找到,通过 __bases__
属性在父类中查找。这外面有一些小细节,咱们接下来探讨。
单继承中的属性查找
在继承层级构造中,通过按程序遍历继承树来找到属性。
class A: pass
class B(A): pass
class C(A): pass
class D(B): pass
class E(D): pass
在单继承中,因为达到下层父类的门路只有一条,所以当找到第一个匹配的属性时即可进行。
办法解析程序(MRO)
Python 会事后计算继承链并将其存储到类的 MRO 属性中。你能够像这样查看:
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.D'>,
<class '__main__.B'>, <class '__main__.A'>,
<type 'object'>)
>>>
该继承链称为 办法解析程序(Method Resolution Order)。为了找到属性,Python 按程序遍历 MRO,第一个匹配的属性即是要找的属性。(译注:无关 MRO 的更多信息,请查看 https://www.python.org/downlo…)。
多继承中的办法解析程序
应用多继承时,达到下层父类的门路有很多条,请看示例:
class A: pass
class B: pass
class C(A, B): pass
class D(B): pass
class E(C, D): pass
拜访属性时会产生什么?
e = E()
e.attr
会执行属性查找,那么按什么程序查找呢?这是个问题。
Python 应用的是 合作多重继承(cooperative multiple inheritance),合作多继承恪守的类排序规定如下:
- 总是在查看父类之前查看子类
- 父类(如果有多个)总是依照列出的程序查看
依据该规定,通过按层级构造对所有的类进行排序,而后计算出办法解析程序。
>>> E.__mro__
(
<class 'E'>,
<class 'C'>,
<class 'A'>,
<class 'D'>,
<class 'B'>,
<class 'object'>)
>>>
底层算法称为“C3 线性化算法(C3 Linearization Algorithm)”,确切的细节不重要,只有记住类层级构造恪守的排序规定与你家房子着火后必须撤退时恪守的规定雷同:首先是孩子,其次是父母。
奇怪的代码重用(波及多继承)
思考以下两个齐全不相干的对象:
class Dog:
def noise(self):
return 'Bark'
def chase(self):
return 'Chasing!'
class LoudDog(Dog):
def noise(self):
# Code commonality with LoudBike (below)
return super().noise().upper()
和
class Bike:
def noise(self):
return 'On Your Left'
def pedal(self):
return 'Pedaling!'
class LoudBike(Bike):
def noise(self):
# Code commonality with LoudDog (above)
return super().noise().upper()
LoudDog.noise()
办法和LoudBike.noise()
办法中有一些通用的代码。事实上,这些通用的代码是齐全一样的。天然,这样的代码势必会吸引软件工程师。
“Mixin” 模式
Mixin 模式(pattern)是蕴含一部分代码片段的类。
class Loud:
def noise(self):
return super().noise().upper()
该类不能独自应用。通过继承和其它类混合应用。
class LoudDog(Loud, Dog):
pass
class LoudBike(Loud, Bike):
pass
神奇的是,noise()
办法只实现了一次,却在两个齐全不相干的类中应用。这种技巧是 Python 多继承的主要用途之一。
为什么应用 super()
当要笼罩一个办法的时候,总是应用 super()
函数。
class Loud:
def noise(self):
return super().noise().upper()
super()
函数代表 MRO 中的 下一个类(译注:LoudDog 的 MRO 是 LoudDog>Loud>Dog>object
。因为 Loud 的父类 object 没有定义 noise() 办法,所以 LoudDog 的实例在 Loud 中找不到 noise() 办法。而后 LoudDog 的实例就会到 MRO 中 Loud 的下一个类 Dog 中寻找)。
麻烦的是你不晓得它是什么,尤其是应用多继承的时候。
注意事项
多继承是一种弱小的机制。应用这种弱小的机制时请牢记“权力越大,责任越大”。有时候,框架或者库应用多继承来实现一些高级个性,如组件组合。
练习
在第 4 节中,定义了一个示意股票持有信息的类 Stock
。在本节练习中,咱们将应用该类。请重新启动解释器并创立一些 Stock
类的实例:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
练习 5.1:实例的示意
在交互式 shell 中,查看 goog
和 ibm
两个实例的底层字典:
>>> goog.__dict__
... look at the output ...
>>> ibm.__dict__
... look at the output ...
>>>
练习 5.2:批改实例属性
尝试给上述其中一个实例增加新属性:
>>> goog.date = '6/11/2007'
>>> goog.__dict__
... look at output ...
>>> ibm.__dict__
... look at output ...
>>>
在上述输入中,你会发现 goog
实例具备 date
属性,然而 ibm
实例没有。重要的是要留神,Python 对实例属性的确没有任何限度。例如,实例属性不限于 __init__()
办法中设置的属性。
尝试间接增加一个新的值到 __dict__
对象中:
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
在这里,你会发现一个事实,实例仅仅是字典顶部的一层。留神:应该强调的是,间接操作字典并不常见——你应该始终应用语法 (.) 编写代码。
练习 5.3:类的作用
类中的定义被类的所有实例所共享。所有的实例都有一个链接,指向它们的关联类:
>>> goog.__class__
... look at output ...
>>> ibm.__class__
... look at output ...
>>>
尝试在实例上调用办法:
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
名字 ‘cost’ 既不在 goog.__dict__
中定义,也不在 ibm.__dict__
中定义。相同,而是由类字典提供的。请尝试以下代码:
>>> Stock.__dict__['cost']
... look at output ...
>>>
尝试间接通过字典调用 cost()
办法:
>>> Stock.__dict__['cost'](goog)
49010.0
>>> Stock.__dict__['cost'](ibm)
4561.5
>>>
你是如何调用类中定义的函数,那么 self
就是怎么调用实例的。
尝试给 Stock
类增加新属性::
>>> Stock.foo = 42
>>>
该新属性会呈现在所有实例中:
>>> goog.foo
42
>>> ibm.foo
42
>>>
然而,foo
并不属于实例字典:
>>> goog.__dict__
... look at output and notice there is no 'foo' attribute ...
>>>
你能够拜访 foo
属性的起因是:当 Python 在实例字典中查找不到某个属性时,那么它就会到类字典中查找。
留神:本局部次要说明什么是类变量。假如你有这样一个类:
class Foo(object):
a = 13 # Class variable
def __init__(self,b):
self.b = b # Instance variable
在 Foo 类中,因为变量 a
在类体(body of the class)中被赋值,所以 a
是“类变量(class variable)”。变量 a
能够被 Foo 类的所有实例所共享。示例:
>>> f = Foo(10)
>>> g = Foo(20)
>>> f.a # Inspect the class variable (same for both instances)
13
>>> g.a
13
>>> f.b # Inspect the instance variable (differs)
10
>>> g.b
20
>>> Foo.a = 42 # Change the value of the class variable
>>> f.a
42
>>> g.a
42
>>>
练习 5.4:绑定办法
Python 有一个奥妙的个性:调用办法实际上波及两个步骤以及一个称为绑定办法的货色。示例:
>>> s = goog.sell
>>> s
<bound method Stock.sell of Stock('GOOG', 100, 490.1)>
>>> s(25)
>>> goog.shares
75
>>>
实际上,绑定办法蕴含调用一个办法的所需的所有内容。例如,它们记录了实现办法的函数:
>>> s.__func__
<function sell at 0x10049af50>
>>>
这与在 Stock
字典中找到的值是一样的:
>>> Stock.__dict__['sell']
<function sell at 0x10049af50>
>>>
绑定办法还记录实例,即 self
:
>>> s.__self__
Stock('GOOG',75,490.1)
>>>
你能够应用 ()
一起调用所有的函数。例如,调用 s(25)
理论是这样做的:
>>> s.__func__(s.__self__, 25) # Same as s(25)
>>> goog.shares
50
>>>
练习 5.5:继承
创立一个继承自 Stock
的类:
>>> class NewStock(Stock):
def yow(self):
print('Yow!')
>>> n = NewStock('ACME', 50, 123.45)
>>> n.cost()
6172.50
>>> n.yow()
Yow!
>>>
通过扩大属性的搜寻过程来实现继承。__bases__
属性是一个蕴含间接父类的元组:
>>> NewStock.__bases__
(<class 'stock.Stock'>,)
>>>
__mro__
属性是一个蕴含所有父类的元组,父类按查找顺序排列。
>>> NewStock.__mro__
(<class '__main__.NewStock'>, <class 'stock.Stock'>, <class 'object'>)
>>>
实例 n
是这样找到 cost()
办法的:
>>> for cls in n.__class__.__mro__:
if 'cost' in cls.__dict__:
break
>>> cls
<class '__main__.Stock'>
>>> cls.__dict__['cost']
<function cost at 0x101aed598>
>>>
目录 | 上一节 (4.4 异样) | [下一节 (5.2 封装)]()
注:残缺翻译见 https://github.com/codists/practical-python-zh