class
是 Python 语句。class
语句是对象的创建者并且是一个隐含的赋值运算——执行时,它会产生类对象,并把引用值存储在前面所使用的变量名。
存储数据#
使用类来管理数据:
class SharedData:
spam = 42 # 由所有实例共享
# 创建两个实例
x = SharedData()
y = SharedData()
x.spam, y.spam
(42, 42)
x.spam = 7 # 修改实例 x
x.spam, y.spam # y 没有被修改
(7, 42)
SharedData.spam = 45 # 通过类修改数据
x.spam, y.spam # y 被修改
(7, 45)
如果想要同时修改 x
、 y
,你需要这样:
SharedData.spam = 45
x = SharedData()
y = SharedData()
x.spam, y.spam # x, y 均被修改
(45, 45)
id(x) == id(y)
False
x.spam = 7 # 修改实例 x
x.spam, y.spam # y 没有被修改
(7, 45)
命名空间#
无点号运算的变量名(如,X)与作用域相对应
点号的属性名(例如,object.X)使用的是对象的命名空间
有些作用域会对对象的命名空间进行初始化(模块和类)
类与模块#
模块
是数据/逻辑包
通过编写 Python 文件或 C 扩展来创建
通过导入来使用
类
实现新的对象
由
class
语句创建通过调用来使用
总是位于一个模块中
运算符重载#
运算符重载只是意味着在类方法中拦截内置的操作——当类的实例出现在内置操作中,Python 自动调用你的方法,并且你的方法的返回值变成了相应操作的结果。
运算符重载允许类拦截常规的 Python 操作。
类可以重载所有 Python 表达式运算符。
类还可以重载内置操作, 如打印、函数调用、属性访问等.
重载使类实例的作用更类似于内置类型。
通过在类中提供特殊命名的方法来实现重载
Constructors and Expressions: __init__
and __sub__
#
作为审查, 请考虑以下简单示例: 它的 Number 类, 编码在文件 number.py
中, 提供一种方法来拦截实例构造 (__init__
), 以及一个用于捕获减法表达式 (__sub__
)。特殊的方法, 如这些是挂钩, 让您连接到内置的操作:
# File number.py
class Number:
def __init__(self, start): # On Number(start)
self.data = start
def __sub__(self, other): # On instance - other
return Number(self.data - other) # Result is a new instance
from number import Number # Fetch class from module
X = Number(5) # Number.__init__(X, 5)
Y = X - 2 # Number.__sub__(X, 2)
Y.data # Y is new Number instance
3
常用的运算符重载方法#
方法 |
重载 |
调用 |
---|---|---|
|
Constructor,构造器 |
建立对象: |
|
Destructor,析构器 |
Object reclamation(回收) of X |
|
|
|
|
\(\vert\) (bitwise OR) |
|
|
Printing, conversions(转换) |
|
|
Function calls(函数调用) |
|
|
Attribute fetch(提取特征或属性) |
|
|
Attribute assignment,属性赋值 |
|
|
属性删除 |
|
|
Attribute fetch |
|
|
Indexing, slicing, iteration |
|
|
Index and slice assignment |
|
|
Index and slice deletion |
|
|
长度 |
|
|
Boolean tests |
|
|
Comparisons |
|
|
Right-side operators,右侧加法 |
|
|
In-place augmented operators,实地(增强的)加法 |
|
|
Iteration contexts |
|
|
Membership test,成员关系测试 |
|
|
Integer value |
|
|
Context manager |
|
|
Descriptor attributes |
|
|
Creation |
Object creation, before |
Indexing and Slicing: __getitem__
and __setitem__
#
索引(__getitem__
)和切片(__setitem__
):当实例 X
出现 X[i]
这样的索引运算时,Python 会调用这个实例继承的 __getitem__
方法(如果有的话),把 X
当作第一个参数传递,并且方括号内的索引值传递给第二个参数。
class Indexer:
def __getitem__(self, index):
return index ** 2
X = Indexer()
X[2]
4
for i in range(5):
print(X[i], end=' ')
0 1 4 9 16
拦截分片#
__getitem__
也实现分片操作:
L = [5, 6, 7, 8, 9]
L[2:4]
[7, 8]
L[:-2]
[5, 6, 7]
L[::2]
[5, 7, 9]
L[slice(2, 4)]
[7, 8]
L[slice(1, None)]
[6, 7, 8, 9]
L[slice(None,-2)]
[5, 6, 7]
L[slice(None, None, 2)]
[5, 7, 9]
重载:
class Indexer:
data = [5, 6, 7, 8, 9]
def __getitem__(self, index): # Called for index or slice
print('getitem:', index)
return self.data[index] # Perform index or slice
X = Indexer()
X[0]
getitem: 0
5
X[1]
getitem: 1
6
X[-1]
getitem: -1
9
X[2:4]
getitem: slice(2, 4, None)
[7, 8]
X[::2]
getitem: slice(None, None, 2)
[5, 7, 9]
If used, the __setitem__
index assignment method similarly intercepts both index and slice assignments it receives a slice object for the latter, which may be passed along in another index assignment or used directly in the same way:
class IndexSetter:
def __setitem__(self, index, value): # Intercept index or slice assignment
...
self.data[index] = value # Assign index or slice
In fact,__getitem__
may be called automatically in even more contexts than indexing and slicing—it’s also an iteration fallback option, as we’ll see in a moment.
Index Iteration: __getitem__
#
class StepperIndex:
def __getitem__(self, i):
return self.data[i]
X = StepperIndex()
X.data = 'Spam'
X[1]
'p'
for item in X:
print(item, end=' ')
S p a m
'p' in X # All call __getitem__ too
True
[c for c in X] # List comprehension
['S', 'p', 'a', 'm']
list(map(str.upper, X)) # map calls
['S', 'P', 'A', 'M']
(a, b, c, d) = X # Sequence assignments
a, c, d
('S', 'a', 'm')
list(X), tuple(X), ''.join(X) # And so on...
(['S', 'p', 'a', 'm'], ('S', 'p', 'a', 'm'), 'Spam')
X
<__main__.StepperIndex at 0x1c0ef8b3a20>
Iterable Objects: __iter__
and __next__
#
Python 中的所有迭代 context 都将先尝试 __iter__
方法, 然后再尝试 __getitem__
。
# File squares.py
class Squares:
def __init__(self, start, stop): # Save state when created
self.value = start - 1
self.stop = stop
def __iter__(self): # Get iterator object on iter
return self
def __next__(self): # Return a square on each iteration
if self.value == self.stop: # Also called by next built-in
raise StopIteration
self.value += 1
return self.value ** 2
for i in Squares(1, 5):
print(i, end=' ')
1 4 9 16 25
下面实现了跳跃迭代:
# File skipper.py
class SkipObject:
def __init__(self, wrapped): # Save item to be used
self.wrapped = wrapped
def __iter__(self):
return SkipIterator(self.wrapped) # New iterator each time
class SkipIterator:
def __init__(self, wrapped):
self.wrapped = wrapped # Iterator state information
self.offset = 0
def __next__(self):
if self.offset >= len(self.wrapped): # Terminate iterations
raise StopIteration
else:
item = self.wrapped[self.offset] # else return and skip
self.offset += 2
return item
if __name__ == '__main__':
alpha = 'abcdef'
skipper = SkipObject(alpha) # Make container object
I = iter(skipper) # Make an iterator on it
print(next(I), next(I), next(I)) # Visit offsets 0, 2, 4
for x in skipper: # for calls __iter__ automatically
for y in skipper: # Nested fors call __iter__ again each time
print(x + y, end=' ') # Each iterator has its own state, offset
a c e
aa ac ae ca cc ce ea ec ee
Coding Alternative: __iter__
plus yield#
由于生成器函数会自动保存局部变量状态并创建所需的迭代器方法, 因此它们非常适合最小化用户定义的自定义迭代器的编码要求, 并补充了从类中获取的状态保留和其他实用程序。
As a review, recall that any function that contains a yield statement is turned into a generator function. When called, it returns a new generator object with automatic retention of local scope and code position, an automatically created
__iter__
method that simply returns itself, and an automatically created__next__
method that starts the function or resumes it where it last left off:
def gen(x):
for i in range(x):
yield i ** 2
G = gen(5) # Create a generator with __iter__ and __next__
G.__iter__() == G # Both methods exist on the same object
True
I = iter(G)
next(I), next(I)
(0, 1)
list(gen(5))
[0, 1, 4, 9, 16]
即使具有yield
的生成器函数恰好是名为 __iter__
的方法, 这仍然是正确的: 每当迭代上下文工具调用时, 此类方法将返回具有必需 __next__
的新生成器对象。作为额外的奖励, 在类中编码为方法的生成器函数可以访问实例属性和局部范围变量中的已保存状态。
# File squares_yield.py
class Squares: # __iter__ + yield generator
def __init__(self, start, stop): # __next__ is automatic/implied
self.start = start
self.stop = stop
def __iter__(self):
for value in range(self.start, self.stop + 1):
yield value**2
for i in Squares(1, 5):
print(i, end=' ')
1 4 9 16 25
S = Squares(1, 5) # Runs __init__: class saves instance state
S
<__main__.Squares at 0x1c0ff230160>
I = iter(S) # Runs __iter__: returns a generator
next(I), next(I), next(I), next(I), next(I)
(1, 4, 9, 16, 25)
next(I)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-88-cba4a91f39e1> in <module>()
----> 1 next(I)
StopIteration:
Membership: __contains__
, __iter__
, and __getitem__
#
在迭代域中, 类可以使用 __iter__
或 __getitem__
方法实现 in
成员资格运算符作为迭代。不过, 为了支持更具体的成员身份, 类可以编写 __contains__
方法——当存在时, 此方法优先于 __iter__
, __iter__
优于 __getitem__
。__contains__
方法应将成员身份定义为应用于映射的键 (并且可以使用快速查找), 并作为对序列的搜索。
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i): # Fallback for iteration
print('get[%s]:' % i, end='') # Also for index, slice
return self.data[i]
def __next__(self):
print('next:', end='')
if self.ix == len(self.data):
raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
if __name__ == '__main__':
X = Iters([1, 2, 3, 4, 5]) # Make instance
print(3 in X) # Membership
for i in X: # for loops
print(i, end=' | ')
print()
# Other iteration contexts print( list(map(bin, X)) )
print([i**2 for i in X])
# Manual iteration (what other contexts do)
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
get[0]:get[1]:get[2]:True
get[0]:1 | get[1]:2 | get[2]:3 | get[3]:4 | get[4]:5 | get[5]:
get[0]:get[1]:get[2]:get[3]:get[4]:get[5]:[1, 4, 9, 16, 25]
get[0]:1 @ get[1]:2 @ get[2]:3 @ get[3]:4 @ get[4]:5 @ get[5]:
可以看出上面的显示很乱,为此我们重载 __iter__
方法:
class Iters1(Iters):
def __iter__(self): # Preferred for iteration
print('iter=> ', end='') # Allows only one active iterator
self.ix = 0
return self
if __name__ == '__main__':
X = Iters1([1, 2, 3, 4, 5]) # Make instance
print(3 in X) # Membership
for i in X: # for loops
print(i, end=' | ')
print()
# Other iteration contexts print( list(map(bin, X)) )
print([i**2 for i in X])
# Manual iteration (what other contexts do)
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
iter=> next:next:next:True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:
还可以进一步的改进:
class Iters2(Iters1):
def __contains__(self, x): # Preferred for 'in'
print('contains: ', end='')
return x in self.data
if __name__ == '__main__':
X = Iters2([1, 2, 3, 4, 5]) # Make instance
print(3 in X) # Membership
for i in X: # for loops
print(i, end=' | ')
print()
# Other iteration contexts print( list(map(bin, X)) )
print([i**2 for i in X])
# Manual iteration (what other contexts do)
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
contains: True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:
最后我们将其合并为:
class Iters:
def __init__(self, value):
self.data = value
def __getitem__(self, i): # Fallback for iteration
print('get[%s]:' % i, end='') # Also for index, slice
return self.data[i]
def __iter__(self): # Preferred for iteration
print('iter=> ', end='') # Allows only one active iterator
self.ix = 0
return self
def __next__(self):
print('next:', end='')
if self.ix == len(self.data):
raise StopIteration
item = self.data[self.ix]
self.ix += 1
return item
def __contains__(self, x): # Preferred for 'in'
print('contains: ', end='')
return x in self.data
if __name__ == '__main__':
X = Iters([1, 2, 3, 4, 5]) # Make instance
print(3 in X) # Membership
for i in X: # for loops
print(i, end=' | ')
print()
# Other iteration contexts print( list(map(bin, X)) )
print([i**2 for i in X])
# Manual iteration (what other contexts do)
I = iter(X)
while True:
try:
print(next(I), end=' @ ')
except StopIteration:
break
contains: True
iter=> next:1 | next:2 | next:3 | next:4 | next:5 | next:
iter=> next:next:next:next:next:next:[1, 4, 9, 16, 25]
iter=> next:1 @ next:2 @ next:3 @ next:4 @ next:5 @ next:
正如我们所看到的, __getitem__
方法更通用: 除了迭代, 它还截获显式索引以及切片。切片表达式使用包含边界的切片对象触发 __getitem__
, 无论是内置类型还是用户定义类, 因此切片在我们的类中是自动的:
X = Iters('spam')
X[0]
get[0]:
's'
'spam'[1:]
'pam'
'spam'[slice(1, None)]
'pam'
X[1:]
get[slice(1, None, None)]:
'pam'
X[:-1]
get[slice(None, -1, None)]:
'spa'
list(X)
iter=> next:next:next:next:next:
['s', 'p', 'a', 'm']
Attribute Access: __getattr__
and __setattr__
#
__getattr__
方法是拦截属性点号运算的。具体地说, 对于从类创建的对象, 点运算符表达式对象. 属性也可以由代码实现, 用于引用、赋值和删除上下文。具体地说, 对于从类创建的对象, 点运算符表达式 object.attribute
也可以由代码实现, 用于引用、赋值和删除上下文。
class Empty:
def __getattr__(self, attrname): # On self.undefined
if attrname == 'age':
return 40
else:
raise AttributeError(attrname)
X = Empty()
X.age
40
age
变成了一个 dynamically computed attribute(动态计算的属性)—its value is formed by running code, not fetching an object.
The
__getattribute__
method intercepts all attribute fetches, not just those that are undefined, but when using it you must be more cautious than with__get attr__
to avoid loops.The property built-in function allows us to associate methods with fetch and set operations on a specific class attribute.
Descriptors provide a protocol for associating
__get__
and__set__
methods of a class with accesses to a specific class attribute. - Slots attributes are declared in classes but create implicit storage in each instance.
Call 表达式: __call__
#
Python 为应用于您的实例的函数调用表达式运行 __call__
方法,这样可以让类实例类似于函数。
class Callee:
def __call__(self, *pargs, **kargs): # Intercept instance calls
print('Called:', pargs, kargs) # Accept arbitrary arguments
C = Callee()
C(1, 2, 3)
Called: (1, 2, 3) {}
C(1, 2, 3, x=4, y=5)
Called: (1, 2, 3) {'x': 4, 'y': 5}
更正式的说,所有的参数传递方式,__call__
方法都支持:
class C:
def __call__(self, a, b, c=5, d=6): ... # Normals and defaults
class C:
def __call__(self, *pargs, **
kargs): ... # Collect arbitrary arguments
class C:
def __call__(self, *pargs, d=6, **kargs): ... # keyword-only argument
都匹配如下调用方式:
X = C()
X(1, 2) # Omit defaults
X(1, 2, 3, 4) # Positionals
X(a=1, b=2, d=4) # Keywords
X(*[1, 2], **dict(c=3, d=4)) # Unpack arbitrary arguments
X(1, *(2, ), c=3, **dict(d=4)) # Mixed modes