[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 代表类。想要访问类的实例:

  1. 可以通过 cls.变量名 访问,不过只能在类方法 @classmethod 内访问。
  2. 一般的类方法可以通过 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(
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值