Python 的 type 及常用魔法方法(上)

CatherineC00 2020-02-03

魔法方法是 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 __ 可以用在 装饰器上 ..... 等等.

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

相关推荐