您的位置:首页 > 编程语言 > Python开发

【python初级】010-构造方法,属性和迭代器

2017-06-14 00:00 591 查看
构造方法,属性和迭代器

目录:

-------------构造方法

1、构造方法的基本使用

2、重写一般方法和特殊的构造方法

3、绑定的超类构造方法

4、调用为绑定的超类来构造方法

5、使用super函数

6、成员访问

7、基本的序列和映射规则

8、子类化列表,字典和字符串

-------------属性

-------------迭代器

一:构造方法

在python的心版本中,会有一些特性(比如属性和super函数)不会再老式的类上起作用,为了确保使用的类是新型的,应该把赋值语句metaclass=type放在模块最开始,或者子类化object。

例子:

class NewStyle(boject):

more_code_here

class OldStyle(boject):

more_code_here

===============================================================================

1、构造方法的基本使用

构造方法与其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法。在python中创建一个构造方法很简单,只需要把init方法的名字从简单的init修改为魔法版本__init__即可。

例子:

class foobar:

def __init__(self):

self.somevar = 42

>>> f = foobar()
>>> f.somevar #调用方法的属性
42

解析:

1、构造方法用__init__()来构造。

2、self.somevar是指对象的属性,属性就是somevar,赋予其值为42。

===============================================================================

2、重写一般方法和特殊的构造方法

初始化:表面上将就是还原到原来的样子,将需要运行的程序或内容加载到内存,为一些变量提供空间来赋值。

超类:指的是被继承的类,也就是父类

例子:

>>> class foobar:
def __init__(self, value = 42):
self.somevar = value #初始化这个值

>>> f = foobar('this is a test') #将‘this is a test’赋值给value。在由value赋值给属性somevar
>>> f.somevar
'this is a test'

重写一般方法和特殊的构造方法

每个类都可能有一个或者多个超类,它们从超类那里继承行为方式,如果一个方法在b类的一个实例中被调用,但在b类中没有找到该方法,那么就去它的超类a里面找[/b]

例子:

class a:

def hello(self):

print "hello,i'am a."

class b(a)

>>>A = a()

>>>B = b()
>>>A.hello()hello,i'am a

>>>B.hello()

hello,i'am a

解析:

b类中没有定义hello方法,当要调用hello方法时,先会在自己的类中查找,没有找到,就会向中父类中查找,在父类中找到就会打印a类中的信息。

在子类中增加功能的最基本的方法就是增加方法,但是也可以重写一些超类的方法来自定义继承的行为。b类也能重写这个方法,比如下面的列子中b类的定义被修改了。

class b(a):

def hello(self): #在子类b中定义一个像父类的hello方法,也叫重写方法。

print "hello,i'am b."

解析:

重写是继承机制中的一个重要内容,对于构造方法尤其重要,构造方法用来初始化新创建的对象的状态,大多数子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码,虽然重写的机制对于所有方法来说都是一样的,但是当重写构造方法和普通重写方法更容易遇到特别的问题,比如,如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则,对象可能不会被正确的初始化。

===============================================================================

-------绑定的超类构造方法

例子:绑定的超类构造方法

>>> class Bird:

def __init__(self):

self.hungry = True #定义self.hungry 为真

def eat(self):

if self.hungry: #判断为真输入一下信息

print 'Aaaah...'

self.hungry = False #输入信息之后,将条件转换为假

else:

print 'No, tks' #条件为假的输出

>>> class SongBird(Bird):

def __init__(self):#父类中的__init__()构造方法在子类中被重写,没有初始化self.hungry特性的代码

self.sound = 'Squawk!'

def sing(self):

print self.sound

结果

>>> b = Bird()

>>> b.eat()

Aaaah... #这是条件为真的输出,输出完之后,将条件该为假

>>> b.eat()

No, tks #这是条件为假的输出

>>> sb = SongBird()

>>> sb.sing()#这里是调用子类中的方法

Squawk!

注意:因为SongBird类是Bird的一个子类,他继承了父类的eat方法,但是如果调用eat方法就会产生异常。如下:

>>> sb.eat()

Traceback (most recent call last):

File "<pyshell#59>", line 1, in <module>

sb.eat()

File "<pyshell#51>", line 6, in eat

if self.wa:

AttributeError: SongBird instance has no attribute 'wa'

解析:

报出 的异常是因为SongBird子类没有Bir父类的特性。原因就是在子类SongBird中,构造方法__init__()被重写了,但是这个新的构造方法没有将hungry特性进行初始化,还原成原来的样子,所以会产生这样的一个异常。

为了表面上述例子中情况,子类的构造方法必须调用其超类(父类)的构造方法来确保进行基本的初始化,一般有两种方法来避免这种情况,一种是调用超类构造方法的为绑定版本,另一种就是使用super函数。

===============================================================================

------调用未绑定的超类来构造方法

修改代码:未绑定超类的构造方法

>>> class SongBird(Bird):

def __init__(self):

Bird.__init__(self)

self.sound = 'Squawk!'

def sing(self):

print self.sound

结果

>>> sb = SongBird()

>>> sb.sing()

Squawk!

>>> sb.eat()

Aaaah...

解析:

在子类中 添加了这样一行代码:Bird.__init__(self)。原因就是:在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(这称为绑定方法)。如果直接调用父类的方法,比如:Bird.__init__(self),那么久没有实例会被绑定,这样就能够自由的提供需要的self参数,这样的方法称为未绑定方法。

通过将当前的实例作为self参数提供给未绑定的方法,子类就能够适用男其超类构造方法的所有实现,也就是说属性hungary能被设置。

===============================================================================

-------使用super函数

super函数只能在新式类中使用。当前的类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法,那就可以不用再子类的构造方法中使用超类,儿直接使用super(父类名称,self).__init__。除此之外,__init__

方法能以一个普通的(绑定)方式呗调用。

注意:在python3.0版本中,super函数可以不带任何参数进行调用。

例子:

>>> __metaclass__ = type

>>> class Bird:

def __init__(self):

self.hungry = True

def eat(self):

if self.hungry:

print 'Aaaah...'

self.hungry = False

else:

print 'No, tks'

>>> class SongBird(Bird):

def __init__(self):

super(SongBird, self).__init__()

self.sound = 'Squawk!'

def sing(self):

print self.sound

结果:

>>> sb = SongBird()

>>> sb.sing()

Squawk!

>>> sb.eat()

Aaaah...

>>> sb.eat()

No, tks

为什么说super函数怎么超级

super函数比在超类中直接调用方法更直观,但是这不是他的唯一有点。super函数实际上是 很智能的,因此即使类已经继承多个超类,他也只需要使用一次super函数(但是要确保所有的超类构造方法都使用了super函数)。在一些含糊的情况下使用旧式类会很别扭(比如两个超类共同继承一个超类),单能被新式类和super函数自动处理,内部的具体工作原理不用理解,单必须清楚的知道:在大多数情况下,使用新式类和super函数是比调用超类的未绑定好构造方法(或者其他的方法)更好的选择。

实际上,super函数返回的是一个super对象,这个对象辅助进行方法解析。当对其特性进行访问时,他会查找所有的超类以及超类的超类,知道找到所需要的特性为止,或者引发一个AttributeError异常。

===============================================================================

----------成员访问

略过

===============================================================================

-----------基本的序列和映射规则

序列和映射是对象的集合。

1. __len_(self):返回集合中所含项目的数量。

2. __getitem__(self, key):返回与所给键对应的值。

3. __setitem__(self, key, value):设置和key相关的value。只能为可以修改的对象定义这个方法。

4. __delitem__(self, key):对一部分对象使用del语句时被调用,同时必须删除和元素相关的键。

注意:

1. 对于序列,如果键是负数,从末尾开始计数。

2. 如果键是不合适的类型,会引发TypeError异常。

3. 如果序列是正确的类型,但超出范围,引发IndexError异常

===============================================================================

-------------子类化列表,字典和字符串

class CounterList(list):

def __init__(self, *args):

super(CounterList, self).__init__(*args)

self.counter = 0

def __getitem__(self, index):

self.counter += 1

return super(CounterList, self).__getitem__(index)

cl = CounterList(range(10))

print cl

cl.reverse()

print cl

del cl[3:6]

print cl

print cl.counter

print cl[4] + cl[2]

print cl.counter

# 结果

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

[9, 8, 7, 3, 2, 1, 0]

0

9

2

------------------------静态方法和类成员方法

静态方法和类成员分别被装在staticmethod和classmethod类型的对象中,静态方法的定义没有self参数,且能够被类本身直接调用,类方法在定义的时候需要名为cls的类似于self参数,类成员方法可以直接用类的具体对象对象,但cls参数是自动被绑定到类的。

例子:2.7版本

__metaclass__ = type

class MyClass:

@classmethod

def smeth():

print('This is a static method')

smeth = staticmethod(smeth)#静态方法,没有self

@classmethod

def cmeth(cls):

print('This is a class method of', cls)

cmeth = classmethod(cmeth)#类方法,有类似于self的cls参数

MyClass.smeth()

MyClassNaNeth()

MyClass().meth()

结果

This is a static method

('This is a class method of', <class __main__.MyClass at 0x7f54fc8b81f0>)

('This is a instance method of', <__main__.MyClass instance at 0x7f54fc8d3950>

例子:3.0版本

#!/usr/bin/python

#coding:utf-8

__author__ = 'MXi4oyu'

class People():

#构造方法

def __init__(self):

self.name='张珊'

self.__age=23

def fun1(self):

#共有方法可以在类的外部进行调用

#可以通过 对象名.方法名 来调用

print("共有方法")

def __fun2(self):

#私有方法不能在类的外部进行调用

#可以在类的其他方法中调用私有方法

#可以通过self.方法名() 来调用

print("私有方法")

def funcshow(self):

self.__fun2() //这里就是在类的其他方法中调用私有方法

@classmethod

#类方法要加上@classmethod修饰器,类方法可以通过 类名.方法名进行调用

def fun3(self): #3.0版本中类方法有self参数

print("成员方法")

@staticmethod

#静态方法需要加上@staticmethod修饰器,静态方法不需要加self,

#同样可以通过 类名.方法名 调用

def fun4():

print("静态方法")

if __name__=='__main__':

二:属性

使用属性函数的最简单的方法之一是将它作为一个方法的装饰器来使用。这可以让你将一个类方法转变成一个类属性。当我需要做某些值的合并时,我发现这很有用。其他想要获取它作为方法使用的人,发现在写转换函数时它很有用。让我们来看一个简单的例子:

例子:代码:

class Rectangle:

def __init__(self):

self.width = 0 #给对象的属性width封装值

self.height = 0

def setSize(self, size):

self.width, self.height = size

def getSize(self):

return self.width, self.height

r = Rectangle()

r.width = 10

r.height = 5

print r.getSize()

r.setSize((150,100))

print r.setSize()

print r.width

结果

==================== RESTART: C:/Users/yu/Desktop/init.py ====================

(10, 5)

(150, 100)

150

解析:

print r.getSize()的结果是(10, 5),print r.getSize()输出的结果是(150, 100)。print r.width的结果是150。r.width = 10和r.height = 5是对象调用属性,给其封装一个值。这个时候类中的属性width和height的值分别是10和5,。后面的setsize方法中,重新给属性封装了值。

getSize和setSize方法是一个名为size的假想特性的访问器方法,size是由width和height构成的元组。这些代码没有错,但是有缺陷。当程序员使用者个类时不应该还要考虑他是怎么实现的(封装)。如果有一天要改变类的实现,将size变成一个真正的特性,这样width和height就可以动态算出,那么就要把他们方法哦一个访问器方法中去,并且任何使用这个类的程序都必须重写。客户端代码应该能够用同样的方式对待所有特性。

Python能隐藏访问器方法,让所有特性看起来一样,这些通过访问器定义的特性被称为属性。访问器是一个简单的方法,它能够使用getHeight和setHeight这样的名字来得到或者绑定一些特性。

------公私有属性

在python中,属性分为公有属性和私有属性,公有属性可以在类的外部调用,私有属性不能在类的外部调用。公有属性可以是任意变量,私有属性是以双下划线开头的变量。

下面我们定义一个People类,它有一个公有属性name,和一个私有属性__age。

class People():

def __init(self):

self.name='张珊'

self.__age=24

我们创建一个People类的实例,P1,当我们调用它的私有属性__age时发现有如下错误。

>>> p1.__age
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
p1.__age
AttributeError: 'People' object has no attribute '__age'

这就说明了私有属性不可以在类的外部使用。那么我们要想调用私有属性的值,就可以在类的内部通过定义一个方法来调用。

>>> class People():

def __init__(self):

self.name='jack'

self.__age=23

def showinfo(self):

print(self.__age) #定义了一个方法再调用私有属性

>>> p2=People()

>>> p2.showinfo()

23

-------------------Property函数----值适用于新式类

函数property的基本功能就是把类中的方法当作属性来访问,

通常我们在访问和赋值属性的时候,都是在直接和类(实例的)的__dict__打交道,或者跟数据描述符等在打交道。但是假如我们要规范这些访问和设值方式的话,一种方法是引入复杂的数据描述符机制,另一种恐怕就是轻量级的数据描述符协议函数Property()。它的

标准定义是:property(fget=None,fset=None,fdel=None,doc=None)

前面3个参数都是未绑定的方法,所以它们事实上可以是任意的类成员函数,property()函数前面三个参数分别对应于数据描述符的中的__get__,__set__,__del__方法,所以它们之间会有一个内部的与数据描述符的映射。

数据描述符是指实现了__get__,__set__,__del__方法的类属性(由于Python中,一切皆是对象,所以你不妨把所有的属性也看成是对象)

代码:

class Rectangle:

def __init__(self): #构造方法

self.width = 0 #给对象的属性width封装一个值

self.height = 0

def setSize(self, size): #赋值 _set_

self.width, self.height = size

def getSize(self): #取值--_get_

return self.width, self.height

size = property(getSize, setSize) #把方法当做属性(变量)

r = Rectangle()

r.width = 10

r.height = 5

print r.size #调用的是方法,实质写的是属性。实现了把类中的方法当做属性来调用(访问)

r.size = 150, 100

print r.width

结果

==================== RESTART: C:/Users/yu/Desktop/init.py ====================

(10, 5)

150

例子:代码

class C(object):

def __init__(self):

self._x = None #定义一个私有属性

def getx(self):

print ("get x") #取值,使用_get_

return self._x #在类中的另一个方法中调用私有属性 x。

def setx(self, value):

print ("set x") #赋值,使用_set_

self._x = value #在类中的另一个方法中调用私有属性 将值赋值给属性x。

def delx(self):

print ("del x") #删除,使用_del_

del self._x #在类中的另一个方法中调用私有属性 x,删除这个私有属性

x = property(getx, setx, delx, "I'm the 'x' property.")

如果要使用property函数,首先定义class的时候必须是object的子类。通过property的定义,当获取成员x的值时,就会调用getx函数当给成员x赋值时,就会调用setx函数,当删除x时,就会调用delx函数。使用属性的好处就是因为在调用函数,可以做一些检查。如果没有严格的要求,直接使用实例属性可能更方便。

>>> t=C()
>>> t.x #调用属性,取值
get x
>>> print (t.x)
get x
None #没有取到值,返回None
>>> t.x='en' #调用属性,赋值
set x
>>> print (t.x)
get x
en #赋值,返回所赋的值。
>>> del t.x
del x
>>> print (t.x)
get x

三:迭代器

迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。

1.1 使用迭代器的优点

对于原生支持随机访问的数据结构(如tuple、list),迭代器和经典for循环的索引访问相比并无优势,反而丢失了索引值(可以使用内建函数enumerate()找回这个索引值)。但对于无法随机访问的数据结构(比如set)而言,迭代器是唯一的访问元素的方式。

另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件,或是斐波那契数列等等。

迭代器更大的功劳是提供了一个统一的访问集合的接口,只要定义了__iter__()方法对象,就可以使用迭代器访问。

迭代器有两个基本的方法

· next方法:返回迭代器的下一个元素

· __iter__方法:返回迭代器对象本身

下面用生成斐波那契数列为例子,说明为何用迭代器

代码

def fab(max):

n, a, b = 0, 0, 1

while n < max:

print b

a, b = b, a + b

n = n + 1

直接在函数fab(max)中用print打印会导致函数的可复用性变差,因为fab返回None。其他函数无法获得fab函数返回的数列。

代码2

def fab(max):

L = []

n, a, b = 0, 0, 1

while n < max:

L.append(b)

a, b = b, a + b

n = n + 1

return L

代码2满足了可复用性的需求,但是占用了内存空间,最好不要。

代码3

对比

for i in range(1000): pass[/b]

for i in xrange(1000): pass

前一个返回1000个元素的列表,而后一个在每次迭代中返回一个元素,因此可以使用迭代器来解决复用可占空间的问题

class Fab(object):

def __init__(self, max):

self.max = max

self.n, self.a, self.b = 0, 0, 1

def __iter__(self):

return self

def next(self):

if self.n < self.max:

r = self.b

self.a, self.b = self.b, self.a + self.b

self.n = self.n + 1

return r

raise StopIteration()

执行

>>> for key in Fabs(5):

print key

1

1

2

3

5

Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数  

1.2 使用迭代器

使用内建的工厂函数iter(iterable)可以获取迭代器对象:

>>> lst = range(5)

>>> it = iter(lst)

>>> it

<listiterator object at 0x01A63110

使用next()方法可以访问下一个元素:

>>> it.next()

0

>>> it.next()

1

>>> it.next()

2

python处理迭代器越界是抛出StopIteration异常

>>> it.next()

3

>>> it.next

<method-wrapper 'next' of listiterator object at 0x01A63110>

>>> it.next()

4

>>> it.next()

Traceback (most recent call last):

File "<pyshell#27>", line 1, in <module>

it.next()

StopIteration

了解了StopIteration,可以使用迭代器进行遍历了

lst = range(5)

it = iter(lst)try:

while True:

val = it.next()

print valexcept StopIteration:

pass

结果

>>>

0

1

2

3

4

事实上,因为迭代器如此普遍,python专门为for关键字做了迭代器的语法糖。在for循环中,Python将自动调用工厂函数iter()获得迭代器,自动调用next()获取元素,还完成了检查StopIteration异常的工作。如下

>>> a = (1, 2, 3, 4)>>> for key in a:

print key

1

2

3

4

首先python对关键字in后的对象调用iter函数迭代器,然后调用迭代器的next方法获得元素,直到抛出StopIteration异常。

1.3 定义迭代器

下面一个例子——斐波那契数列

# -*- coding: cp936 -*-class Fabs(object):

def __init__(self,max):

self.max = max

self.n, self.a, self.b = 0, 0, 1 #特别指出:第0项是0,第1项是第一个1.整个数列从1开始

def __iter__(self):

return self

def next(self):

if self.n < self.max:

r = self.b

self.a, self.b = self.b, self.a + self.b

self.n = self.n + 1

return r

raise StopIteration()

print Fabs(5)for key in Fabs(5):

print key

结果

<__main__.Fabs object at 0x01A63090>

1

1

2

3

5

四: 生成器

带有 yield 的函数在 Python 中被称之为 generator(生成器),几个例子说明下(还是用生成斐波那契数列说明)

可以看出代码3远没有代码1简洁,生成器(yield)既可以保持代码1的简洁性,又可以保持代码3的效果

代码4

def fab(max):

n, a, b = 0, 0, 1

while n < max:

yield b

a, b = b, a + b

n = n + 1

执行

>>> for n in fab(5):

print n

1

1

2

3

5

简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable 对象!在for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:

>>> f = fab(3)

>>> f.next()

1

>>> f.next()

1

>>> f.next()

2

>>> f.next()

Traceback (most recent call last):

File "<pyshell#62>", line 1, in <module>

f.next()

StopIteration

return作用

在一个生成器中,如果没有return,则默认执行到函数完毕;如果遇到return,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。例如

>>> s = fab(5)

>>> s.next()

1

>>> s.next()

Traceback (most recent call last):

File "<pyshell#66>", line 1, in <module>

s.next()

StopIteration

代码5 文件读取

def read_file(fpath):

BLOCK_SIZE = 1024

with open(fpath, 'rb') as f:

while True:

block = f.read(BLOCK_SIZE)

if block:

yield block

else:

return

如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。

登录乐搏学院官网http://www.learnbo.com/

或关注我们的官方微博微信,还有更多惊喜哦~



本文出自 “末班车” 博客,请务必保留此出处http://blxueyuan.blog.51cto.com/9673381/1879099
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: