Python类中的魔术方法

特殊属性

 __name__    对象名称
 __module__  类定义所在的模块名
 __class__   对象或类所属的类
 __bases__   类的基类元组,顺序为它们在基类列表中出现的顺序
 __doc__     函数, 类 文档; 如果没有定义默认为 None
 __mro__     类的mro, class.mro()返回的结果保存在__mro__中
 __dict__    类或实例的属性, 可写的字典

特殊方法

 __dir__     是通过调用内建函数dir()获取到对象的属性, 方法

魔术方法

 1. 创建与销毁
     __init__
     __del__
 2. hash
 3. bool
 4. 可视化
 5. 运算符重载
     __eq__
     __lt__
     __gt__
 6. 容器和大小
 7. 可调用对象
 8. 上下文管理
     with相关的
 9. 反射
 10. 描述器 难点
 11. 其他杂项

一. 可哈希 hash

关于hash, 只能哈希一些不可变的对象, 比如1, 2,(1,2,3,4). 可变类型的数据都是不可hash的, 比如 list

 # hash(obj)  <==> obj.__hash__()
 
 class Test:
     
     def __hash__(self):
         # __hash__需要返回一个整数
         return 1
     # 实现不可hash类型
     __hash__ = None # 为None就是不可hash类型
     
     
     
 # 同时引出一个set去重的机制, set去重是比对的对象__qe__方法
 # 所以set不是根据hash去重的, 是先使用is比对两个对象是否为同一个内存,如果是就只直接去重, 否者判断两个对象是否 == 
 # hash还会出现hash冲突

判断一个对象是否是可hash的

 from collections import Hashable
 
 num = 1
 isinstance(num, Hashable)
 issubclass(int, Hashable)

二. 可布尔的

关于__bool__, 如果类没有定义__bool__方法, 就找__len__方法, 费0为真, 如果__len__方法也没有定义, 那么所有实例都返回为真

 class Test:
     
     def __bool__(self):
         return True
     def __len__(self):
         return 0
     
 # bool(A()) 是先找__bool__方法, 如果没有定义就去找__len__, __len__没有定义就返回True
 # 为什么集合类型的数据为空的时候bool(OBJ)为False, 其实就是类中没有定义__bool__方法, 但是有__len__方法, 为空的时候__len__ => 0 所以返回的是False

三. 可视化

 __str__     # 求对象的字符串输出, str(OBJ),会print(OBJ),format(OBJ)
 __repr__    # 对象的表现形式, 内部调用的
 __bytes__   # bytes(OBJ)相当于类型强制转换
 class A:
     def __str__(self):
         # 这里必须返回字符串
         return 'abc'
     
     def __repr__(self):
         # 这里必须返回字符串
         return 'repr'
     
     def __bytes__(self):
         # 这里必须返回bytes类型的数据
         return b'bytes'
 m = A()
 print(a)    # 调用了 __str__ 
 # 输出为: abc
 l = [A(), A()]  # 调用了 __repr__
 print(l)
 # 输出为: [repr, repr]
 bytes(m)    # 调用了 __bytes__ 相当于进行了强制类型转换, 至于转换 结果需要编程者进行控制

四. 运算符重载

# 比较运算符
__lt__    <
__le__    <=
__eq__    ==
__gt__    >
__ge__    >=
__ne__    !=

# 算数运算符
__add__    +
__sub__    -
__mul__    *
__truediv__    /
__mod__    %
__floordiv__    //
__pow__    **
__divmod__    divmod

# 语法糖
__iadd__    +=
__isub__    -=
__imul__    *=
__itruediv__    /=
__imod__    %=
__ifloordiv__    //=
__ipow__    **=
# e.g
class Point:

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        return hash((self.x, self.y))

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return self.__class__(self.x - other.x, self.y - other.y)

    def __str__(self):
        return '<Point ({}, {})>'.format(self.x, self.y)

    def __repr__(self):
        return self.__repr__()


if __name__ == '__main__':
    a = Point(1, 2)
    c = Point(1, 9)
    print(a)
    print(c)
    print(a + c)
    print(a - c)

五. 容器方法

__len__        长度 len()会调用该方法
__iter__    迭代容器时调用, 返回一个新的迭代器对象
__contains__    in成员运算符, 没有实现就调用__iter__方法遍历
__getitem__        实现self[key]访问.序列对象, key接受整数为索引,或者切片.对于set和dict, key为hashable. key不存在引发KeyError
__setitem__        同上,不过该方法是进行设置值的
__missing__        字典使用__getitem__()调用时, key不存在执行该方法

六.可调用对象

# __call__
class Test:
    def __init__(self, name):
        self._name = name
     
    def _show_name(self):
        return self._name
  
    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)
        print(self._show_name())
      
a = Test()
a()
# a是一个可调用对象

七.上下文管理

"""
    重要的几个方法
    __enter__    进入执行的方法, 会将enter的返回值绑定到as后面的变量上
    __exit__    退出执行的方法
    场景
    with OBJECT as ALIAS:
        pass
"""
class Point(object):
    def __init__(self):
        print('init')

    def __enter__(self):
        """
        with 进入执行的方法
        :return:
        """
        print('__enter__')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        with 退出执行的方法
        :param exc_type:
        :param exc_val:
        :param exc_tb:
        :return:
        """
        print('__exit__')
with Point() as point:
    pass

"""
    with 异常处理能力
    __exit__的三个参数
    exc_type    异常类型
    exc_val    异常的值
    exc_tb    taceback 
    没有异常的话这三个值为None

    拦截异常, 增加返回值return True
"""
with Point() as point:
    # raise Exception('Error')
    sys.exit()
    # 如上, 就算遇到异常, exit方法一定会执行
    # 错误照常抛出, __exit__还是会正常执行
    print('point', point)

contextlib.contextmanager他是一个装饰器实现的上下文管理, 装饰一个函数, 而不用像类一样实现__enter____exit__方法

import contextlib

# 使用例子

@contextlib.contextmanager
def foo():
    print('enter')    # 相当于__enter__
    yield VALUE        # 相当于enter return 的对象
    print('exit')    # 相当于__exit__
  
with foo() as f:
    print(f)

functools.total_ordering 与比较相关的装饰器

from functools import total_ordering


@total_ordering
class A:
    def __init__(self, x):
        self.x = x

    def __eq__(self, other):
        return self.x == other.x

    def __lt__(self, other):
        return self.x < other.x


if __name__ == '__main__':
    print(A(1) > A(2))
    print(A(2) >= A(2))
    print(A(98) <= A(33))
    print(A(98) == A(33))

# 总结他的作用, 当类要重载比较运算符的时候, 只想实现==, > 但是又想拥有<=, >=, !=这些比较运算符, # total_ordering 可以简化操作, 必须定义一个__eq__, 比较__lt__ 需要定义一个
# 缺点: 效率不高, 对于需要频繁比较的场景不建议使用该装饰器

八.反射

反射, reflection,指的是运行时获取类型定义信息

反射相关的函数和方法

getattr(OBJ, name, [DEFAULT])    # 获取对象属性
setattr(OBJ, name, value)    # 设置对象属性
hasattr(OBJ, name)        # 判断对象是否有某属性

反射相关的魔术方法

# __getattr__ 获取属性
# __setattr__ 设置属性
# __delattr__ 删除属性
# 该魔术方法对应getattr, setattr
# 本质上就是调用实例的__getattr__或__setattr__

class Point:
    def __init__(self):
        self.x = 1
        self.y = 2

    def __setattr__(self, key, value):
        print('设置了属性')
        super(Point, self).__setattr__(key, value)

    def __getattr__(self, item):
        """在字典中没有才会到达这里"""
        print('获取了属性')
        return super(Point, self).__getattr__(item)

    def __delattr__(self, item):
        print('实例删除')

    def __getattribute__(self, item):
        """在找属性之前做的操作,比__getattr__先运行"""
        pass
  
    """
    实例的所有的属性访问, 第一个都会调用__getattribute__方法, 它阻止了属性的查找, 该方法应该返回(计算后的)值或抛出一个AttributeError异常
    它的return值将作为属性查找的结果.如果抛出AttributeError异常, 则会直接调用__getattr__方法, 因为表示属性没有找到
    """

属性的查找顺序:__getattribute__ -> instace.__dict__ -> instance.__class__.__dict__ -> 继承的祖先类(一直找到object)的__dict__ -> 调用__getattr__()

九.描述器(比较绕)

定义: 当一个类用到了这三个魔术方法(__set__, __get__, __delete__), 且该类的实例被用作另一个的属性, 就叫描述器

# 例子
class A:
    def __init__(self):
        self.a = 'My is Class(A)'
  
    def __get__(self, instance, owner):
        """
        :param instance: 使用者实例
        :param owner: 属主类(实例的__class__)
        :return:
        """
        # __get__方法只有当前实例是某个类的属性, 通过某个类访问该类的属性才能触发
        print(self, instance, owner)
        return self

class B:
    x = A()    # 描述器
  
    def __init__(self):
        self.x = A()
        self.y = 100
if __name__ == '__main__':
    b = B()
       print(A.a)     # 不会调用A.__get__
    print(B.x.a)    # 会调用A.__get__
    print(b.x.a)    # 不会调用A.__get__    因为类的属性搜索顺序, 他获取到的是实例的属性, 不是类的属性

如果仅实现了__get__, 就是 非数据描述符 non-data descriptor

同时实现了__get__ + __set__或__delete__, 就是 数据描述符 data descriptor

如果一个类的类属性设置为描述器, 那么它被称为owner属主

# 当为一个数据描述器的时候
class A:
    def __init__(self):
        self.x = 'Is A'
  
    def __get__(self, instance, owner):
        print(self, instance, owner)
        return self
  
       def __set__(self, instance, value):
        print(instance. value)
      
      
class B:
    m = A()
  
    def __init__(self):
        self.m = 100    # 因为是数据描述器, 这里会调用类A的__set__方法
        self.m = A()    # 和上面一样
      
if __name__ == '__main__':
    print(B.m.x)    # 会触发A.__get__方法
    b = B()            # 根据上面的定义会触发两次A.__set__
    print(b.m.x)
  
# 一旦类的属性是一个描述器, 那么该属性优先级高于当前类的属性

# 官方或书籍中的定义:
# 实例的__dict__优先于非数据描述器
# 数据描述器优于实例的__dict__
  • staticmethodclassmethod就是非数据描述器
  • property是数据描述器
  • 类中的所有方法都是非数据描述器,包括魔术方法

以下实现了两个重要的类方法装饰器

from functools import partial


class StaticMethod(object):
    """静态方法"""
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, instance, owner):
        return self.fn


class ClassMethod(object):
    """类方法"""
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, instance, owner):
        # 引入了偏函数
        # 偏函数的作用是为函数提供默认的参数
        return partial(self.fn, owner)


class MyClass:

    @StaticMethod
    def add(x, y):
        return x + y

    @ClassMethod
    def show(cls, value):
        print(cls)
        print(value)


if __name__ == '__main__':
    print(MyClass.__dict__)
    print(MyClass.add(50, 50))
    my = MyClass()
    print(my.add(50, 50))
    MyClass.show('HelloWorld')
    my.show('HelloWorld')

顺带补一下偏函数

from functools import partial
# partial(函数对象, 参数1,参数2,...)
# partial主要作用是包装函数对象,为函数提供一些默认参数, 并返回一个函数对象
# 如下
def add(x,y):
    return x + y
# 此时我们想吧x固定下来, 固定为2
new = partial(add, 2)
# 直接调用new
print(new(2)) # ->  4
print(new(5)) # -> 7

通过描述器实现参数数据类型的强制判断

import inspect
from functools import wraps

class Typed:
    """类型描述器"""
    def __init__(self, type):
        self.type = type
        self.value = None
  
    def __get__(self, instance, owner):
        return self.value
  
    def __set__(self, instance, value):
        if isinstance(value, self.type):
            self.value = value
        else:
            raise TypeError('需要传入一个类型{}的数据'.format(self.type))
  
class TypeAssert:
    """类型断言,装饰器; 其实给类添加数据描述器"""
    def __init__(self, cls):
        self.cls = cls
        wraps(cls)(self)
    
    def __call__(self, *args, **kwargs):
        params = inspect.signature(self.cls).parameters
        for name, param in params.items():
            if param.annotiation != param.empty:
                # 类型不为空
                setattr(self.cls, name, Typed(param.annotiation))
        return self.cls(*args, **kwargs)
  

@TypeAssert
class Ponit:
    def __init__(self, x:int, y:int):
        self.x = x
        self.y = y
    
p = Point(1,2)
p.x = 'str'    # 将会抛出错误
最后修改:2021 年 06 月 04 日 04 : 59 PM
如果觉得我的文章对你有用,请随意赞赏