python中的魔术方法

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

1. 分类

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

2.实例化

new方法

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

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)

猜你喜欢

转载自blog.csdn.net/sqsltr/article/details/90442901