python:魔法方法

魔法方法

在python中,有一些内置好的特定的方法,这些方法在进行特定的操作时会自动被调用,称之为魔法方法,下面介绍几种常见的魔法方法。

1、__init__:

初始化函数,在创建实例对象为其赋值时使用,在__new__之后,__init__必须至少有一个参数self,就是这个__new__返回的实例,__init__是在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
例1:

class Dog:
    def __init__(self,name,color):
        self.name = name
        self.color = color

dog = Dog("xiao","red")

2:__new__:

很多人认为__init__是类的构造函数,其实不太确切,__init__更多的是负责初始化操作,相当于一个项目中的配置文件,__new__才是真正的构造函数,创建并返回一个实例对象,如果__new__只调用了一次,就会得到一个对象。继承自object的新式类才有__new__这一魔法方法,__new__至少必须要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供,__new__必须要有返回值,返回实例化出来的实例(很重要),这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例,若__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行。__new__是唯一在实例创建之前执行的方法,一般用在定义元类时使用。

__new__ 使用时注意以下6点:
1. __new__ 是在一个对象实例化的时候所调用的第一个方法
2. 它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法
3. __new__ 决定是否要使用该 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __new__ 没有返回实例对象,则 __init__ 不会被调用
4. __new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string
5. __new__ return的是一个构建的实例
6.__new__ 方法平时很少去重写它,一般都是让python用默认的方案执行就可以了。但是有一种情况需要重写这个魔法方法,就是当继承一个不可变类型的时候,它的特性就显得尤为重要了

例2:

lass A(object):
    pass

class B(A):
    def __init__(self):
        print("__init__被调用")

    def __new__(cls):
        print("__new__被调用")
        print(id(cls))
        return object.__new__(A)
    #注意此处采用了参数A而不是cls,__new__没有正确的返回当前类cls的实例

b = B()
print(b)
print(type(b))
print(id(A))
print(id(B))

"""
上面代码的输出结果为:
__new__被调用
3070243603352
<__main__.A object at 0x000002CAD8D385F8>
<class '__main__.A'>
3070243610904
3070243603352
"""

从运行结果可以看出,__new__中的参数cls和B的id是相同的,表明__new__中默认的参数cls就是B类本身,而在return时,并没有正确返回当前类cls的实例,而是返回了其父类A的实例,因此__init__这一魔法方法并没有被调用,此时__new__虽然是写在B类中的,但其创建并返回的是一个A类的实例对象。现在将return中的参数A变为cls,再来看一下运行结果:

例3:

class A(object):
    pass

class B(A):
    def __init__(self):
        print("__init__被调用")

    def __new__(cls):
        print("__new__被调用")
        print(id(cls))
        return object.__new__(cls)
        #__new__正确的返回当前类cls的实例

b = B()
print(b)
print(type(b))
print(id(A))
print(id(B))

"""
上面代码的输出结果为:
__new__被调用
1786091884120
__init__被调用
<__main__.B object at 0x0000019FDBE885F8>
<class '__main__.B'>
1786091888840
1786091884120
"""

可以看出,当__new__正确返回其当前类cls的实例对象时,__init__被调用了,此时创建并返回的是一个B类的实例对象。

例3:

class Capstr(str):
    def __new__(cls, string):
        string = string.upper()
        return str.__new__(cls,string)

a = Capstr("my name")
print(a)

#上面代码的输出结果为:MY NAME

Python定制容器

Python中,像序列类型(如列表、元祖、字符串)或映射类型(如字典)都是属于容器类型,容器是可定制的。

1. 如果说你希望定制的容器是不可变的话,你只需要定义 __len__() 和 __getitem__() 方法。

2. 如果你希望定制的容器是可变的话,除了 __len__() 和 __getitem__() 方法,你还需要定义 __setitem__() 和 __delitem__() 两个方法。


__getitem__的简单用法:

当一个类中定义了__getitem__方法,那么它的实例对象便拥有了通过下标来索引的能力。

例4:

class A(object):
    def __getitem__(self, item):
        return item

a = A()
print(a[5], a[12])

用__getitem__实现斐波那契数列:
例4_1:

class Fib(object):
    def __getitem__(self, item):
        if item == 0 or item == 1:
            return 1
        else:
            a, b = 1, 1
            for i in range(item - 1):
                a, b = b, a+b
            return b

fib = Fib()
for i in range(10):
    print(fib[i])

"""
上面代码的输出结果为:
1
1
2
3
5
8
13
21
34
55
"""

魔法方法的作用:

__getitem__(self,key):返回键对应的值。

__setitem__(self,key,value):设置给定键的值

__delitem__(self,key):删除给定键对应的元素。

__len__():返回元素的数量

__reversed__():当使用reversed函数翻转对象时调用

__contains__():当使用in,not in 对象的时候 调用(not in 是在in完成后再取反,实际上还是in操作)

例5:

class ArithemeticSequence(object):
    def __init__(self, start=0, step=1):
        print('Call function __init__')
        self.start = start
        self.step = step
        self.myData = {}

    # 定义获取值的方法
    def __getitem__(self, key):
        print('Call function __getitem__')
        try:
            return self.myData[key]
        except KeyError:
            return self.start + key * self.step

    # 定义赋值方法
    def __setitem__(self, key, value):
        print('Call function __setitem__')
        self.myData[key] = value

    # 定义获取长度的方法
    def __len__(self):
        print('Call function __len__')
        # 这里为了可以看出__len__的作用, 我们故意把length增加1
        return len(self.myData) + 1

    # 定义删除元素的方法
    def __delitem__(self, key):
        print('Call function __delitem__')
        del self.myData[key]

s = ArithemeticSequence(1, 2)

print(s[3])    # 这里应该执行self.start+key*self.step,因为没有3这个key
s[3] = 100     # 进行赋值
print(s[3] )   # 前面进行了赋值,那么直接输出赋的值100
print(len(s))  # 我们故意多加了1,应该返回2
del s[3]       # 删除3这个key

"""
上面代码的输出结果为:
Call function __init__
Call function __getitem__
7
Call function __setitem__
Call function __getitem__
100
Call function __len__
2
Call function __delitem__
"""

例子6:

class Fib(object):
    def __getitem__(self, item):
        if isinstance(item, slice):
            print(item.start)
            print(item.stop)

            stop = item.stop
            if item.stop is None:
                stop = 100

            re_list = []

            a, b = 0, 1
            for i in range(stop):
                a, b = b, a + b
                re_list.append(a)

            return re_list[item.start:stop:item.step]

        elif item == 0 or item == 1:
            return 1
        else:
            a, b = 1, 1
            for i in range(item - 1):
                a, b = b, a+b
            return b

fib = Fib()
print(fib[1:20:2])

"""
上面代码的输出结果为:
1
20
[1, 3, 8, 21, 55, 144, 377, 987, 2584, 6765]
"""

生成器

1、通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[ ]改成( ),就创建了一个generator

2、迭代器需要我们自己去定义一个类和实现相关的方法,而生成器则只需要在普通的函数中加入一个yield语句即可

3、常规函数定义中,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间挂起函数的状态以便下次重它离开的地方继续执行(迭代器中无return语句)

4、yield与return的比较:
     相同:都有返回值的功能
  不同:return只能返回一次值,而yield可以返回多次值
例7:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x104feab40>

创建L和g的区别仅在于最外层的[ ]和( ),L是一个list,而g是一个generator。我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过generator的next()方法
例7_1:

>>> g.next()
0
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
16
>>> g.next()
25
>>> g.next()
36
>>> g.next()
49
>>> g.next()
64
>>> g.next()
81
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象(生成器函数返回生成器的迭代器)
例7_2

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print n
...
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next()方法,而是通过for循环来迭代它。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
例8:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print b
        a, b = b, a + b
        n = n + 1

>>> fib(6)
1
1
2
3
5
8

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:
例8_1:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
print(fib(6))

#上面代码的输出结果为:<generator object fib at 0x000001FBD7A24BA0>

1、这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

2、我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用next()来调用它,而是直接使用for循环来迭代:

例8_2:

def fibs():
    a = 0
    b = 1
    while True:
        a,b = b,a + b
        yield  a

for each in fibs():
    if each > 20:
        break
    print(each)

#上面代码的输出结果为:1、1、2、3、5、8、13

猜你喜欢

转载自blog.csdn.net/qq_39314932/article/details/81175256