美文网首页
python 魔法方法

python 魔法方法

作者: 天空蓝雨 | 来源:发表于2019-11-20 20:35 被阅读0次

    内置魔法方法参见:官网:Python数据模型
    Python 魔术方法指南 ——稍微有点乱

    Python魔法方法指南 —— 翻译版 很简洁全面

    关于Python的私有属性和魔法方法
    魔法方法都是 _ xx _ 形式的,虽然 _ _开头,但是实例却可以直接访问,所以魔法方法,并不属于私有属性。当然自己也可以定义 _ xx 的魔法方法。
    只有
    举个简单例子 _ init _ 可以直接被继承,子类可以直接 使用,就肯定不是私有的了。哈哈哈
    私有属性(包括私有方法),实例或继承都不可以直接访问,但是可以通过,实例名 或者 直接访问 _ classname.
    _私有属性名 访问(这也算是一个突破私有限制的通道)
    其实可以调用的属性都可以用 dir(obj),查看

    class confirm_obj_magic_func():
        "类的说明,可被自动收集  这个说明会自动传递给魔法属性  _ _doc_ _,
    当然你可以自定义这样就会覆盖掉默认的类描述信息"
        def __init__(self,a):
            self.__a = a
        def __a__(self):
            print("这是一个普通方法")
        def _b(self):
             print("这是单下划线普通的方法")
        def __b_(self):
            print("这个也是私有方法")
        def __b___(self):
            print("后面_ 超过两个(含两个),都不属于私有属性,无论前面是不是 _ _")
        def __doc__(self):
            return "这是一个魔法方法"
    aa  = confirm_obj_magic_func(1324)
    
    aa.__a__()
    这是一个普通方法
    aa.__b_()
    ...  object has no attribute '__b_'
    aa.__b___()
    后面_ 超过两个(含两个),都不属于私有属性,无论前面是不是 _ _
    print(aa.__doc__())
    这是一个魔法方法
    
    如果删掉自定义的   __doc__(self)
    print(aa.__doc__)
    类的说明, 这个说明会自动传递给魔法属性  _ _doc_ _,
    当然你可以自定义这样就会覆盖掉默认的类描述信息
    print(__doc__())   #  这时候默认的 doc 只是属性,而不是方法属性
    TypeError: 'str' object is not callable
    
    

    python 如果是自定义的魔法(不是你自己单独定义的,默认是普通属性,不是 callable 的方法属性,不可 xx(),调用,自己定义的 相反必须 xx() 调用)

    参考
    刘江博客

    • _ str _ 默认返回 str(self) 当然也可自定义返回字符串

    str() 返回的就是 object._ str _()
    print() 默认也是返回 str(obj) 可以这样考虑,当然print 不止这简单
    str 函数官方解释:
    returns the result of object. _ str _() (if defined) or repr(object)

        def __str__(self, *args, **kwargs): # real signature unknown
            """ Return str(self). """
            pass    
    如果你写pass ,就返回str(self) 这是解释器帮你调用一次,并返回
    如果你自己写了 return str(self),那不好意思,会无线递归,因为str(self)  还
    是会调用 self.__str__() 自己试了递归 330 次报错了
    但是你返回其他字符串,就不会产生递归了,哈哈
    
    
        def __repr__(self, *args, **kwargs): # real signature unknown
            """ Return repr(self). """
            pass
    这边书写方式同 __str__
    
    print(xxobj)
    如果没有  __str__
    返回类似的   实例内存地址
    <__main__.confirm_obj_magic_func object at 0x0000000004E33780>
    如果有  __str__
    返回 str   return 的内容,因为 print() 的定义就是先返回 对象的  __str__
    
    

    如果你自己的 str 没有返回字符串,就会报错
    如 当只返回数字:

    return 12     
    __str__ returned non-string (type int)
    

    返回其他格式就更不行了
    print(xx) xx 参数会被转换为字符串,就像是执行了 str(xx) 一样

    • _ module _

    表示当前操作的对象在属于哪个模块

    print(aa.__module__)
    __main__
    
    • _ class _ 获取当前对象所在的 属于的类对象

    print(aa.__class__)
    <class '__main__.confirm_obj_magic_func'>
    # 打印出来是当前运行模块的某个类对象
    有个疑问,为什么打印class名,没有出来  __str__ 的东西呢,
    很好理解 __str__ 是实例魔法方法,类访问不到实例的方法
    
    

    aa._ class _ 等价于一个类 对象
    你都可以这样用
    aa._ class _() 初始化和 aa 相同的实例了(因为和 aa 是同一个类初始化生成的)

    • _ del _() 析构方法,当对象在内存中被释放时,自动触发此方法。

    此方法无需定义,因为python有垃圾回收,释放该对象内存的时会,自动调用这个方法
    如果过你想在释放内存的时候,做点其他工作,那么你可以自定义 _ del _ 方法
    如:

    def __del__(self):
            print("我被回收了!")
    del  xx实例  
    "我被回收了!"
    
    • _ call _()

    如果为一个类编写了此方法,那么这个类实例化后可以加() 执行 _ call _
    相当于是实例的直接运行的简单实现,其实和接着调用其他实例方法,一样的,只不过这个不用 . 而是直接加 () 来执行。主要优雅

    class xx():
        def __call__(self, *args, **kwargs):
    
                print('__call__')
    a = xx()
    a()   #这就调用了  _ _call_ _  这个实例方法
    
    

    问题来了,如何我们如果看代码,可以直接判断有 _ call _ 就是可以直接执行的,如果看不了代码,可以使用 Python 内置函数来判断
    callable()

    callable(max)
    True
    callable([13,])
    false
    
    • _ dict _() 获取类或对象中所有成员

    获取类 _ dict _

    获取实例:


    获取实例
    • 对象字典操作三剑客

    getitem()、setitem()、delitem()

    定义了这三个方法之后,就可以愉快的使用 字典操作符, [ ] 了。


    字典形式操作对象属性
    • _ len _() 获取对象长度

    len(xx) 等价于 xx._ len _()

    Python的 list、dict、str等内置数据类型都实现了该方法,但是你自定义的类要实现len方法需要好好设计

    • __ iter __() 迭代器方法:

    列表、字典、元组之所以可以进行for循环,是因为其内部定义了 iter()这个方法。如果想使对象可迭代,那么就需要给他设置这个魔法函数。其实完全可以直接读取对象里面的东西,但是用魔法方法就比较简洁明了,有水平的存在。
    该魔法方法需要返回一个克迭代的迭代器。(基本可迭代类型,列表,字典,字符串等等通过iter()转化即可,当然自己实现一个迭代器,那也是妥妥的)
    用for循环遍历对象时,就会调用类的这个_ iter () ,我感觉是 iter 方法就是一个中转站(优美的连接当前对象到 iter 返回的克迭代的对象上面),把for 转移到了 iter 返回的可迭代的对象上面,类似于for i in xx. iter _ 就像 xx[ ] 调用 setitem() 一样。

    错误做法
    但是可以用内置函数实现
    iter 内置函数源码,可以看出 是把克迭代对象转化为 迭代器
    关于可迭代,迭代器 ,生成器 参考
    Python可迭代对象,迭代器,生成器的区别
    def iter(source, sentinel=None): # known special case of iter
        """
        iter(iterable) -> iterator
        iter(callable, sentinel) -> iterator
        
        Get an iterator from an object.  In the first form, the argument must
        supply its own iterator, or be a sequence.
        In the second form, the callable is called until it returns the sentinel.
        """
        pass
    
    iter 返回迭代器

    或者直接用生成器


    生成器
    • _ _ next _ _()

    参考 next 和 iter 的关系
    https://www.programiz.com/python-programming/iterator
    简单来说 如果用for 那就必须写 --iter--(因为for 内部首先调用 iter 函数) ,如果直接用 next,可以只写 --next--, 一般来说 -- iter-- 里面写一些 初始化,最后返回一个迭代器(如果有 next ,那一般返回 self)

    上面也有个例子说,使用 --iter-- 里面直接返回 yield 或 iter(list),然后没有 --next-- 函数,这是因为 yield 和 iter内置函数会帮我们自动实现 ------next-- 函数

    上面说的是对的,看两个例子对比下,就明白了
    直接使用 next ,绕过 iter

    class test():
        def __init__(self,data=1):
            self.data = data
    
        def __next__(self):
            if self.data > 5:
                raise StopIteration
            else:
                self.data+=1
                return self.data
    i = test(3)
    for item in range(3):
        print(i.__next__())
    4
    5
    6
    
    class test():
        def __init__(self,data=1):
            self.data = data
        #def __iter__(self): 
        #      return self
        def __next__(self):
            if self.data > 5:
                raise StopIteration
            else:
                self.data+=1
                return self.data
    for item in test(3):
        print(item)
    TypeError: 'test' object is not iterable
    

    可以看出一旦,注释 --iter-- 直接for 这个对象,就会报错了
    架上 --iter-- 就正常了。所以还需要了解 for 底层是先吊用了 --iter-- ,然后调用了 next。

    绕过 --next--

    class test():
        def __init__(self,data=1):
            self.data = data
    
        def __iter__(self):  
            return iter([1,2,3])    
        def __next__(self):
            if self.data > 5:
                raise StopIteration
            else:
                self.data+=1
                return self.data
    
    for item in test(3):  # 这里必须是运行状态,才是迭代器哦
    注意此时,for 循环的迭代器,已经不是当前类实例了,而是 iter() 内置函数转化的 迭代器了。所以结果自然和当前实例的 --next--   无关
    其实你要是,直接使用 next(当前是实例),他还是返回实例里面的 --next--,和 --iter-- 无关,iter只是决定了 他的循环次数
        print(item)
    1
    2
    3
    

    所以 --iter-- 如果直接返回了 迭代器,而不是 self ,那么已写的 --next-- 就只有直接使用 next() 函数时候,才有作用了

    所以 理解 for 底层,十分重要哟。iter 和 next 函数确实默认调用到 当前迭代对象的 --iter-- 和 --next-- 。

    • repr()

    和 _ str _ 类似,但是返回的是 堆栈的内存啥的,主要是给开发看的
    如果你实现了 str ,他就直接返回 --str-- ,否则使用 --repr-- , 如果你自己没有 --repr-- 解释器返回默认的 对象地址格式

    • add: 加运算 sub: 减运算 mul: 乘运算 div: 除运算 mod: 求余运算 pow: 幂运算

    • author

    这个魔法属性一般是在 文件里面的,也经常见到在文件最上面写上 _ auth _
    author代表作者信息!类似的特殊成员还有很多,就不罗列了

    • _ slots _

    这个也很熟悉了,限制实例的属性集合 ,类里面写的实例属性也会限制,外面添加更会限制的,哈哈哈,
    子类不写不限制,子类写,会继承父类限制并加上自己的 slots 限制

    • 赋值三剑客

    参考:
    _ setattr _, _ delattr _, _ getattr _ 到底干了什么
    -- setattr -- --delattr -- --getattr-- 的使用场景

    _ setattr _ 添加/修改属性会触发它的执行
    _ getattr _只有在使用点调用属性且属性不存在的时候才会触发

    class mm():
        
        def __init__(self):
            self.a = "正赋值"
            print(self.b)
        def __getattr__(self, key):
            print("不存在的属性", key)
            return 0
    mm()
    

    上面初始化 里面 使用 self.b 来调用 b 属性,但是 self 里面没有b ,所以此时触发 --getattr-- , 并且 使用 --getattr-- 的返回值
    上面的结果:

    不存在的属性 b
    sgfsdgf
    

    --delattr--

    def __delattr__(self, item):
            print('你在删除属性')
            # del self.item #无限递归了,和上面的__setattr__原理一样  delattr(self, key) 也是一样的
    self.__dict__.pop(item)  正确的做法
    

    所以一般我们不会,设置这三个方法,因为正常的赋值取值 ,Python 基类里面都已经
    帮我们弄好了。那这种情况,肯定是用在更高级的地方 前方高能:

    使用 赋值三剑客实现 已经存在类型修改:
    其实一种可以使用继承,来实现(因为继承,没有腹泻的方法属性,都会找到
    父类里面的, 有些编程语言中如python2.2之前,基本数据类型不属于类,那样就不能被类继承,所以很多东西都实现不了,但是现在基本 py3 了,所以有限继承把),还有一种方法就是 _ getattr _ 实现授权(虽然可以通过继承,但这个也要知道的)
    举个下的例子看看吧:

    在原生List类的基础下,实现append方法只能插入int类型数据
    执行新功能获取列表的中间index值
    
    class List:
        def __init__(self,onelist):
            self.onelist=onelist  #
    #基于原生的List类,修改原生的append方法,只能插入int类型的数据
    def append(self, p_object):
            if not isinstance(p_object,int):
                raise TypeError('must be int')
            self.onelist.append(p_object)
    
        @property
        def getmid(self):
            '新增自己的方法'
    index = len(self.onelist) // 2
            return self.onelist[index]
    
        def __getattr__(self, item):
            return getattr(self.onelist,item)  #这是关键点,如果item在本类中不存在,
    会去原生的List类里面取寻找item方法
    l=List([1,2,3])  #等价于l=[1,2,3]
    l.append(4)
    print(l)
    #l.append('abc') #报错,必须为int类型
    #新增自己的方法,获取列表中间的index
    print(l.getmid)
    
    #基于授权,获得insert方法,insert方法在本类中没有实现,所以回去原生的List中寻找insert方法
    #list.insert(index, obj)
    l.insert(0,9)
    print(l)
    
    

    最后说明一下,魔法方法有的和内置函数有很紧密的关系,有可能是 魔法方法调用了默认函数,或者默认函数直接使用 魔法方法的返回值。所以内置函数还是很有必要掌握的。娃哈哈
    pyhton 内置函数官方文档

    注意python 很多内置方法都已经 帮我们定义好了返回值,很多都是 直接写了 pass
    比如 上面讲的一个例子:

    def __str__(self, *args, **kwargs): # real signature unknown
            """ Return str(self). """
            pass    
    如果你写pass ,就返回str(self) 这是解释器帮你调用一次,并返回
    如果你自己写了 return str(self),那不好意思,会无线递归,因为str(self)  还
    是会调用 self.__str__() 自己试了递归 330 次报错了
    但是你返回其他字符串,就不会产生递归了,哈哈
    

    还有很多类似的 比如:

    class mm():
        def __init__(self):
            self.a = "正赋值"
        def __setattr__(self, key, value):
            print(key, value)
            setattr(self, key, value)   # self.key=value 也会递归,因为他会直接触发 --setattr-- (可以看出 setattr是先触发  . 操作,然后   .  就触发了 --setattr--) 
    正确的写法: self._ _dict_ _[key] = value  
    在给对象属性赋值的时候,内部原理就是操作对象的__dict__字典,所以直接操作属性字典就可以实现了
    mm()
    

    上面的结果:

    a 正赋值
    a 正赋值
    a 正赋值。。。。
    [Previous line repeated 494 more times]
      File "D:\算法练习\algo-master\python\my_coding\__getattr__.py", line 31, in __setattr__
        print(key, value)
    

    可以看出上面产生了无线递归

    因为 很显然内置函数 setattr(self, key ,value) 他触发的是 传入的第一个对象 self 的 --setattr-- 然后就明白了,又调用了 self 他自己,所以产生了无线递归

    当我们是使用它们的时候注意,它们触发的方式
    下面总结一下:
    len -> --len--
    getitem -> --getitem-- ( 字典三剑客都是)
    getattr -> --getattr-- (其他两个也是这样)
    str -> --str--

    (其实 很多内置函数,都是触发了对象的默认操作标志,进而触发 魔法方法)

    getattribute

    这个其实和 --getattr-- 是有联系的,其实当调用属性时候,会先触发这个getattribute(如果你定义的话,没有定义 object 基类里面有这个,)返回属性值,如果没有这个属性 引发 AttriburteError,才会接着触发 --getattr--(正常是 会直接触发 --getattr--,但是基类里有 getattribute, 所以 --getattr-- 就轮到 娶不到属性时候调用了)
    getattr(x, y) 内置函数等价于 x.y -> X.getattribute(y) -> --getattr--(y)
    ok

        def __getattribute__(self, *args, **kwargs): # real signature unknown
            """ Return getattr(self, name). """
            pass
    

    因为
    一般使用object.getattribute来避免无限调用. 因为直接
    self. getattribute 就是无限递归了
    def getattribute(self, item):
    pass

    相关文章

      网友评论

          本文标题:python 魔法方法

          本文链接:https://www.haomeiwen.com/subject/oonybctx.html