【Python】【数据类型】
【序列更新&散列&切片】
"""
from array import array
import reprlib
array1 = array('d',(1,2,3))
print(array1) #array('d', [1.0, 2.0, 3.0])
com = reprlib.repr(array1)
components = com[com.find('['):-1]
print('Vector({})'.format(components)) #Vector([1.0, 2.0, 3.0])
"""
#10.2 Vector类第一版,与Vector2d类兼容
#例子10-2
from array import array
import reprlib
import math
import numbers
import functools
import operator
import itertools
class Vector:
typecode = 'd'
def __init__(self,components):
self._components = array(self.typecode,components) # 把Vector分量保存在一个数组里
def __iter__(self):
return iter(self._components) #构造一个迭代器
def __repr__(self):
components = reprlib.repr(self._components) #使用reprlib.repr()获取self._components的有限长度表示形式(如array('d',[0.0,1.0,2.0,3.0,4.0,...]))
components = components[components.find('['):-1] #把字符串插入Vector的构造方法调用之前,去掉前面的array('d' 和后面的)
return 'Vector({})'.format(components)
'''
def __str__(self):
return str(tuple(self))
'''
__str__ = __repr__
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(self._components)) #直接用self._components构建bytes对象
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self):
return math.sqrt(sum(x*x for x in self)) #不能直接用hypot方法了,因此我们先计算各分量的平方之和,然后再使用sqrt方法开平方
def __bool__(self):
return bool(abs(self))
@classmethod
def frombytes(cls,octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(memv) #构造方法变了,所以不用像前面那样使用*拆包
def __len__(self): #以下两个方法实现切片的序列
return len(self._components)
def __getitem__(self, item):
cls = type(self) #获取实例的类,供后面使用
if isinstance(item,slice):
return cls(self._components[item])
elif isinstance(item,numbers.Integral):
return self._components[item]
else:
msg = '{cls.__name__} indices must be integers'
raise TypeError(msg.format(cls=cls))
def __eq__(self, other):
return len(self) == len(other) and all(a==b for a,b in zip(self,other)) #all函数,只要一次比较结果为False,则结果为False.所有结果为True,结果为True
#考虑到,按照老规则的话,Vector([1,2]) 和(1,2)的比较结果是一样的,所以出来了这个新规则
def __hash__(self):
hashes = map(hash,self._components)
return functools.reduce(operator.xor,hashes)
def angle(self,n):
r = math.sqrt(sum(x*x for x in self[n:]))
a = math.atan2(r,self[n-1])
if (n == len(self) -1) and (self[-1] < 0):
return math.pi*2 - a
else:
return a
def angles(self):
return (self.angle(n) for n in range(1,len(self)))
def __format__(self, fmt_spec):
if fmt_spec.endswith('h'):
fmt_spec = fmt_spec[:-1]
coords = itertools.chain([abs(self)],self.angles())
a = list(self.angles())
outer_fmt = '<{}>'
else:
coords = self
outer_fmt = '({})'
components = (format(c,fmt_spec) for c in coords)
return outer_fmt.format(','.join(components))
#通过@property来动态获取属性,想要获取向量的四个分量时,要写四个这样的特性,太麻烦,所以选择__getattr__这个特殊方法。
#属性查找失败后,解释器会调用__getattr__方法。简单来说,对my_obj.x表达式,python会检查my_obj实例有没有名为x的属性;如果没有,到类(my_obj.__class__)中查找;如果还没有,顺着继承树继续查找。如果依旧找不到,调用my_objh所属类中
#...定义的__getattr__方法,传入self和属性名称的字符串形式。
#例子10-8
shortcut_names = 'xyzt'
def __getattr__(self, item):
cls = type(self)
if len(item) == 1:
pos = cls.shortcut_names.find(item)
if 0<= pos < len(self._components):
return self._components[pos]
msg = '{.__name__!r} object has no attribute {!r}'
raise AttributeError(msg.format(cls,item))
#【备注】1⃣️使用reprlib.repr的方式需要做些说明。这个函数用于生成大型数据结构或递归结构的安全表现形式,它会限制输出字符串的长度,用'...'表示截断的部分。我希望Vector实例的表现形式是Vector([3.0,4.0])这样,而不是
#Vector(array('d',[3.0,4.0])),因为我们不需要实例中的实现细节。
#【备注】2⃣️编写__repr__方法时,本可以使用这个表达式生成简化的componets显示形式:reprlib.repr(list(self._components))。然而,这么做优点浪费,因为要把self._components中每个元素复制带一个列表中,
#然后使用列表的表示形式。
#【备注】3⃣️调用repr()目的是调试
#【备注】4⃣️我们本可以让Vector继承Vector2d,没这么做的原因幼儿。其一,两个构造方法不兼容,因此不建议继承。这一点可以通过适当处理__init__方法的参数解决,不过第二个原因更重要:我想把Vector类当作单独的事例,以此实现序列协议
#10.3 协议和鸭子类型
'''
#例子10-3 可切片的序列
v7 = Vector(range(7))
print(v7[-1]) #6.0
print(v7[1:4]) #Vector([1.0, 2.0, 3.0])
print(v7[-1:]) #Vector([6.0])
print(v7[1,2]) #TypeError: Vector indices must be integers Vector不支持多维索引
#例子10-4 了解__getitem__和切片的行为
class MySeq:
def __getitem__(self, item):
return item
s = MySeq()
print(s[1]) #1
print(s[1:4]) #slice(1, 4, None)
print(s[1:4:2]) #slice(1, 4, 2)
print(s[1:4:2,9]) #(slice(1, 4, 2), 9)
print(s[1:4:2,7:9]) #(slice(1, 4, 2), slice(7, 9, None))
#例子10-5 查看slice属性
print(slice) #<class 'slice'>
print(dir(slice))
print(slice(None,10,2).indices(5)) #(0, 5, 2)
print('ABCDE'[:10:2]) #ACE 根据上方的原理,等同于 'ABCDE'[0:5:2]
print(slice(-3,None,None).indices(5)) #(2, 5, 1)
print('ABCDE'[-3:]) #CDE 等同于'ABCDE'[2:5:1]
#例子10-15 zip内置函数的使用
print(zip(range(3),'ABC')) #<zip object at 0x102832788>
print(list(zip(range(3),'ABC'))) #[(0, 'A'), (1, 'B'), (2, 'C')]
print(list(zip(range(3),'ABC',[0.0,1.1,2.2,3.3]))) #[(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2)] zip有个奇怪的特性,当一个可迭代对象耗尽后,它不发出警告就停止
from itertools import zip_longest
print(list(zip_longest(range(3),'ABC',[0.0,1.1,2.2,3.3],fillvalue=-1))) #[(0, 'A', 0.0), (1, 'B', 1.1), (2, 'C', 2.2), (-1, -1, 3.3)]
print(','.join(format(c,'.3e') for c in (3.45678,0.555555))) #3.457e+00,5.556e-01
print('{:.3e},{:.3e}'.format(3.4567855,0.555555)) #3.457e+00,5.556e-01
import itertools
print(itertools.chain('123','456')) #<itertools.chain object at 0x10c554c18>
print(list(itertools.chain('123', '456'))) # ['1', '2', '3', '4', '5', '6']
print(list(itertools.chain([3.4444],(3.4444444,)))) #[3.4444, 3.4444444]
#tests of 'format()' with spherical coordinates in 2d,3d and 4d:
v3 = Vector([1,1])
print(format(v3,'h')) #<1.4142135623730951,0.7853981633974483>
print(format(v3,'.3eh')) #<1.414e+00,7.854e-01>
print(format(v3,'0.5fh')) #<1.41421,0.78540>
v4 = Vector([1,1,1])
print(format(v4,'h')) #<1.7320508075688772,0.9553166181245093,0.7853981633974483>
v5 = Vector([-1,-1,-1,-1])
print(format(v5,'.3eh')) #<2.000e+00,2.094e+00,2.186e+00,3.927e+00>
#tests of 'format()' with cartesian coordinates in 2d:
v5 = Vector([3,4])
print(format(v5)) #(3.0,4.0)
print(format(v5,'.2f')) #(3.00,4.00)
print(format(v5,'.3e')) #(3.000e+00,4.000e+00)
#tests of 'format()' with cartesian coordinates in 3d and 7d:
v6 = Vector([3,4,5])
print(format(v6))
v7 = Vector(range(7)) #(3.0,4.0,5.0)
print(format(v7)) #(0.0,1.0,2.0,3.0,4.0,5.0,6.0)
#tests of hashing
v9 = Vector([3,4])
v10 = Vector(range(6)) #7 1
print(hash(v9),hash(v10))
#Most hash values on ono-integers vary from a 32-bit to 64-bit CPython build::
import sys
v8 = Vector([3.1,4.2])
print(hash(v8)) #384307168202284039
print(hash(v8) == (384307168202284039 if sys.maxsize > 2**32 else 357915986)) #True
#tests of '.__bytes__ ' and 'frombytes()' methods:
v11 = Vector([3,4,5])
v11_clone = Vector.frombytes(bytes(v11))
print(v11_clone) #Vector([3.0, 4.0, 5.0])
print(v11 == v11_clone) #True
# 例子10-9 针对例子10-8的验证,不恰当的行为:为v.x赋值没有报错,但是前后矛盾
v12 = Vector(range(5))
print(v12) #Vector([0.0, 1.0, 2.0, 3.0, 4.0])
print(v12.x) #0.0
v12.x = 10
print(v12.x) #10
print(v12) #为向量赋新值后,向量依然没变。Vector([0.0, 1.0, 2.0, 3.0, 4.0])
#【分析】如果向量的分量数组中没有新值,为什么v12.x返回10?例子10-9之所以前后矛盾,是__getattr__的运作方式导致的:仅当对象没有指定名称的属性时,python才会调用那个方法,这是一种后备机制。可是,像v12.x=10这样赋值
#...赋值后,v12对象就有x属性了,因此使用v12.xhuoqu x属性的值时不会调用__getattr__方法了,解释器直接返回绑定到v12.x上的值,即10。为避免这种前后矛盾的现象,我们要改写Vector中设置属性的逻辑。
#例子10-10 实现__setter__方法:虽然这个例子不支持为Vector分量赋值,但是有一个问题要特别注意:多数时候,如果实现了—__getattr__方法,那么也要实现__setattr__ 方法,以防对象的行为不一致。
#如果像允许修改分量,针对Vector可以使用__setitem__,支持v[0]=1.1,或者实现__setattr__,支持v.x=1.1。不过,我们要保持Vector是不可变的,因为接下去的实验我们要把它变成可散列的。
#这里格外讲一下__setattr__
class Test:
shortcut_names = 'xyzt'
def __setattr__(self, key, value):
cls = type(self)
if len(key) == 1:
if key in cls.shortcut_names:
error = 'readonly attribute {attr_name!r}'
elif key.islower():
error = "cannot set attribute 'a' to 'z' in {cls_name!r}"
else:
error = ''
if error:
msg = error.format(cls_name=cls,attr_name=key)
raise AttributeError(msg)
super().__setattr__(key,value)
v11 = Test()
#v11.x = 2 #AttributeError: readonly attribute 'x'
#v11.a = 3 #AttributeError: cannot set attribute 'a' to 'z' in <class '__main__.Test'>
v11.B = 3
print(v11.B) #3
#【分析】
#1⃣️ 为了给AttributeError选择错误信息,我查看了内置的complex类型的行为,因为complex对象是不可变的,而且有一堆数据属性:real imag。如果试图修改任何一个属性,complex实例会抛出AttributeError,而且把错误
#消息设置为"cannot set attribute'。而如果尝试为受保护的只读属性赋值,得到的错误消息是'readonly attribute'。在__setattr__方法中为error字符串选词时,我参考了这两个错误消息
#注意,我们没有禁止为全部属性赋值,只是禁止为单个小写字母属性赋值,以防与只读属性x y z t 混淆
#2⃣️ 我们知道,在类型声明__slots__属性可以防止设置新实例属性;因此,你可能想使用这个功能,而不像这里所做的,实现__setattr__方法。可是,不将以只为了避免创建实例属性而使用它。__slots__属性只应该用于节省内存,而且仅当内存严重不足时才使用
'''
【list&tuple&dict】
# 栗子 list排序
ls = ['d.txt','a.xlxs','a.xlas','b.txt']
print(ls) #['d.txt', 'a.xlxs', 'a.xlas', 'b.txt']
ls.sort(key=lambda x:x[0])
print(ls) #['a.xlxs', 'a.xlas', 'b.txt', 'd.txt']
ls.sort(key=lambda x:x[4])
print(ls) #['a.xlas', 'b.txt', 'd.txt', 'a.xlxs']
students = [('john','A',15),('jane','B',12),('dave','B',10)]
print(sorted(students,key=lambda s:s[2])) #[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]