python魔法函数_python魔法函数

1 什么是魔法函数¶

先来定义一个类:

In [1]:

class Company(object):

def __init__(self, employee_list):

self.employee_list = employee_list

In [4]:

company = Company(['张三', '李四', '王五'])

print(company)

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

In [7]:

class Company(object):

def __init__(self, employee_list):

self.employee_list = employee_list

def __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 = name

def __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]:

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

In [20]:

class Company(object):

def __init__(self, name=None):

self.name = name

def __str__(self):

return '*****公司名称为:%s*****' % self.name

def __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 = name

self.employee_lst = employee_lst

def __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] = value

def __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)

in

----> 1c['type']

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 = {}

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)

True

False

结合反射机制使用:

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)

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

)

self.cursor = None

if 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 = name

def 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)

腾讯

>

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

In [16]:

c.abc

__getattr__方法被调用

---------------------------------------------------------------------------

AttributeError Traceback (most recent call last)

in

----> 1c.abc

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 = name

def __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 = name

def __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)

in

----> 1c.abc

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

def 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__': ,

'fun': ,

'__dict__': ,

'__weakref__': ,

'__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__']

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值