Python 魔术方法

Python 魔术方法

官方网站:https://docs.python.org/3/reference/datamodel.html

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

举两个例子:

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = 'spades diamonds clubs hearts'.split()

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, position):
        return self._cards[position]

这个例子实现了一副扑克牌,并通过 __len__ 和 __getitem__ 方法使得该类可以使用 len() 函数和序列的基本功能。这里的__getitem__是魔法函数中的其中一个,具有的功能是返回一个有序化数组的值。在定义的类magic中,你引用了一个魔法函数,这个magic类就拥有了该魔法函数的功能。当使用for循环的时候,因为getitem基于你的magic类一个可迭代的功能,所以magic类具有可迭代功能。

from math import hypot

class Vector:

    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Vector(%r, %r)' % (self.x, self.y)

    def __abs__(self):
        return hypot(self.x, self.y)

    def __bool__(self):
        return bool(abs(self))

    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

这个例子则实现了基本的向量操作,被使其具备了初始化对象(__init__),字符化输出(__repr__),取模(__abs__),判断真假(__bool__),加(__add__)和乘(__mul__)的操作。 

更完整的对象实现可以参见:Python 内置方法创建 Python 风格对象

魔法函数对类的影响

如上若是,仅仅在类中添加了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 方法。不过,repr 方法更为基础,因为当不存在 str 方法时,Python 会自动调用 repr 方法。

集合序列

1. __len__
2. __getitem__
3. __setitem__
4. __delitem__
5. __contains__

 定义了 __getitem__ 方法就具有了列表的基本功能,可以通过 [] 进行取值。同时,虽然 for(本质调用 __iter__)和 in(本质调用__contains__),但如果这些函数未定义,那么将通过 __getitem__ 进行循环来实现。

迭代

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,这说明,在类中,如果new和init同时存在会优先调用new。

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

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

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

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

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

总而言之就是:

  1. new:创建对象时调用,会返回当前对象的一个实例,new是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例,是个静态方法,常用于允许继承不可变类型(str,int, tuple)

  2. init:创建完对象后调用,对当前对象的一些实例初始化,无返回值,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是创建对象是调用的。

最后给出一个 __new__ 实现单例的应用

class Test(object):
	_instance = None
	
	def __new__(cls, *args, **kwargs):
		if cls._instance is None:
			cls._instance = super(Test, cls).__new__(cls)
		return cls._instance
			
	def __init__(self, *args, **kwargs):
		self.value = args[0]
		self.a = kwargs['a']
		
obj1= Test(1, a='a')
print(obj1.value)
print(obj1.a)

obj2 = Test(2, a='b')
print(obj1.value)
print(obj1.a)

print(obj1 is obj2)

返回结果:

1
a
2
b
True

属性相关

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. 使用魔法函数实现高灵活性,实现自己所需要的独特的数据类型。

一些魔术方法直接和内建函数相对,在这种情况下,调用他们的方法很简单,但是,如果是另外一种不是特别明显的调用方法,这个附录介绍了很多并不是很明显的魔术方法的调用形式。

魔术方法 调用方式 解释
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ 在创建实例的时候被调用
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ 在创建实例的时候被调用
__cmp__(self, other) self == other, self > other, 等。 在比较的时候调用
__pos__(self) +self 一元加运算符
__neg__(self) -self 一元减运算符
__invert__(self) ~self 取反运算符
__index__(self) x[self] 对象被作为索引使用的时候
__nonzero__(self) bool(self) 对象的布尔值
__getattr__(self, name) self.name # name 不存在 访问一个不存在的属性时
__setattr__(self, name, val) self.name = val 对一个属性赋值时
__delattr__(self, name) del self.name 删除一个属性时
__getattribute(self, name) self.name 访问任何属性时
__getitem__(self, key) self[key] 使用索引访问元素时
__setitem__(self, key, val) self[key] = val 对某个索引值赋值时
__delitem__(self, key) del self[key] 删除某个索引值时
__iter__(self) for x in self 迭代时
__contains__(self, value) value in self, value not in self 使用 in 操作测试关系时
__concat__(self, value) self + other 连接两个对象时
__call__(self [,...]) self(args) “调用”对象时
__enter__(self) with self as x: with 语句环境管理
__exit__(self, exc, val, trace) with self as x: with 语句环境管理
__getstate__(self) pickle.dump(pkl_file, self) 序列化
__setstate__(self) data = pickle.load(pkl_file) 序列化

希望这个表格对你对于什么时候应该使用什么方法这个问题有所帮助。

你可能感兴趣的:(Python)