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 __ 可以用在 装饰器上 ..... 等等.
下篇再接着整几个, 我突然来劲了还.