Python 类对象
经典迭代器
-
可迭代对象的定义: 使用内置的iter可以获取迭代器的对象。如果对象实现了能返回迭代器的
__iter__
方法,那么对象就是可迭代的。序列都可以迭代。实现了__getitem__
方法,而且接受从0开始的索引,这种对象也是可以迭代的。 -
可迭代对象与迭代器之间的关系:Python从可迭代对象中获取迭代器。
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class SentenceV2:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return f'Sentence({reprlib.repr(self.text)})'
def __iter__(self):
# 返回一个迭代器
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
# 初始化索引
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
- 可迭代对象有一个
__iter__
方法,每次都实例化一个新迭代器。 - 迭代器要实现
__next__
方法,返回单个元素,此外还要实现__iter__
方法,返回迭代器本身。 - 迭代器也是可迭代对象,但是可迭代对象不是迭代器。
生成器函数
import re
import reprlib
RE_WORD = re.compile(r'\w+')
class SentenceV3:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
# 产生当前的word
yield word
class ArithmeticProgression:
def __init__(self, begin, step, end=None):
self.begin = begin
self.step = step
self.end = end # None -> "infinite" series
def __iter__(self):
result_type = type(self.begin + self.step)
result = result_type(self.begin)
forever = self.end is None
while forever or result < self.end:
yield result
result += self.step
只要Python函数的主体中有yield
关键字,该函数就是生成器函数。调用生成器函数,返回一个生成器对象。
生成器工作原理:
- 生成器函数创建一个生成器对象,包装生成器函数的主体。
- 把生成器对象传给
next()
函数时,生成器函数提前执行函数主体中的下一个yield
语句,返回产出的值,并在函数主体的当前位置暂停。 - 函数的主体返回时,Python创建的外层生成器对象抛出
StopIteration
异常。
上下文管理器
import sys
class LookingGlass:
def __enter__(self):
self.original_write = sys.stdout.write
# 打上猴子补丁
sys.stdout.write = self.reverse_write
return 'JABBERWOCKY'
def reverse_write(self, text):
# 反转参数的内容
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback):
# 将原来的方法还原
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True
Vector2d
from array import array
import math
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
# 可以解包 v = Vector2d(2,2)
# x,y = v
def __iter__(self):
return (i for i in (self.x, self.y))
# print返回
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self):
# 调用__iter__
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(array(self.typecode, self)))
# 判断Vector2d是否相等
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
格式化显示
>>> format(42, 'b')
'101010'
>>> format(2 / 3, '.1%')
'66.7%'
# datetime类重构了__format__方法
>>> from datetime import datetime
>>> now = datetime.now()
>>> format(now, '%H:%M:%S')
'18:49:05'
>>> "It's now {:%I:%M %p}".format(now)
"It's now 06:49 PM"
def __format__(self, fmt_spec=''):
components = (format(c, fmt_spec) for c in self)
return '({}, {})'.format(*components)
>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'
可哈希的
为了把 Vector2d 实例变成可哈希的,必须实现 hash 方法 (还需要 eq 方法,前面已经实现了)。此外,还要让向量实例 不可变
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y)
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
def __hash__(self):
return hash((self.x, self.y))
使用 slots 节省空间
默认情况下,Python 把各个实例的属性存储在一个名为 dict 的字典中字典消耗的内存很多。但是,如果定义一个名为 slots 的类属性,以序列的形式 存储属性名称,那么 Python 将使用其他模型存储实例属性: slots 中的属性名称存储在一个隐藏的引用数组中,消耗的内 存比字典少。
>>> class Pixel:
... __slots__ = ('x', 'y')
...
>>> p = Pixel()
>>> p.__dict__
Traceback (most recent call last):
...
AttributeError: 'Pixel' object has no attribute '__dict__'
>>> p.x = 10
>>> p.y = 20
# 不允许添加其他属性
>>> p.color = 'red'
Traceback (most recent call last):
...
AttributeError: 'Pixel' object has no attribute 'color'
Vector
多维向量
>>> Vector([3.1, 4.2])
Vector([3.1, 4.2])
>>> Vector((3, 4, 5))
Vector([3.0, 4.0, 5.0])
>>> Vector(range(10))
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
from array import array
import reprlib
import math
class Vector:
typecode = 'd'
def __init__(self, components):
self._components = array(self.typecode, components)
def __iter__(self):
return iter(self._components)
def __repr__(self):
# 替换为省略号
# 返回array('d', [0.0, 1.0, 2.0, 3.0, 4.0, ...])
components = reprlib.repr(self._components)
components = components[components.find('['):-1]
return f'Vector({components})'
def __str__(self):
return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) +
bytes(self._components))
# 调用len()返回
def __len__(self):
return len(self._components)
def __eq__(self, other):
if len(self) != len(other):
return False
for a, b in zip(self, other):
if a != b:
return False
return True
def __abs__(self):
return math.hypot(*self)
def __bool__(self):
return bool(abs(self))
切片
def __getitem__(self, index):
return self._components[index]
>>> v1 = Vector([3, 4, 5])
>>> len(v1)
3
>>> v1[0], v1[-1]
(3.0, 5.0)
>>> v7 = Vector(range(7))
# 返回的是array
>>> v7[1:4]
array('d', [1.0, 2.0, 3.0])
def __getitem__(self, key):
# 调用Vector[a:b:c] 传入的是slice(a,b,c)
if isinstance(key, slice):
cls = type(self)
return cls(self._components[key])
# 调用Vector[a]
index = operator.index(key)
return self._components[index]
动态存取属性
# 希望xyzt能获取前4个元素
>>> v = Vector(range(10))
>>> v.x
0.0
>>> v.y, v.z, v.t
(1.0, 2.0, 3.0)
__match_args__ = ('x', 'y', 'z', 't')
def __getattr__(self, name):
cls = type(self)
try:
pos = cls.__match_args__.index(name)
except ValueError:
pos = -1
if 0 <= pos < len(self._components):
return self._components[pos]
msg = f'{cls.__name__!r} object has no attribute {name!r}'
raise AttributeError(msg)
>>> a = Vector(range(9))
>>> a
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
>>> a.x
0.0
>>> a.y
1.0
# x不应该可以直接复制,这样会创建一个x变量
>>> a.x = 10
>>> a
Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...])
>>> a.x
10
>>>
# 赋值操作
def __setattr__(self, name, value):
cls = type(self)
if len(name) == 1:
if name in cls.__match_args__:
error = 'readonly attribute {attr_name!r}'
elif name.islower():
error = "can't set attributes 'a' to 'z' in
{cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls.__name__,
attr_name=name)
raise AttributeError(msg)
# 没问题就调用父类方法
super().__setattr__(name, value)
哈希
def __hash__(self):
hashes = (hash(x) for x in self._components)
# reduce类似于递归函数,sum、any 和 all的本质都是调用reduce
return functools.reduce(operator.xor, hashes, 0)
msg = error.format(cls_name=cls.__name__,
attr_name=name)
raise AttributeError(msg)
# 没问题就调用父类方法
super().setattr(name, value)
### 哈希
```python
def __hash__(self):
hashes = (hash(x) for x in self._components)
# reduce类似于递归函数,sum、any 和 all的本质都是调用reduce
return functools.reduce(operator.xor, hashes, 0)