python基础(2)

10 魔法方法

10.1 构造和析构

10.1.1 _ _init_ _(self[, ...])

        它相当于其他面向对象编程语言的构造方法,也就是类在实例化成对象的时候首先会调用的一个方法。这个构造方法可以用也可以不用,根据实际需求来就行。

        需要注意的是,_ _init_ _()方法的返回值一定是None,不能是其他。所以,只有在需要进行初始化的时候才重写_ _init_ _()方法。这个_ _init_ _()并不是实例化对象时第一个被调用的魔法方法。

10.1.2 _ _new_ _(cls[, ...])

        事实上,_ _new_ _()才是在一个对象实例化的时候调用的第一个方法。它与其他魔法方法不同,它的第一个参数不是self而是这个类(cls),而其他的参数会直接传递给_ _init_ _()方法。

        _ _new_ _()方法需要返回一个实例对象,通常是cls这个类实例化的对象,当然也可以返回其他对象。_ _new_ _()方法平时很少去重写它,一般让Python用默认的方案执行就可以了。但是有一种情况需要重写这个魔法方法,就是当继承一个不可变的类型的时候,它的特性就显得尤为重要了。

10.1.3 _ _del_ _(self)

        如果说_ _init_ _()和_ _new_ _()方法是对象的构造器的话,那么Python也提供了一个析构器,称为_ _del_ _()方法。当对象将要被销毁的时候,这个方法就会被调用。但一定要注意的是,并非del x就相当于自动调用x._ _del_ _(),_ _del_ _()方法是当垃圾回收机制回收这个对象的时候调用的。

10.2 算术运算

        Python 2.2以后,对类和类型进行了统一,做法就是将int()、float()、str()、list()、tuple()这些BIF转换为工厂函数。普通的BIF应该是<class 'builtin_function_or_method'>,而工厂函数则是<class 'type'>(类)。

        从下面例子看出对象是可以进行计算的。

        Python的魔法方法还提供了自定义对象的数值处理,通过对下面这些魔法方法的重写,可以自定义任何对象间的算术运算。

10.2.1 常见的算术运算

        举个例子来说明:

         有需要再针对每周方法细致研究吧。

10.2.2 反运算

        一些反运算相关的魔法方法:

        这里的反运算相关的魔法方法与上一节介绍的算术运算相关的魔法方法保持一一对应的关系,不同之处就是反运算的魔法方法多了一个“r”,例如,_ _add_ _()就对应_ _radd_ _()。实现方式就是:如果a对象的_ _add_ _()方法没有实现或者不支持相应的操作,那么Python就会自动调用b的_ _radd_ _()方法。

        举例说明:

10.2.3 一元操作符

        Python支持的一元操作符:_ _neg_ _()表示正号行为;_ _pos_ _()表示定义负号行为;而_ _abs_ _()表示定义abs()函数(取绝对值)被调用时的行为;_ _invert_ _()表示定义按位取反的行为。

10.3 属性访问(难点)

       通常可以通过点(.)操作符的形式去访问对象的属性,在前面也谈到了如何通过几个BIF适当地去访问属性:getattr、setattr、delattr。举个例子:

还介绍了一个名为property()函数的用法,这个property()使得我们可以用属性去访问属性:

 先运行该程序。然后实现效果如下:

        关于属性访问,肯定也有相应的魔法方法来管理。通过对这些魔法方法的重写,可以随心所欲地控制对象的属性访问。属性相关的魔法方法如下:

        针对上述提到的魔法方法,举个例子说明:(一般重写魔法方法后再调用super()父类的标准方法,否则可能会面临有一个死循环的陷阱)

 程序实现如下:

 下面程序就是陷入了死循环:

         __setattr__的if......else......语句不断进行调用赋值魔法函数__setattr__导致一直在循环出不来,这种一般在后面添加super()父类进行赋值即可。修改后如下:

class Rectangle:
    def __init__(self,width=0,height=0):
        self.width=width
        self.height=height
        print('init func')
    def __setattr__(self,name,value):
        if name == 'square':
            self.width = value
            self.height = value
        else:
            #self.name = value
            #下面为推荐方法
            super().__setattr__(name,value)
            #self.__dict__[name]=value
    def getArea(self):
        return self.width*self.height

10.4 描述符(难点)

       这一节要讲的内容为描述符(descriptor),用一句话来解释,描述符就是将某种特殊类型的类的实例指派给另一个类的属性。那什么是特殊类型的类呢?就是至少要在这个类里边定义_ _get_ _()、_ _set_ _()或__delete_ _()三个特殊方法中的任意一个。

       描述符相关的魔法方法如下:

 举个简单例子:

        从图上的内容看出,当访问x属性的时候,Python会自动调用描述符的_ _get_ _()方法,几个参数的内容分别是:self是描述符类自身的实例;instance是这个描述符类的拥有者所在的类的实例,在这里也就是Test类的实例;owner是这个描述符的拥有者所在的类本身。

        对x属性进行赋值操作的时候,Python会自动调用_ _set_ _()方法,前两个参数与_ _get_ _()方法是一样的,最后一个参数value是等号右边的值。最后一个del操作也是同样的道理。

        定义一个属于我们自己的MyProperty:(这个例子我一直没太懂)

 class MyProperty:
	def __init__(self,fget=None,fset=None,fdel=None):
		self.fget = fget
		self.fset = fset
		self.fdel = fdel
	def __get__(self,instance,owner):
		return self.fget(instance)
	def __set__(self,instance,value):
		self.fset(instance,value)
	def __delete__(self,instance):
		self.fdel(instace)

class C:
	def __init__(self):
		self._x = None
	def getX(self):
		return self._x
	def setX(self,value):
		self._x = value
	def delX(self):
		del self._x
	x = MyProperty(getX,setX,delX)

实现方式:

 下面举一个比较常见的例子(相对容易理解一点):

 程序实现如下:

 这里应该就能明白前面所说的描述符类里打印出的self、instance、owner、value各自的意义了。

10.5 定制序列(稍微难点)

        本节要谈的是定制容器,要想成功地实现容器的定制,便需要先谈一谈协议。协议是什么呢?协议(protocol)与其他编程语言中的接口很相似,它规定哪些方法必须定义。然而,在Python中的协议就显得不那么正式。事实上,在Python中,协议更像是一种指南。

        在Python中,像序列类型(如列表、元组、字符串)或映射类型(如字典)都属于容器类型。本节来讲定制容器,那就必须知道与定制容器有关的一些协议:

  • 如果希望定制的容器不可变,则只需要定义_ _len_ _()和__getitem_ _()方法。
  • 如果希望定制的容器是可变的,除了_ _len_ _()和_ _getitem_ _()方法,还需要定义_ _setitem_ _()和_ _delitem_ _()两个方法。  

        与定制容器相关的魔法方法:

        举个例子,写一个访问列表元素次数的程序:

class Countlist:
    def __init__(self,*args):
        self.values=[x for x in args]
        self.count={}.fromkeys(range(len(self.values)),0)
    def __len__(self):
        return len(self.values)
    def __getitem__(self,key):
        self.count[key]+=1
        return self.values[key]

 备注:fromkeys作为内嵌函数使用。

程序实现如下:

10.6 迭代器

        迭代的意思类似于循环,每一次重复的过程被称为一次迭代的过程,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。提供迭代方法的容器称为迭代器,通常接触的迭代器有序列(如列表、元组、字符)、字典等,它们都支持迭代的操作。

        Python提供了两个BIF:iter()和next()。对一个容器对象调用iter()就得到它的迭代器,调用next()迭代器就会返回下一个值,然后怎么样结束呢?如果迭代器没有值可以返回了,Python会抛出一个名为StopIteration的异常。利用这两个BIF,可以分析出for语句其实是这么工作的:

        关于实现迭代器的魔法方法有两个:_ _iter_ _()和_ _next_ _()。一个容器如果是迭代器,那就必须实现_ _iter_ _()魔法方法,这个方法实际上就是返回迭代器本身。接下来重点要实现的是_ _next_ _()魔法方法,因为它决定了迭代的规则。简单举个例子:

        这个迭代器的唯一亮点就是没有终点,所以如果没有跳出循环,它会不断迭代下去。通过在迭代器里增加参数来防止无限迭代。

10.7 生成器和生成器表达式

        生成器的学习,并不涉及魔法方法,甚至它巧妙地避开了类和对象,仅通过普通的函数就可以实现了。

        生成器其实是迭代器的一种实现,那既然迭代可以实现,为何还要生成器呢?有一句话叫“存在即合理”,生成器的发明一方面是为了使得Python更为简洁,因为,迭代器需要我们去定义一个类和实现相关的方法,而生成器则只需要在普通的函数中加上一个yield语句即可。另一个更重要的方面,生成器的发明使得Python模仿协同程序的概念得以实现。所谓协同程序,就是可以运行的独立函数调用,函数可以暂停或者挂起,并在需要的时候从程序离开的地方继续或者重新开始。

        对于调用一个普通的Python函数,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数所有语句执行完毕。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。(经典语录啊)

        Python是通过生成器来实现类似于协同程序的概念:生成器可以暂时挂起函数,并保留函数的局部变量等数据,然后再次调用它的时候,从上次暂停的位置继续执行下去。举例说明:

        当函数结束时,一个StopIteration异常就会被抛出。由于Python的for循环会自动调用next()方法和处理StopIteration异常,所以for循环当然也是可以对生成器产生作用的:

 10.7.1 生成器表达式

        举例,列表推导式:

        带条件的列表推导式:

[i for i in range(100) if not(i % 2) and i % 3] 

        字典推导式:

        集合推导式(set comprehension):

        如果将生成器表达式作为函数的参数使用的话,可以直接写推导式,而不必加小括号:

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值