优有价值的人在任何时代都会被尊重。
魔法函数概念
魔法函数是以双下划线开头并且以双下划线结尾的功能函数,可以用来定义自己类的新特性。
举一个例子:
class magic:
def __init__(self,num):
self.num = num
def __getitem__(self, item):
return self.num[item]
a = magic(['1','2','3'])
for x in a:
print x
返回结果:
1
2
3
简直鹅妹子嘤!
这里的__getitem__是魔法函数中的其中一个,具有的功能是返回一个有序化数组的值。在定义的类magic中,你引用了一个魔法函数,这个magic类就拥有了该魔法函数的功能。
当使用for循环的时候,因为getitem基于你的magic类一个可迭代的功能,所以magic类具有可迭代功能。
魔法函数对类的影响
如上若是,仅仅在类中添加了getitem这个魔法函数,就能直接使用for循环,也就是说魔法函数在一定程度上可以影响python自定义类的语法,或者说是增强了你这个类的类型。
通过python内置的大量魔法函数,你可以创造出具有独特个性的数据类型,符合业务的需求。
举个例子:
class magic:
'''
这是功能性注释
使用__doc__就可以看到啦
'''
def __init__(self,num):
self.num = num
def __len__(self):
return 6666666
a = magic(5)
print len(a)
print a.__doc__
返回结果:
6666666
这是功能性注释
使用__doc__就可以看到啦
通过魔法函数__len__实现获取,len本来是获取字符串或者列表数量长度,但是通过自定义类就实现了返回6666666.
内置魔法函数
python中内置了大量的魔法函数,尝试理解和记下这些魔法函数在以后的业务需求中可以如鱼得水,所以说还是要背啊~~
字符串表示
1. __repr__ 格式化字符串式样,主用开发模式下
2. __str__ 常用的字符串,格式化字符串
这连个魔法函数的作用都是和字符串相关,一般来说在print打印中会调用这个魔法函数
class magic:
def __init__(self,num):
self.num = num
def __str__(self):
return (self.num + '\n')*5
a = magic('浪子好帅啊')
print a
输出结果:
浪子好帅啊
浪子好帅啊
浪子好帅啊
浪子好帅啊
浪子好帅啊
这里使用print a和使用 print a.__str__()效果是一样的。同理repr(a)和a.__repr__
区别:
__repr__ 目的是为了表示清楚,是为开发者准备的。
__str__ 目的是可读性好,是为使用者准备的。
__repr__ 应该尽可能的表示出一个对象来源的类以及继承关系,方便程序员们了解这个对象。而 __str__ 就简单的表示对象,而不要让不懂编程的以为输出的是 bug。
同时定义 repr 方法和 str 方法时,print() 方法会调用 str 方法。
集合序列
1. __len__
2. __getitem__
3. __setitem__
4. __delitem__
5. __contains__
迭代
1. __iter__
2. __next__
在存储数据的数据结构中有list,set,tuple,dict,当使用for循环他们的时候,本质上是做了两件事。
- 获得一个迭代对象,调用__iter__魔法函数
- 循环的时候,循环调用__next__魔法函数
举个例子:
class magic:
def __init__(self,num):
self.num = num
def __iter__(self):
# 使用__iter__,magic类就变成了可迭代对象
return self
def __next__(self):
# __next__魔法函数的作用是在循环的时候,无限提供输出下一个值,直到没有数据后抛出异常
if self.num >5:
# 设置上限
raise StopIteration
else:
self.num += 1
return self.num
a = magic(-5)
for x in a:
print(x)
返回结果:
-4
-3
-2
-1
0
1
2
3
4
5
6
可能有些难理解,这是第一次自己做出来的一个数据类型,他的作用是提供一个原始值,自增长到6就停止。
如果这样做也可以的:
a = magic(-5)
print(a.__next__())
print(next(a))
返回结果:
-4
-3
总的来说,使用iter魔法函数,这个类就变成了可迭代对象,但是如何调用这个可迭代对象的数值呢?这个时候就需要使用next来循环调用了。
注意:含有\next__()函数的对象都是一个迭代器(Iterator),也就是说__next__要继承Iterator,__iter__要继承Iterable,继承的类来自与collections
可调用
1. __call__
一个类实例变成一个可调用对象,只需要实现一个特殊方法__call__()。在创建类的时候,只要使用了call这个魔法函数,那么这个类就是可调用的。
函数之所以可以被直接调用,原因在于他的底层是用call实现的。
举个例子:
class magic:
def __init__(self):
pass
def __call__(self, num):
return '浪子:' + num
a = magic()
print(a('admin'))
运行结果:
浪子:admin
可以看到结果直接运行出来了。这样的例子不够深刻,尝试把场景转移到业务需求中来。
浪子餐馆卖馒头,每次买出一个,店小二就会大喊’xxx买了一个馒头花了1块钱‘
使用类来实现:
class ao:
def __init__(self,name):
self.name =name
def __call__(self, money):
return self.name + '大老板买了一个馒头~~花了%s元~~让我们感谢这位老铁~~'%money
a = ao('小桃红')
print(a(15))
b = ao('猫饼饼')
print(b(666))
运行结果:
小桃红大老板买了一个馒头~~花了15元~~让我们感谢这位老铁~~
猫饼饼大老板买了一个馒头~~花了666元~~让我们感谢这位老铁~~
总的来说,就是你定义类中只要有call,那么就可以直接调用,注意call是对象不是类。他和new以及init的关系如下(可以先不看)
new: 对象的创建,是一个静态方法,第一个参数是cls。(想想也是,不可能是self,对象还没创建,哪来的self)
init : 对象的初始化, 是一个实例方法,第一个参数是self。
call : 对象可call,注意不是类,是对象。
问题:为了让下面这段代码运行,需要增加哪些代码?
class A(object):
def __init__(self,a,b):
self.__a = a
self.__b = b
def myprint(self):
print 'a=', self.__a, 'b=', self.__b
a1=A(10,20)
a1.myprint()
a1(80)
为了能让对象实例能被直接调用,需要实现call方法
class A(object):
def __init__(self,a,b):
self.__a = a
self.__b = b
def myprint(self):
print('a=', self.__a, 'b=', self.__b)
def __call__(self, *args, **kwargs):
print(args)
with上下文管理
1. __enter__
2. __exit__
with上下文管理器,对于那些需要必须成对打开关闭的操作是非常方便的,比如打开关闭文件。他的实现原理就是通过enter和exit这两个魔法函数来实现的。
先看看常规的实现一个with上下文管理器的步骤,Pymysql with 操作来源
import contextlib
@contextlib.contextmanager
def mysql(host='127.0.0.1',user='root',passwd='root',db='meizi',port=3306,charset='utf8'):
conn = pymysql.connect(host='127.0.0.1',user='root',passwd='root',db='meizi',port=3306,charset='utf8')
cursor = conn.cursor()
try:
yield cursor
finally:
conn.commit()
cursor.close()
conn.close()
# # 执行sql
# with mysql() as cursor:
# print(cursor)
# row_count = cursor.execute("select * from tb7")
# row_1 = cursor.fetchone()
# print row_count, row_1
想要自己实现这种类的话,就必须要使用到enter和exit这两个魔法函数。
比如上面的
with mysql() as cursor
把步骤分析一下:
- 当with的后面mysql()函数被执行的时候,对象的enter方法被调用
- 函数主动发起数据库连接,获取一个游标
- 随后使用yield生成器寄存这个游标
- 这个游标被赋值给as后面的cursor
- 当with后面的代码全都执行完毕后,调用前面返回对象的exit方法
举个例子:
class magic:
def __enter__(self):
print('enter魔法函数执行')
return 'enter魔法函数执行完毕'
def __exit__(self, exc_type, exc_val, exc_tb):
print('exti魔法函数执行')
return 'exit魔法函数执行完毕'
def ma():
return magic()
with ma() as a:
print('随便执行一些东西')
运行结果:
enter魔法函数执行
随便执行一些东西
exti魔法函数执行
是不是想到了装饰器?通过查看contextlib库的源码发现导入了wrapper装饰器,说到装饰器你是不是你又想到了闭包?然后又想到了变量的作用域?
是的,这些知识都是一个完整的体系,相互串联。
还没结束,继续深入分析。
在exit魔法函数的值中的含义
exc_type:异常类(如果抛出异常,这里获取异常的类型 )
exc_value:异常实例(如果抛出异常,这里显示异常内容)
exc_tb:异常位置(如果抛出异常,这里显示所在位置)
traceback:追溯对象()
因为with操作本身就是为了简写try/finally操作的。
比如在熟悉的with操作文本文件一样,打开文件是放在enter函数中,关闭文件放在exit函数中。
with真正强大之处是它不仅可以完善的管理上下文,同时还可以处理异常。
__enter__
__enter__ 用于赋值给 as 后面的变量。不过 with 语句中 as 不是必须的。__enter__ 和 __exit__ 必须并用。
__exit__
用于捕获异常,它的返回值是一个 boolean 对象。除了 self 之外,必须传入另外三个参数,分别表示 exception 的类型,值(如 IndexError: list index out of range 中,冒号后面的部分就是值),以及 traceback。
返回 True 则表示这个异常被忽略。
返回 None, False 等则这个异常会抛出。
如果要忽略所有的异常可以这样写:
def __exit__(self, exc_type, exc_value, traceback):
return True
经过测试, SyntaxError 是不能忽略的,其他已知的是可以的。
数值转换
1. __abs__
2. __bool__
3. __int__
4. __float__
5. __hash__
6. __index__
元类相关
1. __new__
2. __init__
说到init你会想到创建类的时候实例的对象,但是new是啥?
依照Python官方文档的说法,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。
是不是感觉有点迷?没关系,用例子来说明就好了。
class magic(object):
def __init__(self,nums):
self.nums = nums
print '666'
print 'init:' + self.nums
def __new__(cls, nums):
cls.nums = nums
print 'new:'+cls.nums
a = magic('A')
返回结果:
new:A
这里看到只输出了new的对象,并且注意magic类继承了object,在上一篇文章中有说道,元类(即类的类)都必须要继承自object,因为记住了,继承自object的新类才有__new__
重新修改一下代码,让init也执行下呢?
class magic(object):
def __init__(self,nums):
self.nums = nums
print '666'
print 'init:' + self.nums
def __new__(cls, nums):
cls.nums = nums
print 'new:'+cls.nums
# 执行到这里会打印内容
return object.__new__(cls)
# 这里会返回一个内容
# 返回的内容会传递到init中的self中去
a = magic('A')
返回结果:
new:A
666
init:A
大家注意看,这里返回了object的new方法,然后init就执行了,这证明上面说的,new会先于init执行,并且new方法返回的值就是init方法中的self。
继续举例子:
class magic(object):
def __init__(self,nums):
self.nums = self.nums
self.langzi = 'langzi'
print '666'
print 'init:' + self.nums
def __new__(cls, nums):
print nums
# 这里打印出传递进来的数值
cls.nums = nums+'BCDEFG'
cls.langzi = '浪子'
print 'new:'+cls.nums
return object.__new__(cls)
a = magic('A')
print '-'*10
print a.langzi
print a.nums
先猜一猜会输出什么呢?
返回结果:
A
new:ABCDEFG
666
init:ABCDEFG
----------
langzi
ABCDEFG
来分析一下
- 打印传递进来的A
- 随后赋值给cls.nums,打印new:ABCDEFG,同时定义cls.langzi=浪子
- 继续执行,new方法返回object对象,这个时候会执行到init
- 这里的self其实就是new中返回的对象
- 然后定义self.nums和self.langzi=langzi(这个时候这个类的langzi对象就变成了langzi,不再是浪子)
- 打印666
- 打印出init:ABCDEFG
- 打印—————-
- 验证这个类中的属性值a.langzi和a.nums
通过分析这个实例步骤,来进一步研究new和init的关系:
通过上面代码的执行结果我们可以发现程序首先执行了new,之后执行的init,这说明,在类中,如果new和init同时存在会优先调用new。
new方法会返回所构造的对象,init则不会。init无返回值。
new至少要有一个参数cls,代表要实例化的类(类对象),此参数在实例化时由Python解释器自动提供。
new必须要有返回值,返回实例化出来的实例,这点在自己实现,new时要特别注意,可以return父类new出来的实例或者直接是object的new出来的实例。
init有一个参数self,就是这个new返回的实例,init在new的基础上可以完成一些其它初始化的动作,init不需要返回值
我们可以将类比作制造商,new方法就是前期的原材料购买环节,init方法就是在有原材料的基础上,加工,初始化商品环节
总而言之就是:
- new:创建对象时调用,会返回当前对象的一个实例,new是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法,常用于允许继承不可变类型(str,int, tuple)
- init:创建完对象后调用,对当前对象的一些实例初始化,无返回值,init是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
- new是控制对象的生成过程,init是完善对象,如果new方法不返回对象,则不会调用init魔法函数。
下面这段代码输出什么?
class B(object):
def fn(self):
print 'B fn'
def __init__(self):
print "B INIT"
class A(object):
def fn(self):
print 'A fn'
def __new__(cls,a):
print "NEW", a
if a>10:
return super(A, cls).__new__(cls)
return B()
def __init__(self,a):
print "INIT", a
a1 = A(5)
a1.fn()
a2=A(20)
a2.fn()
返回结果:
NEW 5
B INIT
B fn
NEW 20
INIT 20
A fn
使用new方法,可以决定返回那个对象,也就是创建对象之前,这个可以用于设计模式的单例、工厂模式。init是创建对象是调用的。
属性相关
1. __getattr__,__setattr__
2. __getattribute__,setattribute__
3. __dir__
属性描述符
1. __get__,__set__,__delete__
协程
1. __await__
2. __aiter__
3. __anext__
4. __aenter__
5. __aexit__
这个是最重要也是最难的,以后慢慢说。
梳理
- 魔法函数是python内置的,具有双下划线开头结尾的特性。
- 自定义的类中使用魔法函数,该类就具有了该魔法函数的功能,比如使用iter魔法函数,该类就具有迭代功能。
- 使用魔法函数实现高灵活性,实现自己所需要的独特的数据类型。