Python魔法函数

优有价值的人在任何时代都会被尊重。

魔法函数概念

魔法函数是以双下划线开头并且以双下划线结尾的功能函数,可以用来定义自己类的新特性。

举一个例子:

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循环他们的时候,本质上是做了两件事。

  1. 获得一个迭代对象,调用__iter__魔法函数
  2. 循环的时候,循环调用__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的关系如下(可以先不看)

  1. new: 对象的创建,是一个静态方法,第一个参数是cls。(想想也是,不可能是self,对象还没创建,哪来的self)

  2. init : 对象的初始化, 是一个实例方法,第一个参数是self。

  3. 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

把步骤分析一下:

  1. 当with的后面mysql()函数被执行的时候,对象的enter方法被调用
  2. 函数主动发起数据库连接,获取一个游标
  3. 随后使用yield生成器寄存这个游标
  4. 这个游标被赋值给as后面的cursor
  5. 当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

来分析一下

  1. 打印传递进来的A
  2. 随后赋值给cls.nums,打印new:ABCDEFG,同时定义cls.langzi=浪子
  3. 继续执行,new方法返回object对象,这个时候会执行到init
  4. 这里的self其实就是new中返回的对象
  5. 然后定义self.nums和self.langzi=langzi(这个时候这个类的langzi对象就变成了langzi,不再是浪子)
  6. 打印666
  7. 打印出init:ABCDEFG
  8. 打印—————-
  9. 验证这个类中的属性值a.langzi和a.nums

通过分析这个实例步骤,来进一步研究new和init的关系:

通过上面代码的执行结果我们可以发现程序首先执行了new,之后执行的init,这说明,在类中,如果newinit同时存在会优先调用new

new方法会返回所构造的对象,init则不会。init无返回值。

new至少要有一个参数cls,代表要实例化的类(类对象),此参数在实例化时由Python解释器自动提供。

new必须要有返回值,返回实例化出来的实例,这点在自己实现,new时要特别注意,可以return父类new出来的实例或者直接是object的new出来的实例。

init有一个参数self,就是这个new返回的实例,initnew的基础上可以完成一些其它初始化的动作,init不需要返回值

我们可以将类比作制造商,new方法就是前期的原材料购买环节,init方法就是在有原材料的基础上,加工,初始化商品环节

总而言之就是:

  1. new:创建对象时调用,会返回当前对象的一个实例,new是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法,常用于允许继承不可变类型(str,int, tuple)
  2. init:创建完对象后调用,对当前对象的一些实例初始化,无返回值,init是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值。
  3. 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

参考链接2

属性相关

1. __getattr__,__setattr__
2. __getattribute__,setattribute__
3. __dir__

属性描述符

1. __get__,__set__,__delete__

协程

1. __await__
2. __aiter__
3. __anext__
4. __aenter__
5. __aexit__

这个是最重要也是最难的,以后慢慢说。

梳理

  1. 魔法函数是python内置的,具有双下划线开头结尾的特性。
  2. 自定义的类中使用魔法函数,该类就具有了该魔法函数的功能,比如使用iter魔法函数,该类就具有迭代功能。
  3. 使用魔法函数实现高灵活性,实现自己所需要的独特的数据类型。
坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%