您的位置:首页 > 编程语言 > Python开发

python中的魔术方法

2019-07-07 11:35 633 查看
版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

1. 分类

  1. 创建、初始化与销毁:
  2. __new__
  3. __init__与__del__
  4. 可视化(__str__,__repr__)
  5. hash
  6. bool
  7. 预算符重载
  8. 容器和大小
  9. 可调用对象
  10. 上下文管理
  11. 反射
  12. 描述器
  13. 其他

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)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: