[Python] 深入理解 self、cls、__call__、__new__、__init__、__del__、__str__、__class__、__doc__等 )
前情提要
刷各种Python书籍的时候,都会看到很多类里有各种 __xxx__ 函数,这些函数,涉及到了很多知识点,故想做一个汇总。弄懂这些 __something__ 之后,相信会对 Python 的机制有更深的了解。本篇文章花费了很多工夫,查了不少资料,为了确保结果的正确性,所有代码都是我纯手码然后执行,运行环境为Pycharm 2019.2.4 x64,使用的 Python 版本为 3.7.5。
如果你想要学会看Python优秀框架的源码,这些Python高级知识,也是必须要掌握的。
1. self 和 cls 的区别
一句话,cls
代表类,self
代表实例对象。重要的事情说三遍。
一句话,cls
代表类,self
代表实例对象。重要的事情说三遍。
一句话,cls
代表类,self
代表实例对象。重要的事情说三遍。
1.1 self 关键字
self
是类的每一个实例对象本身,相当于 C++ 的 this 指针。
self 表示的就是实例对象本身,即类的对象在内存中的地址。 ——《编写高质量的代码:改善Python程序的91个建议》
class A:
def get_self(self):
return self
a1 = A()
print(a1.get_self()) # <__main__.A object at 0x00000250D18C66C8>
a2 = a1.get_self()
print(a1 is a2) # True 说明 a1 和 a2 是同一个实例对象
a3 = A()
print(a3 is a1) # False 说明 a3 是一个新的实例对象
理解了 self 关键字是什么,再看看类内的变量吧,这对我们了解 cls
关键字有帮助。
1.2 类的变量(公有、保护、私有)
它们分为类的公有变量、保护变量、私有变量和实例的公有变量、保护变量、私有变量。
Python 没有真正的私有。 —— 曾经说
很多人都是这么说的,但是经过亲测,Python 3 已经不能在外部访问 __xxx
私有变量了(通过对象名._类名__xxx 也不行),这说明 Python 的安全性变得越来越高了。
总结一下命名:
xx_
:以单下划线结尾仅仅是为了区别该名称与关键词。_xx
:以单下划线开头,表示这是一个保护成员,只有类对象和子类对象自己能访问到这些变量。以单下划线开头的变量和函数被默认当作是内部函数,使用from module improt *
时不会被获取,但是使用import module
可以获取。__xx
:双下划线开头,表示为私有成员,只允许类本身访问,子类也不行。__xx__
:双下划线开头,双下划线结尾。Python内部的名字,用来区别其他用户自定义的命名,以防冲突。Python不建议将自己命名的方法写为这种形式。
class A:
class_public = "class public" # 类的公有变量
_class_protected = "class protected" # 类的保护变量
__class_private = "class private" # 类的私有变量
name = "class A"
def __init__(self):
name = 'Local A' # 局部变量
self.name = "self A"
self.instance_public = "instance public" # 实例的公有变量
self._instance_protected = "instance protected" # 实例的保护变量
self.__instance_private = "instance private" # 实例的私有变量
print(name) # Local A 优先访问局部变量
a = A()
# 实例的私有变量,每个类的实例拥有自己的私有变量,各个实例的私有变量互不影响。
print(a.name) # self A 同名的 实例私有变量 覆盖了 类的公有变量
print(a.class_public) # class public
print(a._class_protected) # class protected (Pycharm 提示: Access to a protected member _instance_protected of a class)
# print(a.__class_private) # AttributeError: 'A' object has no attribute '__class_private'
print(a.instance_public) # instance public
print(a._instance_protected) # instance protected (Pycharm 提示: Access to a protected member _instance_protected of a class)
# print(a.__instance_private) # AttributeError: 'A' object has no attribute '__instance_private'
# 类内,函数外定义的变量,都是类的变量,所有类内函数需要通过 cls 关键字访问类的变量,外部可直接 类名.公有变量名 访问类的公有变量
print(A.name) # class A
print(A.class_public) # class public
print(A._class_protected) # class protected (Pycharm 提示: Access to a protected member _class_protected of a class)
# print(A.__class_private) # AttributeError: type object 'A' has no attribute '__class_private'
A.name = "Changed class A" # 修改公有变量
print(A.name)
A._class_protected = "Changed class protected" # 修改保护变量
print(A._class_protected)
想要了解更多变量的作用域,推荐文章:[Python] 变量名的四个作用域及 nonlocal 关键字
我们只知道了外部是怎么修改类的公有变量的,那么类内怎么修改类的公有变量呢? 这就涉及到了 cls
关键字了。
1.3 cls 关键字
cls
代表类。想要访问类的实例:
- 可以通过
cls.变量名
访问,不过只能在类方法@classmethod
内访问。 - 一般的类方法可以通过
self.__class__.变量名
访问。
先来了解类方法 @classmethod
和静态方法 @staticmethod
。
1.3.1 staticmethod和classmethod
Python中的静态方法(staticmethod)和类方法(classmethod)都依赖于装饰器(decorator)来实现。定义方法和调用方法如下:
class A(object):
def func(self, *args, **kwargs): pass # self 关键字
@staticmethod
def func1(*args, **kwargs): pass # 不需要 self 和 cls 关键字
@classmethod
def func2(cls, *args, **kwargs): pass # 不能缺少 cls 关键字
静态方法和类方法都可以通过类名.方法名(如A.f())或者实例.方法名(A().f())的形式来访问。其中静态方法没有常规方法的特殊行为,如绑定、非绑定、隐式参数等规则,而类方法的调用使用类本身作为其隐含参数,但调用本身并不需要显示提供该参数。
a = A()
a.func()
# A.func() # 报错 不能访问实例的函数
a.func1() # 类名.方法名 访问 staticmethod
A.func1() # 实例.方法名 访问 staticmethod
a.func2() # 类名.方法名 访问 classmethod
A.func2() # 实例.方法名 访问 classmethod 注意:cls 是作为隐含参数传入的
1.3.2 classmethod 的作用
类方法 @classmethod
用来做什么呢?下面我们看一个例子:
class Fruit(object):
total = 0
@classmethod
def print_total(cls):
print(cls.total)
# print(id(Fruit.total))
# print(id(cls.total))
@classmethod
def set_total(cls, value):
print(f"调用类方法 {
cls} {
value}")
cls.total = value
class Apple(Fruit): pass
class Banana(Fruit): pass
Fruit.print_total() # 0 注意:此处是0,我们会发现后面Apple、Banana修改total不会影响此值
a1 = Apple()
a1.set_total(200) # 调用类方法 <class '__main__.Apple'> 200
a2 = Apple()
a2.set_total(