zoukankan      html  css  js  c++  java
  • Python 的 type 及常用魔法方法(上)

    魔法方法是 Python 内置方法, 不需要我们手动调用, 它存在的目的是给 解释器 调用的. 比如我们在写 "1 + 1 " 的时候, 这个 "+ " 就会自动调用内置的魔法方法 "__ add__" . 几乎每个魔法方法, 都有一个对应的内置函数或运算符. 当我们使用这些方法去操作数据时, 解释器会自动调用这些对应的魔法方法. 也可以理解为, 重写内置函数, 如果改变的话.

    具体的魔法方法等. 可以去看 内置的 builtins.py 这个接口文档.

    内置函数对应魔法方法: 如 next() 对应 __ next __ , 类名() 对应 __ call __ 等这样的映射关系 ....

    万物皆对象

    彻底阐明, 在 Pyth

    type

    关于 object 和 type 想必大家在编程实践中, 是经常会接触到这两个 关键字.

    首先来看 type.

    估计有 90% 的小伙伴都会以为这个神奇的内置函数 type 是一个函数, 用来判断对象类型的, 然后来看看源码:

    通常我们是这样来使用 type 的.

    >>> type(123)  
    <class 'int'>
    
    >>> type("abc")
    <class 'str'>
    
    >>> type([1,2,3])
    <class 'list'>
    
    >>> type((1,2,3))
    <class 'tuple'>
    
    >>> type({"name":"youge", "age":18})
    <class 'dict'>
    
    >>> type(abs)
    <class 'builtin_function_or_method'>
    
    >>> def my_func(): pass
    ...
    >>> type(my_func)
    <class 'function'>  # 函数也是个类
    
    >>> import numpy as np
    >>> arr = np.array([1,2])
    >>> type(arr)
    <class 'numpy.ndarray'>
    
    >>> class A(): pass
    ...
    >>> a = A()
    >>> type(a)
    <class '__main__.A'>
    
    >>> type(A)
    <class 'type'>
    
    
    >>> type(int)
    <class 'type'>
    
    >>> type(dict)
    <class 'type'>
    
    >>> type(object) # object 的类型是type
    <class 'type'>
    
    >>> type(type) # type 的 type 就是type 类自身呀
    <class 'type'>
    
    

    于是呢, 可以得到这样一个初步结论, 或者是错误矫正:

    • type() 的功能是找到, 输入对象的 "父类", 咱平时说的 int, str, list, dict ... 都表现为一个类.
    • 万物皆对象, 即在Python中用的所有 "值" 的东西都是一个 类的实例, 具体是那个类呢, 用 type( ) 来查看
    • type 父类是 object, 但是 object 的 type 是 type, 这貌似再说, type的地位是跟object一样的, 不同管辖嘛?

    object

    在Python3中, 所有的类都默认继承了 object, 可能大家平时也没太注意, 但我以前用 Python2 的时候, 是需要手动去继承 objecet 的, 当时还区分什么, 新式类, 旧式类这样的东西...

    class A(object):
        pass 
    
    class B:
        pass 
    
    # 二者是一样的, 在Python3中, 会自动默认继承 object, 可以不写. 
    

    都有一个共识, object 是所有类的 基类 (base class). 然后我们所有的类都去继承它, 在进行各种操作. 这没有什么问题, 然后突然想来一句 **灵魂发问: ** object 的基类是什么 ? 我一度认为是 type, 然后打脸了.

    >>> object.__bases__
    ()
    
    >>> type.__bases__
    (<class 'object'>,)
    
    >>> object.__subclasses__
    <built-in method __subclasses__ of type object at 0x0000000056DDB5C0>
    
    

    object 竟然没有基类, 而 type 是继承于 object 的. 基于万物皆对象, 对于万物, 我纵观中国哲学史, 对于世界的本质探讨, 我认为第一人还是 老子, 在 <<道德经>> 里中对于天地的概述:

    道生一, 一生二, 二生三, 三生万物

    • 一, 就是他提出的 "道" 的核心概念, 是本元, 先天地而生.
    • 二, 道演化为两种形态属性, 即 "阴" 和 "阳"
    • 三, 阴 和 阳 的不同比例的调和, 演变出了我们这五彩缤纷的世界.

    **这样给 object 来拔高一看, 类似 object 类 就是 "一", 而 type 类 就是 "阴阳", 咱们平时用的就是 "三" . **

    也不知这样解释是否合理, 但我相信这个点基本是 get到了.

    魔法方法

    其实就是一些常用的内置函数嘛, 在类设计中呢, 可以可以通过改写这些 魔法方法 来帮助我们完成一些事情 , 就类似于临时性改掉了 Python 的源代码. 听上去还挺酷的.

    当然只有先明白了 "万物皆对象" 的原理后呢, 再来理解这些内置函数会很 自然. 当然还忽略了一个前提, 默认大家对面向对象是非常熟悉哦. 如果连什么是类, 什么是实例, 什么是方法, 函数, 这些都不明白话, 本篇的探讨就可能不太适合了...

    当然这里也只是举了一些常用的而已, 更多的可以去参考python的源码, builtins.py 有更多的介绍.

    __ new __

    功能: 定制类创建的过程, 在 创建类对象时被触发, 它是一个静态方法(不需要self), 第一个参数 cls 是传一个类, 而不是实例对象 self, 因此, __ new __ 是在 __ init __ 之前调用的.

    class Cat:
        def __new__(cls, *args, **kwargs):
            print("__new__ is called")
    
    
    if __name__ == '__main__':
        cat = Cat()
    
    # out
    __new__ is called
    

    那当 __ new __ 和 __ init __ 一起的时候, 会如何呢?

    class Cat:
        def __new__(cls, *args, **kwargs):
            print("__new__ is called")
    
        def __init__(self):
            print("__init__ is called")
    
    
    if __name__ == '__main__':
        cat = Cat()
    
    # output
    __new__ is called
    

    发现, __ init __ 没有被执行. __ new __ 方法通常会返回该类的一个实例, 或其他类的实例, 于是就会跳过 __ init __ 方法 . 假如我还是想再 执行 __ init __ , 则 返回父类的 __ new __ 即可, 相当于 "白做了一遍"

    因为 __ new __ 已经构造好对象, 就不在需要 __ init __ 了

    class Cat:
        def __init__(self, name):
            self.name = name
            print("__init__ is called")
    
        def __new__(cls, *args, **kwargs):
            print("__new__ is called")
            return super().__new__(cls)
    
    
    if __name__ == '__main__':
        cat = Cat("youge")
        print('cat name is:', cat.name)
    
    # output
    __new__ is called
    __init__ is called
    cat name is: youge
    

    看上去似乎没啥用, 举个比较经典的栗子, 单例模式. 就是一个类, 值让其 创键一个实例, 和执行一次初始化.

    class Singleton:
        # 定义两个类属性, 状态的判断
        __instance = None
        __init = False
    
        def __new__(cls, *args, **kwargs):
    
            # 如果没有被创建则创建, 创建了则直接返回该实例呀
            if cls.__instance is None:
                print("create the instance")
                cls.__instance = super().__new__(cls)
    
            return cls.__instance
    
        def __init__(self):
            # 第一次先开门,进来
            if not self.__init:
                print("init the instance here")
                # 再关门, 永远关的那种
                self.__init = True
    
    
    if __name__ == '__main__':
        s1 = Singleton()
        s2 = Singleton()
        print(id(s1), id(s2))
        
    # output
    create the instance
    init the instance here, please.
    # s1 跟 s2 是一个实例.
    2249920782008 2249920782008
    

    这样就是所谓的 单例模式 呀, 我想, 应用场景, 应该非常直观了, 举个最近的栗子, 目前让弄一个小工具, 将Excel 数据转为为 一种特殊的格式, 发送到 Tableau 服务器. 准备加一个图片认证, 就防止是机器瞎几攻击服务器嘛, 然后这个功能封装成一个类, 每次实例化再调用方法即可....

    但考虑到, 其功能是固定的, 不能每次用户登录, 都来一遍创建实例, 其实只需要创建一次 就ok了, 这就用到了 单例模式了呀...

    __ init__

    功能: 初始化实例对象. 即在创建实例时 被自动触发 .

    class Cat:
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
            
     
        
    # __init__ 会在实例创建时被调用,.
    cat1 = Cat("youge", 'M')
    
    print(cat1.name)
    
    # output
    youge
    

    __ call __

    功能: 在写 "对象()" 时会自动被触发. 实现将类的实例行为, 表现一个函数.

    应用, 嗯, 有在做代码优化的时候, 将 一个函数 作为参数 传递给另外的函数. 可以做一些参数校验或是, 改变值之类.

    class Plane:
        def __init__(self, name, x, y):
            self.x = x
            self.y = y
            self.name = name
    
        def __call__(self, x, y):
            print("__call__ is called")
            self.x = x
            self.y = y
    
    
    if __name__ == '__main__':
        p = Plane("youge", 20, 30)
        print("pos:", p.x, p.y)
      
    	# call 方法能将 instance 作为 function 使用
        p(50, 100)
    
        print("pos:", p.x, p.y)
    
        
    # output
    pos: 20 30
    __call__ is called
    pos: 50 100
    
    

    另外, 之前在整装饰器的时候, 用类实现 装饰器 其实就有用到 call 方法的呀.

    class MyDecorator:
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            print("--- checking ---")
            return self.func(*args, **kwargs)
    
    
    @MyDecorator
    def check_2019_nCov(name):
        print(f"{name} is very healty")
    
    if __name__ == '__main__':
        check_2019_nCov("youge")
    
    # output
    --- checking ---
    youge is very healty
    
    

    __ str __

    功能: 打印一个对象的信息, 随便打印啥都可以, 在 print(实例对象名) 时被触发.

    class Cat:
        def __init__(self, name, age):
            self.age = age
            self.name = name
    
        def __str__(self):
            signature = f" my name is {self.name}, my age is {self.age}"
            return signature
    
    
    if __name__ == '__main__':
    
        cat1 = Cat("youge", 18)
        print(cat1)
        
        # 没有实现 __str__ 方法就打印对象名而已.
        print(Cat)
    
    # ouptput
    my name is youge, my age is 1
    <class '__main__.Cat'>
    
    

    __ del __

    功能: 当删除一个对象时, 会被自动触发.

    值得注意的是 "del obj" 在 Python 中, 最为牛逼的一个点是 自动垃圾回收. 前面已经讲了无数次, Python 变量的本质是 指针 了. 即地址的引用, 只有当变量指向的对象的 引用计数为0 时, 变量才会被真正回收.

    class Cat:
        def __del__(self):
            print("__del__ is called")
    
    
    if __name__ == '__main__':
        cat = Cat()
        del cat  # 会自动触发 __del__ 方法
    
    # output
    __del__ is called
    	
    

    说明一点是, 如果定义了 __ init __ 方法, 则会 先 会调用 __ init __ , 删除对象的时候调用 __ del __ 方法

    class Cat:
        def __init__(self, name):
            self.name = name
            print("__init__ is called")
    
        def __del__(self):
            print("__del__ is called")
    
    
    if __name__ == '__main__':
        cat = Cat("youge")  # 自动触发 __init__ 方法
        del cat             # 自动触发 __del__ 方法
    
    # output
    __init__ is called
    __del__ is called
    

    小结

    • type 是用来判断父类的, 不是类型, 是类, 基类是 object, 这两老哥既是父子, 也像兄弟, 某种程度上讲
    • 理解Python万物皆对象, 只要真正理解 type() 这个东西, 估计就懂了.
    • 几个常用的魔法方法, __ new __ __, __ __ init __ , __ call __ , __ str __ , __ del __

    当然还有一些常用的魔法方法, 篇幅有限嘛, 毕竟, 关键的是掌握这些, 方法在何种情景下使用是最为关键的. 比如, __ new __ 是静态方法, 可用在 单例设计上, __ init __ 用在类的初始化上, 大家都懂哈; __ call __ 可以用在 装饰器上 ..... 等等.

    下篇再接着整几个, 我突然来劲了还.

  • 相关阅读:
    2017NOIP游记 (格式有点炸)
    Qtree3
    [P2769] 猴子上树
    [Usaco2005 Dec]Cleaning Shifts 清理牛棚
    [NOIP2014] 解方程
    [ZJOI2012] 灾难
    [洛谷P3941] 入阵曲
    [SCOI2009] 最长距离
    [JLOI2011] 飞行路线
    虚拟化数电底层
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12257585.html
Copyright © 2011-2022 走看看