python魔法函数

阅读目录

 

1 什么是魔法函数

先来定义一个类:

In [1]:
class Company(object):
    def __init__(self, employee_list):
        self.employee_list = employee_list
In [4]:
company = Company(['张三', '李四', '王五'])
print(company)
 
<__main__.Company object at 0x7f7c4046ebd0>

此时,直接对 Company 实例化的对象进行 print 输出时,打印出来的信息是类名称和地址信息。但如果我们想看的不是这些,而是想输出 employee_list,怎么做呢?

In [7]:
class Company(object):
    def __init__(self, employee_list):
        self.employee_list = employee_list
<span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">employee_list</span><span class="p">)</span>
In [8]:
company = Company(['张三', '李四', '王五'])
print(company)
 
['张三', '李四', '王五']

在这个例子中,我们添加了一个__str__()函数,然后再打印输出 Company 类实例时,输出的就是 employee_list,但是,我们并没有显式地调用__str__()函数,这是因为,在对一个实例使用 print() 函数时,Python 内部机制自动会调用__str__()函数。

类似__str__()这种函数在类内部还有很多,这一类函数,我们统称为魔法函数。现在,我们明确一下魔法函数的范畴:

魔法函数是指类内部以双下划线开头,并且以双下划线结尾的函数,在特定时刻,Python 会自动调用这些函数。魔法函数不是通过继承等机制获得的,而是类一旦定义,Python 内部机制自动会给类赋予这些特殊的函数,且用户是不能创建魔法函数的,即使函数名以双下划线开头和双下划线结尾。通过魔法函数可以实现许多个性化、便捷的操作。

2 Python 中的魔法函数

2.1 字符串表示:__str____repr__

  • __str__

  • __repr__

在很多时候,人们都容易将__str____repr__两个方法记混,甚至认为这两的功能是一样的,但事实上还是有一些差异的。

__str__在上文中已经说过,是用于将实例对象进行 print 输出时使用。如下所示:

In [17]:
class Company(object):
    def __init__(self, name=None):
        self.name = name
<span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="s1">'*****公司名称为:</span><span class="si">%s</span><span class="s1">*****'</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
In [18]:
c = Company(name='腾讯')
print(c)
 
***** 公司名称为:腾讯 *****

对实例化对象是用 print()函数输出时,Python 内部机制会想调用 str() 方法,在 str() 方法内部继续调用__str__方法实现输出:

In [23]:
str(c)
Out[23]:
'***** 公司名称为:腾讯 *****'

但是如果我们不是用 print() 函数而直接输出 c,那么,输出结果依然是原来默认的:

In [19]:
c
Out[19]:
<__main__.Company at 0x7f7c4049d050>

这是因为直接输出类实例化对象时,调用的是__repr__方法:

In [20]:
class Company(object):
    def __init__(self, name=None):
        self.name = name
<span class="k">def</span> <span class="nf">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="s1">'*****公司名称为:</span><span class="si">%s</span><span class="s1">*****'</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>

<span class="k">def</span> <span class="nf">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="s1">'#####公司名称为:</span><span class="si">%s</span><span class="s1">#####'</span> <span class="o">%</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
In [22]:
c = Company(name='腾讯')
c
Out[22]:
##### 公司名称为:腾讯 #####

综上所述,__str____repr__的区别在于,__str__方法在对实例化对象是用 print()函数输出时调用,其实时 Python 内部机制调用 str() 方法,然后 str() 方法内部继续调用__str__方法获取输出字符串。而__repr__是在开发模式下直接输出实例化对象时被调用。

2.2 集合、序列相关:__len____getitem____setitem____delitem____contains__

  • __len__

Python 内置函数中有一个 len()函数,这个函数适用于获取序列类型数据的长度,在对一个实例使用 len() 方法时,真实输出的其实是__len__的返回值。所以,只要一个类内部实现了__len__方法,就可以对其实例使用__len__方法。

In [24]:
class Company(object):
    def __init__(self, name=None, employee_lst=None):
        self.name = name
        self.employee_lst = employee_lst
<span class="k">def</span> <span class="nf">__len__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">employee_lst</span><span class="p">)</span>
In [26]:
c = Company(name='腾讯', employee_lst=['张三', '李四', '王五'])
len(c)
Out[26]:
3
  • __getitem____setitem____delitem__

我们知道,在 Python 的 dict 类型数据中,可以通过方括号的方式来赋值、取值和删除值,例如通过 t_dict['attr1'] = 1 的方式进行赋值,通过 t_dict['attr1'] 可以取得值,通过 del t_dict['attr1'] 可以删除一个值。那么在自定义的一个类里面,通过__getitem____setitem____delitem__这三个,我们也可以让我们自定义类的实例化对象拥有这样的操作。

In [48]:
class Company(object):
    def __init__(self):
        self.company_info = {}
<span class="k">def</span> <span class="nf">__setitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">key</span><span class="p">,</span><span class="n">value</span><span class="p">):</span>  <span class="c1"># 令类实例化对象可以通过c[key] = value的方式赋值</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">company_info</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
    
<span class="k">def</span> <span class="nf">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">key</span><span class="p">):</span>          <span class="c1"># 令类实例化对象可以通过c[key]的方式取值</span>
        <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">company_info</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
    
<span class="k">def</span> <span class="nf">__delitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>          <span class="c1"># 令类实例化对象可以通过del c[key]的方式删除值</span>
    <span class="k">del</span> <span class="bp">self</span><span class="o">.</span><span class="n">company_info</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
In [51]:
c = Company()
c['name'] = '腾讯'
c['type'] = 'IT'
print(c['name'])
del c['name']
print(c.company_info)
 
腾讯
{'type': 'IT'}

有些时候,配合 Python 的反射机制类使用这三个魔法函数会有更加魔幻的效果,可以直接对实例属性进行操作:

In [59]:
class Company(object):
<span class="k">def</span> <span class="nf">__setitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">key</span><span class="p">,</span><span class="n">value</span><span class="p">):</span>
    <span class="nb">setattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
    
<span class="k">def</span> <span class="nf">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">key</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">__delitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
    <span class="nb">delattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
In [60]:
c = Company()
c['name'] = '腾讯'
c['type'] = 'IT'
In [61]:
c['type']
Out[61]:
'IT'
In [62]:
del c['type']
In [63]:
c['type']
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-63-56601054285d> in <module>
----> 1c['type']

<ipython-input-59-b82d5d10cbb4> in getitem(self, key)
5
6 def getitem(self,key):
----> 7return getattr(self, key)
8
9 def delitem(self, key):

AttributeError: 'Company' object has no attribute 'type'

  • __contains__

对于 Python 中 dict 类型的数据结构,可以使用in关键字判断序列内部是否包含某个 key,在我们自定义的类中,如果定义了__contains__方法,那么也能使用in关键字判断是否包含某个属性。

In [67]:
class Company(object):
    def __init__(self):
        self.company_info = {}
<span class="k">def</span> <span class="nf">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">key</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">company_info</span>
In [69]:
c = Company()
c.company_info['name'] = '腾讯'
print('name' in c)
print('type' in c)
 
True
False

结合反射机制使用:

In [70]:
class Company(object):
    def __setitem__(self,key,value):
        setattr(self, key, value)
<span class="k">def</span> <span class="nf">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
    <span class="k">return</span> <span class="nb">hasattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">)</span>
In [75]:
c = Company()
c['name'] = '腾讯'
print('name' in c)
print('type' in c)
 
True
False

2.3 迭代相关:__iter____next__

  • __iter____next__

我之前写过一篇博客《为什么 for 循环可以遍历 list:Python 中迭代器与生成器》,很详细得介绍了 Python 中关于迭代器与生成器的原理。关于迭代器和生成器,其核心就在于__iter____next__两个方法。

iter 是 Iterable 的简写,表示“可迭代的”,所以,任何内部定义了__iter__的对象,我们都可以称之为可迭代对象,在 Python 中,有一个类专门与之对应:Iterable,我们可以通过判断对象是否是 Iterable 类的实例来判断是否是可迭代对象。进一步的,如果一个类内部定义了__iter__方法的同时,也定了__next__方法,那么,它的实例化对象就是迭代器,也有一个类与迭代器对应,那就是 Iterator。

In [99]:
from collections.abc import Iterable
from collections.abc import Iterator
In [81]:
isinstance(123, Iterable)  # 整型不是可迭代对象
Out[81]:
False
In [101]:
isinstance('abc', Iterator)  # 字符串不是迭代器
Out[101]:
False
In [102]:
isinstance('abc', Iterable)  # 字符串是可迭代对象
Out[102]:
True
In [103]:
class Company():
    def __iter__(self):  # 自定义一个类,只要实现了 __iter__ 方法,就是可迭代对象
        pass
print('Company() 是可迭代对象吗:',isinstance(Company(),Iterable))
print('Company() 是迭代器吗:',isinstance(Company(),Iterator))
 
Company() 是可迭代对象吗: True
Company() 是迭代器吗: False
In [104]:
class Company():
    def __iter__(self):  
        pass
    def __next__(self):  # 自定义一个类,同时实现了 __iter__ 方法和 __next__ 方法,就是迭代器
        pass
print('Company() 是可迭代对象吗:',isinstance(Company(),Iterable))
print('Company() 是迭代器吗:',isinstance(Company(),Iterator))
 
Company() 是可迭代对象吗: True
Company() 是迭代器吗: True

知道怎么区分可迭代对象和迭代器之后,就可以解释__iter____next__的作用了。那就是定义了这两个方法,就可以对实例化对象进行遍历。以 for 循环为例,通过 for 循环对一个可迭代对象进行迭代时,for 循环内部机制会自动通过调用 iter() 方法执行可迭代对象内部定义的__iter__方法来获取一个迭代器,然后一次又一次得迭代过程中通过调用 next() 方法执行迭代器内部定义的__next__方法获取下一个元素,当没有下一个元素时,for 循环自动捕获并处理 StopIteration 异常。

In [94]:
class B():
    def __init__(self, lst):
        self.lst = lst
        self.index = 0
    def __iter__(self):
        print('B.__iter__() 方法被调用')
        return self
    def __next__(self):
        try:
            print('B.__next__() 方法被调用')
            value = self.lst[self.index]
            self.index += 1
            return value
        except IndexError:
            raise StopIteration()
In [98]:
b = B([1, 2, 3])
for i in b:
    print(i)
 
B.__iter__() 方法被调用
B.__next__() 方法被调用
1
B.__next__() 方法被调用
2
B.__next__() 方法被调用
3
B.__next__() 方法被调用

2.4 可调用:__call__

  • __call__

假如有一个对象 A,如果 A 是一个类,我们使用 A()进行调用,那么就是创建一个 A 类的实例化对象,如果 A 是一个函数,我们使用 A() 就是调用函数 A。那么,如果 A 是一个某个类的实例化对象时,A() 是进行什么操作呢?答案就是调用该类的__call__方法,我们可以理解为,__call__就是“()”运算符。

In [88]:
class Company(object):
    def __init__(self):
        pass
    def __call__(self, name):
        self.name = name
        print('__call__ 方法被调用,name:%s' % self.name)
In [89]:
c = Company()
c('腾讯')
 
__call__ 方法被调用,name: 腾讯

现在,我们证实了__call__就是“()”运算法,那么,是不是类、函数这些可使用“()”运算符的对象内部都定义有__call__函数呢?答案是肯定的。

In [90]:
class Company(object):
    def __init__(self):
        pass
def A():
    pass
In [91]:
print('类 Company 是否有 __call_ 方法:', hasattr(Company, '__call__'))
print('函数 A 是否有 __call_ 方法:', hasattr(A, '__call__'))
 
类 Company 是否有 __call_ 方法: True
函数 A 是否有 __call_ 方法: True

借助这一特性,我们可以弥补 hasattr()函数的不足。我们知道,通过 hasattr() 函数可以判断一个类内部是否有某个属性,但是没法判断到底是变量还是方法,但进一步借助方法内部肯定定义有__call__这个特性,就可以进一步判断。

In [92]:
class Company(object):
    def __init__(self):
        self.name = None
    def func(self):
        pass
In [93]:
c = Company()
print('c 中是否存在属性 name:', hasattr(c, 'name'))
print('c 中是否存在属性 func:', hasattr(c, 'func'))
print('name 是函数吗:', hasattr(c.name, '__call__'))
print('func 是函数吗:', hasattr(c.func, '__call__'))
 
c 中是否存在属性 name: True
c 中是否存在属性 func: True
name 是函数吗: False
func 是函数吗: True

2.5 with 上下文管理器:__enter____exit__

只要你熟悉 Python 开发,那么对 with 上下文管理就一定不会陌生,例如操作文本时,我们通常习惯with open来对打开文件,获得句柄。使用 with 来打开文件的好处就是在打开文件后进行操作的过程中,无论是否出现异常,Python 都会对关闭句柄,也就是一定会进行收尾工作,避免占用内存资源。

这种上下文管理机制是怎么实现的呢?这就涉及到我们现在要说的两个两个魔法函数__enter____exit__

__enter__:with 语句开始执行时候调用

__exit__:with 语句结束时候调用,注意,无论 with 语句中的代码是否正常结束,都会执行__exit__方法

除了读写文件之外,我们使用 Python 来操作数据库时,也需要做收尾处理,也就是关闭数据库连接,那么,这个时候我们也可以用 with 来进行。

In [3]:
import pymysql

class Dao(object):
def init(self, cursor_type=None):
self.conn = pymysql.connect( # 创建数据库连接
host='192.168.31.201', # 要连接的数据库所在主机 ip
database='test',
user='root', # 数据库登录用户名
password='admin123456', # 登录用户密码
charset='utf8' # 编码,注意不能写成 utf-8
)

    <span class="bp">self</span><span class="o">.</span><span class="n">cursor</span> <span class="o">=</span> <span class="kc">None</span>
    <span class="k">if</span> <span class="n">cursor_type</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">cursor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">(</span><span class="n">pymysql</span><span class="o">.</span><span class="n">cursors</span><span class="o">.</span><span class="n">DictCursor</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">cursor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">cursor</span>  <span class="c1"># 返回类实例本身</span>

<span class="k">def</span> <span class="nf">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">exc_type</span><span class="p">,</span> <span class="n">exc_value</span><span class="p">,</span> <span class="n">exc_trace</span><span class="p">):</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>  <span class="c1"># 提交事务</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">cursor</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>  <span class="c1"># 关闭游标</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">conn</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>  <span class="c1"># 关闭数据库连接</span>
In [6]:
with Dao() as cursor:
    cursor.execute("select * from employee;")
    e = cursor.fetchall()
    print(e)
 
((1, '张三'), (2, '李四'))

2.6 属性相关:__getattr____setattr____getattribute__

  • __getattr____setattr__

__getattr__函数的作用: 在一个类实例中查找一个属性时,通过__dict__失败, 那么会调用到类的__getattr__函数,如果没有定义这个函数,那么抛出 AttributeError 异常。也就是说__getattr__是属性查找的最后一步。

In [13]:
class Company(object):
    def __init__(self, name):
        self.company_name = name
<span class="k">def</span> <span class="nf">fun</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'fun方法被调用……'</span><span class="p">)</span>
    
<span class="k">def</span> <span class="nf">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'__getattr__方法被调用'</span><span class="p">)</span>
    <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s1">'哥们,你查找的属性"</span><span class="si">%s</span><span class="s1">"不存在'</span> <span class="o">%</span> <span class="n">name</span><span class="p">)</span>
In [14]:
c = Company('腾讯')

如果提前找到了某个属性,那么将不会继续调用__getattr__

In [15]:
print(c.company_name)
print(c.fun)
 
腾讯
<bound method Company.fun of <__main__.Company object at 0x7fa0a8077100>>

当属性不存在是,将会调用__getattr__,所以,我们可以通过__getattr__函数来定义当找不到属性时候的提醒方式,甚至是返回一个其他的默认值。

In [16]:
c.abc
 
__getattr__ 方法被调用
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-a2bb1cff9d71> in <module>
----> 1c.abc

<ipython-input-13-810c2a9c4f3c> in getattr(self, name)
8 def getattr(self, name):
9 print('__getattr__ 方法被调用')
---> 10raise AttributeError('哥们,你查找的属性"%s"不存在' % name)

AttributeError: 哥们,你查找的属性 "abc" 不存在

通过__getattr__方法,我们可以对 Python 的字典进行改造,另外开始通过dict_name.key的方式来访问。

In [21]:
class Dict(dict):
    def __init__(self, *args, **kwargs):
        super(Dict, self).__init__(*args, **kwargs)
<span class="k">def</span> <span class="nf">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
        <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="sa">r</span><span class="s2">"'Dict' object has no attribute '</span><span class="si">%s</span><span class="s2">'"</span> <span class="o">%</span> <span class="n">key</span><span class="p">)</span>
In [22]:
d = Dict({'name': '张三', 'age': '李四'})
d.name
Out[22]:
'张三'

__getattr__是用来获取属性,那么__setattr__就是用来给属性赋值,当我们使用实例.key=value的方式进行赋值的时候就一定会调用__setattr__方法。

In [27]:
class Company(object):
    def __init__(self, name):
        self.company_name = name
<span class="k">def</span> <span class="nf">__setattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">value</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">"__setattr__方法被调用"</span><span class="p">)</span>

# self.name = value # 第一种写法
# object.setattr(self, name, value) # 第二种写法
self.dict[name] = value # 第三种写法

In [29]:
c = Company('腾讯')
c.company_name = '阿里'
print(c.company_name)
 
__setattr__ 方法被调用
__setattr__ 方法被调用
阿里

为什么__setattr__被调用了两次呢?因为在__init__中也使用了一次实例.key=value的方式赋值。

所以,在定义__setattr__的时候一定要注意,一定不能使用上述代码中被注释掉的第一种写法,因为使用self.name = value进行赋值时,本身又会再次调用__setattr__方法,这就造成了无线递归,造成 bug。所以使用第二和第三种写法才是正确的。

继续用__setattr__方法改造字典:

In [30]:
class Dict(dict):
    def __init__(self, *args, **kwargs):
        super(Dict, self).__init__(*args, **kwargs)
<span class="k">def</span> <span class="nf">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">):</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
    <span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
        <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="sa">r</span><span class="s2">"'Dict' object has no attribute '</span><span class="si">%s</span><span class="s2">'"</span> <span class="o">%</span> <span class="n">key</span><span class="p">)</span>
        
<span class="k">def</span> <span class="nf">__setattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="bp">self</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">name</span>
In [31]:
d = Dict()
d.name = '张三'
print(d.name)
 
张三
  • __getattribute__

__getattribute__与上面的__getattr__很相似,区别在于__getattr__是在类中未找到属性时调用,而__getattribute__是不管类中有无查找的属性存在,都优先调用。不过在使用__getattribute__方法市,必须注意陷入无限递归,当在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归,直到 Python 递归深度限制,所以,在__getattribute__中获取属性时,需要通过父类的__getattribute__方法获取对应的属性。

In [32]:
class Company(object):
    def __init__(self, name):
        self.company_name = name
<span class="k">def</span> <span class="nf">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'__getattribute__方法被调用'</span><span class="p">)</span>
    <span class="k">return</span> <span class="nb">object</span><span class="o">.</span><span class="fm">__getattribute__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>

# raise AttributeError('哥们,你查找的属性"%s"不存在' % name)

In [33]:
c = Company('腾讯')
c.company_name
 
__getattribute__ 方法被调用
Out[33]:
'腾讯'
In [34]:
c.abc
 
__getattribute__ 方法被调用
 
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-34-a2bb1cff9d71> in <module>
----> 1c.abc

<ipython-input-32-e6bee225b017> in getattribute(self, name)
5 def getattribute(self, name):
6 print('__getattribute__ 方法被调用')
----> 7return object.getattribute(self, name)
8 # raise AttributeError('哥们,你查找的属性"%s"不存在' % name)

AttributeError: 'Company' object has no attribute 'abc'

  • __dict__dir()__dir__

上文中提到过__dict____dict__是对象的一个属性,并不是函数,它的作用是返回对象的所有属性名为 key,属性值为 value 的一个字典,注意,这里所说的所有属性是指数据对象本身的属性,例如类的__dict__只包含类本身的属性和函数,而类实例也只包含类实例的属性。这一点与dir()函数不同,dir()将会返回一个列表,列表中包含对象所有有关的属性名。也就是说,__dict__dir()的子集。而dir()实际上调用的是__dir__方法。

In [37]:
class Company(object):
    def __init__(self, name):
        self.company_name = name
<span class="k">def</span> <span class="nf">fun</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">'fun方法被调用……'</span><span class="p">)</span>
In [38]:
c = Company('腾讯')
In [40]:
c.__dict__
Out[40]:
{'company_name': '腾讯'}
In [41]:
Company.__dict__
Out[41]:
mappingproxy({'__module__': '__main__',
              '__init__': <function __main__.Company.__init__(self, name)>,
              'fun': <function __main__.Company.fun(self)>,
              '__dict__': <attribute '__dict__' of 'Company' objects>,
              '__weakref__': <attribute '__weakref__' of 'Company' objects>,
              '__doc__': None})
In [44]:
dir(c)
Out[44]:
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'company_name',
 'fun']
In [45]:
c.__dir__()
Out[45]:
['company_name',
 '__module__',
 '__init__',
 'fun',
 '__dict__',
 '__weakref__',
 '__doc__',
 '__repr__',
 '__hash__',
 '__str__',
 '__getattribute__',
 '__setattr__',
 '__delattr__',
 '__lt__',
 '__le__',
 '__eq__',
 '__ne__',
 '__gt__',
 '__ge__',
 '__new__',
 '__reduce_ex__',
 '__reduce__',
 '__subclasshook__',
 '__init_subclass__',
 '__format__',
 '__sizeof__',
 '__dir__',
 '__class__']