1. 分类
- 创建、初始化与销毁:
- __new__
- __init__与__del__
- 可视化(__str__,__repr__)
- hash
- bool
- 预算符重载
- 容器和大小
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他
2.实例化
class A:
# @staticmethod
def __new__(cls, *args, **kwargs): # 静态方法
cls.test = 'abc' # 给类增加属性,但是不好,每次实例化都要创建一次
# return 'abc'
print(cls)
print(args) # ('tom',)
# args = ('jerry', ) # 不会改变a.name的值
print(kwargs) # {'name': 'tom'}
ret = super().__new__(cls)
# ret.age = 100 # 不建议在这里,可以放在__init__方法中
return ret # 调用父类的方法;返回实例
def __init__(self, name): # 将__new__方法返回的实例注入到self
self.name = name
# a = A('tom')
a = A(name='tom')
print(a) # None
print(a.name)
print(A.__dict__)
# {'__module__': '__main__', '__new__': <staticmethod object at 0x00000000027D9A58>, '__init__': <function A.__init__ at 0x00000000027D3F28>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'test': 'abc'}
<Cart Cart [1, 2, 1, 2]>
print(a.age)
__new__方法很少使用,即使创建了该方法,也会使用return super().new(cls)基类object的__new__方法来创建实例并返回。
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __str__(self):
return 'str: {} {}'.format(self.name, self.age)
def __repr__(self):
return 'repr: {} {}'.format(self.name, self.age)
def __bytes__(self):
return 'bytes: {} {}'.format(self.name, self.age).encode()
a = Person(name='tom')
print(a) # <__main__.Person object at 0x00000000027F0FD0>
# str: tom 18
print(str(a)) # <__main__.Person object at 0x00000000027F0FD0>
print(str(a).encode()) # b'str: tom 18'
print(bytes(a)) # b'bytes: tom 18'
print(repr(a)) # repr: tom 18
print([a]) # [repr: tom 18] print(str([])) 列表中又调用repr
print((a, )) # (repr: tom 18,)
print('{}'.format(a)) # str: tom 18
# 没有str方法,会找repr方法,但是没有repr方法,直接找基类的
注意不能通过判断是否带引号来判断输出值的类型,类型判断要使用type或isinstance
4.hash
__hash__方法,内建函数hash() 调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。(整数的hash算法是取模)
class Person:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __hash__(self):
return 1
def __eq__(self, other):
return self.age == other.age # 这样写的话,p1 == p2 ——>True
def __repr__(self):
return '<Person {} {}>'.format(self.name, self.age)
print(hash(1), hash(2), hash(5000000)) # 整数的hash算法是取模,除数是62位的整数
print(hash('abc'))
print(hash(('abc', )))
print(hash(Person))
print(hash(Person('tom')))
p1 = Person('tom')
p2 = Person('jerry')
print(hash(p1), hash(p2))
print({123, 123}) # {123}去重了
print({p1, p2}) # {<Person tom 18>, <Person jerry 18>}
# 没有去重,是因为p1和p2的内容不同(虽然他们的hash值相同);去重两个条件内容相同,hash值相同
print('~~~~~~~~~~', p1 == p2) # 会调用__eq__方法
print({(123, ), (123, )}) # {(123,)} 去重了
set去重需要两个条件: 内容相同,hash值相同
__hash__方法只是返回一个hash值作为set的key,但是去重,还需要__eq__方法来判断两个对象是否相等,hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供__hash__方法是为了作为set或者dict的key,如果去重,要同时提供__eq__方法。不可hash对象isinstance(p1, collections.Hashable)一定为False
思考:
1.list类实例为什么不可hash?
list类中(源码)直接将__hash__设置为None<;也就是调用__hash__()相当于调用None(),一定会报错。所有类都是继承自object,而这个类是具有__hash__方法的,如果一个类不能被hash,就把__hash_设为None
2.functools.lru_cache使用到的functools._HashedSeq类继承自list,为什么可hash?
因为子类重写了hash函数,将父类的hash方法覆盖了,这个方法实际上计算的是元组的hash值
练习
设计二维坐标类Point,使其成为可hash类型,并比较两个坐标的实例是否相等
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '<Point {},{}>'.format(self.x, self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
p1 = Point(12, 14)
p2 = Point(34, 65)
p3 = Point(12, 14)
print(p1) # <Point 12,14>
print(p2)
print(hash(p1))
print(hash(p2))
print(p1 == p2) # False
print(p1 == p3) # True
5. bool
class A:
pass
print(bool(A)) # True
print(bool(A())) # True
print(bool([])) # False
class B:
def __bool__(self):
print('in bool')
# return 1
# return bool(self) # 无限递归
return bool(1)
print(bool(B)) # True
# print(bool(B())) # 会出错
if B():
print('b~~~~~~~~~~~~')
class C:
def __len__(self):
return 1 # 必须大于0
print(bool(C))
print(bool(C()))
6.运算符重载
class A:
def __init__(self, age):
self.age = age
def __sub__(self, other):
return self.age - other.age
def __isub__(self, other):
# return A(self.age - other.age) #新实例
# self.age -= other.age
# return self # 31691272 <__main__.A object at 0x0000000001E39208>\
# 31691272 <__main__.A object at 0x0000000001E39208>就地修改
return A(self - other) # 新实例
a1 = A(20)
a2 = A(12)
print(id(a1), a1) # 32150024 <__main__.A object at 0x0000000001EA9208>
# print(a1 - a2) # 8数值
# print(a1.__sub__(a2)) # 8
# print(a2 - a1, a2.__sub__(a1)) # -8
a1 -= a2 # a1__isub__(a2)
print(id(a1), a1) # 32151032 <__main__.A object at 0x0000000001EA95F8>
__isub__方法定义,一般会in-place就地修改自身;如果没有定义__isub__方法,则会调用__sub__方法
练习
完成Point类设计,实现判断点相等的方法,并完成向量的加法
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def add(self, other):
return self.__class__(self.x + other.x, self.y + other.y)
#
# def __add__(self, other):
# return self.add(other)
__add__ = add
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self # 可以链式
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __repr__(self):
return '<Point {},{}>'.format(self.x, self.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # <Point 4,6>
p1 += p2
print(id(p1), p1) # 32150416 <Point 4,6>
print(p1 + p2 + p2) # <Point 10,14>
6.1运算符重载应用场景
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达式,例如上例中的+进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point。
提供运算符重载,比直接提供加法方法更加适合该领域内使用者的习惯。
int类,几乎实现了所有操作符,可以作为参考。
@functools.total_ordering装饰器
lt,le,eq,gt,ge,是比较大小必须实现的方法,但是全部写完太麻烦了,使用@functools.total_ordering装饰器,就可以大大简化代码。
但是要求__eq___必须实现,其他方法实现其一
from functools import total_ordering
@total_ordering
class A:
def __init__(self, values):
self.values = values
def __eq__(self, other):
return self.values == other.values
def __gt__(self, other):
return self.values > other.values
a = A(10)
b = A(8)
print(a == b)
print(a != b)
print(a > b, a < b, a >= b, a <= b)
上例中大大简化了代码,但是一般来说比较实现等于或者小于方法也就够了,其他可以不实现,所以这个装饰器只是看着很美好,且可能带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。
== 7.容器相关方法==
class A(dict):
def __missing__(self, key): # 默认返回None
print(key, 'missing ~~~')
value = 1000
self.__dict__[key] = value
return value
a = A()
print(isinstance(a, dict)) # True
print(a['tt']) # None; 1000
print(a.__dict__) # {'tt': 1000}
练习
将购物车类改造成方便操作的容器类
class Cart:
def __init__(self):
self.items = []
def add(self, item):
self.items.append(item)
return self
def __add__(self, other):
self.add(other)
return self
def __len__(self):
return len(self.items)
def __getitem__(self, index):
# print(index)
return self.items[index] # self[index]会无限递归
def __setitem__(self, index, value): # 一般不需要返回值
self.items[index] = value
# self[index] = value # 不能这么写,会无限递归
def __repr__(self):
return '<Cart {} {}>'.format(__class__.__name__, self.items)
def __iter__(self):
# return iter(self.items)
# for i in self.items:
# yield i
yield from self.items
c1 = Cart()
c1.add(1)
c1.add(2)
c1.add(1).add(2)
print(c1)
c1 + 4 + 5
print(c1)
print(len(c1))
print(c1[0])
c1[1] = 100
print(c1[1])
for z in c1:
print(z)