AI智能
改变未来

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_listdef __str__(self):return str(self.employee_list)

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 = namedef __str__(self):return \'*****公司名称为:%s*****\' % self.name

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 = namedef __str__(self):return \'*****公司名称为:%s*****\' % self.namedef __repr__(self):return \'#####公司名称为:%s#####\' % self.name

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 = nameself.employee_lst = employee_lstdef __len__(self):return len(self.employee_lst)

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 = {}def __setitem__(self,key,value):  # 令类实例化对象可以通过c[key] = value的方式赋值self.company_info[key] = valuedef __getitem__(self,key):          # 令类实例化对象可以通过c[key]的方式取值return self.company_info[key]def __delitem__(self, key):          # 令类实例化对象可以通过del c[key]的方式删除值del self.company_info[key]

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):def __setitem__(self,key,value):setattr(self, key, value)def __getitem__(self,key):return getattr(self, key)def __delitem__(self, key):delattr(self, key)

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)56     def __getitem__(self,key):----> 7return getattr(self, key)89     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 = {}def __contains__(self, key):return key in self.company_info

In[69]:

c = Company()c.company_info[\'name\'] = \'腾讯\'print(\'name\' in c)print(\'type\' in c)
TrueFalse

结合反射机制使用:

In[70]:

class Company(object):def __setitem__(self,key,value):setattr(self, key, value)def __contains__(self, key):return hasattr(self, key)

In[75]:

c = Company()c[\'name\'] = \'腾讯\'print(\'name\' in c)print(\'type\' in c)
TrueFalse

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 Iterablefrom 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__方法,就是可迭代对象passprint(\'Company()是可迭代对象吗:\',isinstance(Company(),Iterable))print(\'Company()是迭代器吗:\',isinstance(Company(),Iterator))
Company()是可迭代对象吗: TrueCompany()是迭代器吗: False

In[104]:

class Company():def __iter__(self):passdef __next__(self):  # 自定义一个类,同时实现了__iter__方法和__next__方法,就是迭代器passprint(\'Company()是可迭代对象吗:\',isinstance(Company(),Iterable))print(\'Company()是迭代器吗:\',isinstance(Company(),Iterator))
Company()是可迭代对象吗: TrueCompany()是迭代器吗: True

知道怎么区分可迭代对象和迭代器之后,就可以解释

__iter__

__next__

的作用了。那就是定义了这两个方法,就可以对实例化对象进行遍历。以for循环为例,通过for循环对一个可迭代对象进行迭代时,for循环内部机制会自动通过调用iter()方法执行可迭代对象内部定义的

__iter__

方法来获取一个迭代器,然后一次又一次得迭代过程中通过调用next()方法执行迭代器内部定义的

__next__

方法获取下一个元素,当没有下一个元素时,for循环自动捕获并处理StopIteration异常。

In[94]:

class B():def __init__(self, lst):self.lst = lstself.index = 0def __iter__(self):print(\'B.__iter__()方法被调用\')return selfdef __next__(self):try:print(\'B.__next__()方法被调用\')value = self.lst[self.index]self.index += 1return valueexcept IndexError:raise StopIteration()

In[98]:

b = B([1, 2, 3])for i in b:print(i)
B.__iter__()方法被调用B.__next__()方法被调用1B.__next__()方法被调用2B.__next__()方法被调用3B.__next__()方法被调用

2.4 可调用:

__call__

  • __call__

假如有一个对象A,如果A是一个类,我们使用A()进行调用,那么就是创建一个A类的实例化对象,如果A是一个函数,我们使用A()就是调用函数A。那么,如果A是一个某个类的实例化对象时,A()是进行什么操作呢?答案就是调用该类的

__call__

方法,我们可以理解为,

__call__

就是“()”运算符。

In[88]:

class Company(object):def __init__(self):passdef __call__(self, name):self.name = nameprint(\'__call__方法被调用,name:%s\' % self.name)

In[89]:

c = Company()c(\'腾讯\')
__call__方法被调用,name:腾讯

现在,我们证实了

__call__

就是“()”运算法,那么,是不是类、函数这些可使用“()”运算符的对象内部都定义有

__call__

函数呢?答案是肯定的。

In[90]:

class Company(object):def __init__(self):passdef 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 = Nonedef 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: Truec中是否存在属性func: Truename是函数吗: Falsefunc是函数吗: 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 pymysqlclass Dao(object):def __init__(self, cursor_type=None):self.conn = pymysql.connect( # 创建数据库连接host=\'192.168.31.201\', # 要连接的数据库所在主机ipdatabase=\'test\',user=\'root\', # 数据库登录用户名password=\'admin123456\', # 登录用户密码charset=\'utf8\' # 编码,注意不能写成utf-8)self.cursor = Noneif cursor_type:self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)else:self.cursor = self.conn.cursor()def __enter__(self):return self.cursor  # 返回类实例本身def __exit__(self, exc_type, exc_value, exc_trace):self.conn.commit()  # 提交事务self.cursor.close()  # 关闭游标self.conn.close()  # 关闭数据库连接

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 = namedef fun(self):print(\'fun方法被调用……\')def __getattr__(self, name):print(\'__getattr__方法被调用\')raise AttributeError(\'哥们,你查找的属性\"%s\"不存在\' % name)

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)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError(r\"\'Dict\' object has no attribute \'%s\'\" % key)

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 = namedef __setattr__(self, name, value):print(\"__setattr__方法被调用\")#         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)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError(r\"\'Dict\' object has no attribute \'%s\'\" % key)def __setattr__(self, key, name):self[key] = name

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 = namedef __getattribute__(self, name):print(\'__getattribute__方法被调用\')return object.__getattribute__(self, name)#         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 = namedef fun(self):print(\'fun方法被调用……\')

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__\']
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » python魔法函数