[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(300)		# 调用类方法 <class '__main__.Apple'> 300
a1.print_total()		# 300
a2.print_total()		# 300
Apple.print_total()     # 300

b1 = Banana()
b1.set_total(400)		# 调用类方法 <class '__main__.Banana'> 400
b2 = Banana()
b1.print_total()		# 400
b2.print_total()		# 400
Banana.print_total()	# 400

Fruit.print_total()		# 0
Fruit.set_total(100)	# 调用类方法 <class '__main__.Fruit'> 100
Fruit.print_total()		# 100

不管你创建多少个 Apple 或 Banana 的实例对象,这些实例对象都是共用一个 cls.total (注意函数内变量前不加 cls 关键字的话,则是局部变量)。所以类内是怎么修改类的公有变量的呢?显而易见,通过 类方法 @classmethod 关键字 定义的函数可以修改类的公有变量。调用@classmethod方法的时候隐形传入的参数为该对象所对应的类。 其实,Python 中还有一个 __class__ 关键字可以修改类的公有变量。

class A(object):
    total = 0
    def test(self):
        print(self.__class__.total)
        self.__class__.total = 300
        print(self.__class__.total)

A().test()	# 0	300

__class__ 后面再聊一聊。

cls 还有一个作用,就是生成新的实例对象:

class A:
    def __init__(self, name):
        self.name = name

    @classmethod
    def new_instance(cls, name):
        return cls(name)

a = A("a")
b = a.new_instance("New Instance")
print(id(a), id(b), a is b) # 1833642382984 1833642486152 False
1.3.3 staticmethod 的作用

限制访问,更安全,且静态方法节约内存。

待续————————后续会分析类的内存。

2. __call__、__new__、__init__

不知道你面试的时候,有没有人问你这三个的区别呢?

实际上 __init__ 并不是真正意义上的构造方法,__init__ 方法所做的工作是在类的对象创建好之后进行变量的初始化。__new__ 方法才会真正创建实例,是类的构造方法。一个可调用的对象加括号,就是触发这个对象所在的类中的 __call__ 方法的执行。

1. __call__

此方法会在实例作为一个函数被“调用”时被调用;如果定义了此方法,则 x(arg1, arg2, ...) 就相当于 x.__call__(arg1, arg2, ...) 的快捷方式。

不仅Python函数是真正的对象,任何Python对象都可以表现得像函数。为此,只需实现实例方法__call__。——《流畅的Python》

class A:
    def __init__(self):
        self.__name = "A"   # 私有变量

    def __call__(self, *args, **kwargs):
        print(f"{self.__get_name()} {args} {kwargs}")

    def __get_name(self):   # 私有函数
        return self.__name

a = A()
a() 			# A () {}
a.__call__()    # A () {}
print(callable(a))  # True

注意:在元类中,__call__ 的首个参数为 cls。看不懂以下代码没关系,可以先跳过。

class A(type):
    def __call__(cls, *args, **kwargs):
        print("metaclass A __call__")
        print(f"{cls} {args} {kwargs}")

class B(metaclass=A):
    def __init__(self):
        self.__name = "B"
        
b = B()

# metaclass A __call__
# <class '__main__.B'> () {}

2. __new__

调用类时会运行类的 __new__ 方法创建一个实例,然后运行 __init__ 方法,初始化实例,最后把实例返回给调用方。

一般情况下不需要覆盖 __new__ 方法。

class A:
    def __new__(cls, *args, **kwargs):	 #__new__是一个类方法,在对象创建的时候调用
        print("调用 __new__")
        return super().__new__(cls)   # Python3 在不指名父类的情况下,所有类的父类都是object
        # super(A, cls).__new__(cls)


    def __init__(self):     #__init__是一个实例方法,在对象创建后调用,对实例属性做初始化
        print("调用 __init")
        self.name = "A"


a = A()   # 调用 __new__    调用 __init
print(a.name)     # A

__new__ 使用情况:

  1. 当类继承(如str、int、unicode、tuple或者forzenset等)不可变类型且默认的 __new__()方法不能满足需求的时候。

  2. 用来实现工厂模式或者单例模式或者进行元类编程(元类编程中常常需要使用 __new__()来控制对象创建。)

2.1 __new__ 实现单例模式

利用运行 __init__ 方法,初始化实例之前,会运行类的 __new__ 方法,我们可以在 __new__ 方法判断类是否已经创建过实例对象,如果是,则返回已经创建好的实例对象,否则新建一个实例对象并返回。

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):   # hasatter() 判断一个对象是否有某个属性
            cls._instance = super().__new__(cls)
        return cls._instance

s1, s2 = Singleton(), Singleton()
s3 = Singleton.__dict__['_instance']      # __dict__ 可以看后文
print(s1, s2, s3)
print(s1 is s2, s2 is s3)

# <__main__.Singleton object at 0x000001B7045956C8> <__main__.Singleton object at 0x000001B7045956C8> <__main__.Singleton object at 0x000001B7045956C8>
# True True
# <__main__.Singleton object at 0x000001B7045956C8>

拓展知识:[Python] 实现单例模式的四种方式及单例模式日志

关于 __call____new____init__ 的区别: [Python] 深入理解元类并区分元类中的init、call、new方法

3. __del__

3.1 __del__使用方法

__del__ 方法为 “析构方法”,用于实现对象被销毁时所需的操作。常用于释放对象所占用的资源,如文件资源、网络连接、数据库连接等。

class A:
    def __del__(self):
        # 当对象被销毁时,会自动调用这个方法
        print('__del__ 方法被调用了')

a = A()
del a

# __del__ 方法被调用了

什么时候对象会被销毁呢?这取决于 Python 的垃圾回收机制。我们先看看这句话:

del x 并不直接调用 x.__del__() 。前者会将 x 的引用计数减一,而后者仅会在 x 的引用计数变为零时被调用。

这个是怎么回事呢?我们看看下面这个代码:

import time
class A:
    def __del__(self):
        # 当对象被销毁时,会自动调用这个方法
        print('__del__ 方法被调用了')

a1 = A()
a2 = a1
del a1
for x in range(5):
    print("waiting...")
    time.sleep(1)
del a2


# waiting...
# waiting...
# waiting...
# waiting...
# waiting...
# __del__ 方法被调用了

输出5个 waiting… ,且 a1、a2 都被del了,__del__ 方法才被调用,而且只被调用了一次。

3.2 垃圾回收机制

Python 使用 引用计数器(Reference counting) 的方法来管理内存中的对象,即针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。当其他对象引用该对象时,其引用计数会增加1,而删除一个对当前对象的引用,其引用计数会减1。只有当引用计数的值为0的时候该对象才会被垃圾收集器回收,因为它表示这个对象不再被其他对象引用,是个不可达对象。

缺点: 无法解决循环引用的问题,即两个对象相互引用。因为两个对象的引用计数器都不为0,该对象并不会被垃圾收集器回收,而无限循环导致一直在申请内存而没有释放,所以最后出现了内存耗光的情况。例如:

class Leak:
    def __init__(self):
        print(f"object {id(self)} created")

while True:
    A = Leak()
    B = Leak()
    A.b = B     # 循环引用
    B.a = A     # 循环引用
    A = None
    B = None

两种方式可以触发垃圾回收:

  • 通过显式地调用 gc.collect() 进行垃圾回收。详情参考gc 模块
  • 在创建新的对象为其分配内存的时候,检查threshold阈值,当对象的数量超过threshold的时候便自动进行垃圾回收。

4. __str__、__repr__

我们直接打印对象或者使用 str()、和 repr() 打印对象时,分别会调用 __str__()、__repr__() 两个函数。

4.1 __str__

打印对象时,会调用对象的 __str__() 方法,默认会打印类名和对象的地址名。

class A: pass

class B:
    def __str__(self):
        print('B 的 __str__ 方法被调用了')
        return f"{super().__str__()}"	# 默认 super 为 object 

class C:
    def __str__(self):
        return 'C 的 __str__ 方法被调用了'

class D(C): pass    # __str__ 会被继承


a, b, c, d = A(), B(), C(), D()
print(a)		 # <__main__.A object at 0x000001FDC04B1788> 
print(str(a))    # <__main__.A object at 0x000001FDC04B1788>
print(b)		 # B 的 __str__ 方法被调用了     <__main__.B object at 0x000001FDCF53EC48>
print(str(b))    # B 的 __str__ 方法被调用了     <__main__.B object at 0x000001FDCF53EC48>
print(c)		 # C 的 __str__ 方法被调用了
print(str(c))    # C 的 __str__ 方法被调用了
print(d)		 # C 的 __str__ 方法被调用了
print(str(d))    # C 的 __str__ 方法被调用了

4.2 __repr__

当不存在 __str__() 时,会转而调用 __repr__() 。

class A:
    def __repr__(self):
        print(super().__repr__())
        return 'A 的 __repr__ 方法被调用了'

class B(A): pass    # __repr__ 会被继承


a, b = A(), B()
print(a)    # <__main__.A object at 0x000002A31AC50408>     A 的 __repr__ 方法被调用了
print(str(a))
print(repr(a))
print(b)    # <__main__.B object at 0x000002A31AC4ED48>     A 的 __repr__ 方法被调用了
print(str(b))
print(repr(b))

4.3 两者区别

1)两者之间的目标不同:str() 主要面向用户,其目的是可读性,返回形式为用户友好性和可读性都较强的字符串类型;而 repr() 面向的是Python解释器,或者说开发人员,其目的是准确性,其返回值表示Python解释器内部的含义,常作为编程人员debug用途。

2)在解释器中直接输入a时默认调用repr()函数,而print(a)则调用str()函数。

3)repr()的返回值一般可以用eval()函数来还原对象,通常来说有如下等式。

obj == eval(repr(obj))

4)这两个方法分别调用内建的 __str__() 和 __repr__() 方法,一般来说在类中都应该定义 __repr__()方法,而 __str__() 方法则为可选,当可读性比准确性更为重要的时候应该考虑定义 __str__() 方法。如果类中没有定义 __str__() 方法,则默认会使用 __repr__() 方法的结果来返回对象的字符串表示形式。用户实现 __repr__() 方法的时候最好保证其返回值可以用 eval() 方法使对象重新还原。

5. __class__

5.1 __class__

我们平时使用type(object)查看对象的类型,得到的就是__class__。它是类的一个内置属性,也是每个类和其实例的属性,指向的是一个类的引用。

类名.__class__总是返回 <class ‘type’>,实例名.__class__返回实例所属的类的一个引用

class A(object): pass

a = A()
print(a.__class__, type(a))	# <class '__main__.A'> <class '__main__.A'>
print(A.__class__, type(A))	# <class 'type'> <class 'type'>

注:type实际上是Python的一个内建元类,用来直接指导类的生成。在Python元类中必然会见到这个 type

5.2 __class__的作用

  1. 访问类的公有变量。
class A(object):
    total = 0
    def test(self):
        print(self.__class__.total)
        self.__class__.total = 300
        print(self.__class__.total)

A().test()	# 0	300
  1. 通过已有实例的__class__属性生成一个新的实例。
class A(object): pass

a = A()
b = a.__class__()
print(id(a), id(b))	# 2515739605384 2515739651912 	id不同代表不是同一个实例
  1. 在类中返回该类的一个新实例。python的内置模块就有很多这样的应用,例如python的字符串对象,其中对字符串对象的很多操作都会返回字符串对象的一个新实例。
def strip(self, chars=None): 
    return self.__class__(self.data.strip(chars))
def swapcase(self): 
    return self.__class__(self.data.swapcase())
def title(self): 
    return self.__class__(self.data.title())
def translate(self, *args):
    return self.__class__(self.data.translate(*args))
def upper(self): 
    return self.__class__(self.data.upper())

__class__小节参考博客:https://luobuda.github.io/2015/01/16/python-class/

6. __doc__

__doc__属性用于生成对象的帮助文本。

>>> def func(n):
...     '''return n'''
...     return n
...
>>> class A:
...     """This is class A"""
...     pass
...
>>> A.__doc__
'This is class A'
>>> func.__doc__
'return n'

控制台中的 help() 函数或IDE等工具需要显示特性的文档时,会从特性的__doc__属性中提取信息。

>>> help(func)
Help on function func in module __main__:

func(n)
    return n

>>> help(A)
Help on class A in module __main__:

class A(builtins.object)
 |  This is class A
 |
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)

7. __dir__

把对象传给 dir() 函数时调用,列出属性。例如,dir(obj) 触发 类.__dir__(obj) 方法。

>>> class A: pass
...
>>> A.__dir__
<method '__dir__' of 'object' objects>
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__n
e__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

8. __dict__

每一个类都有一个__dict__属性,其中包含的是它的所有属性,又称为类属性。

>>> class A:
...     name = "Name of Class A"
...     def func(self):
...         print(A.__class__.name)
...
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'name': 'Name of Class A', 'func': <function A.func at 0x000001DE62B95048>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of
'A' objects>, '__doc__': None})

访问类的属性和方法:

>>> A.name
'Name of Class A'
>>> A.__dict__['name']
'Name of Class A'
>>> A.func
<function A.func at 0x000001DE62B95048>
>>> A.__dict__['func']
<function A.func at 0x000001DE62B95048>

查看类型:

>>> type(A.__dict__['func'])
<class 'function'>
>>> type(A.func)
<class 'function'>

新增一个属性:

>>> A.new_attr = 1
>>> A.__dict__['new_attr']
1
>>> a = A()
>>> a.new_attr
1

9. __base__、__bases__

__base__ 得到的是首位父类,__bases__ 得到的是所有的父类。所有类的基类,都是 object

>>> int.__base__
<class 'object'>
>>> int.__bases__
(<class 'object'>,)
>>> str.__base__
<class 'object'>
>>> str.__bases__
(<class 'object'>,)
>>> type.__base__		# type 的基类是 object
<class 'object'>
>>> object.__base__		# object 是所有类的基类,输出为空
>>>						

自定义类与多继承:

>>> class A: pass
...
>>> class B: pass
...
>>> class C(A, B): pass
...
>>> A.__base__
<class 'object'>			# 自定义类的基类也是 object
>>> C.__base__
<class '__main__.A'>		# __base__ 只会输出首位父类
>>> C.__bases__
(<class '__main__.A'>, <class '__main__.B'>)	# __bases__ 输出所有父类
>>> C.__base__.__base__		# 访问父类的父类
<class 'object'>

10. __name__

类都有一个 __name__,得到的是类名。

>>> class A: pass
...
>>> A.__name__
'A'
>>> int.__name__
'int'
>>> float.__name__
'float'
>>> object.__name__
'object'
>>> def func(): pass	# 函数也有__name__
...
>>> func.__name__
'func'

实例对象没有__name__

>>> a = A()
>>> a.__name__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute '__name__'
>>> b = "banana"
>>> b.__name__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__name__'
  • 59
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值