Python基础教程---魔法方法,属性和迭代器(1)

前后都使用双下划线,由这些名字组成的集合所包含的方法称为魔法(特殊)方法,如果对象实现了这些方法中的某一个,那么这个方法会在特殊的情况下被Python调用,而几乎没有直接调用他们的必要

本章内容:

魔法方法(最重要的是__init__方法和一些处理对象访问的方法,这些方法允许你创建自己的序列或者映射)

属性(在以前的版本中通过魔法方法来处理,现在则通过property函数)

迭代器(使用魔法方法__iter__来允许迭代器在for循环中使用)

1.准备工作

为了确保类是新型的,应该把赋值语句__metaclass__=type放在模块的最开始

或者(直接或间接)子类化内建类(类型)object(或其他一些新式类)

class NewStyle(object):

more_code_here

class OldStyle:

    more_code_here

如果没有兼容之前旧版Python的需要,建议将所有类都写为新式类,并使用super函数

Python3.0中没有旧式类,也不需要显示子类化object或者将元素设置为type,所有类都会隐式地成为object子类---如果没有明确超类的话,会直接子类化,否则会间接子类化

2.构造方法

构造方法和其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法

>>> f = FooBar()

>>> f.init()

构造方法可以简化

>>> f = FooBar()

Python中构建一个构造方法,只要把init方法的名字从简单的init修改为__init__

class FooBar:

def __init__(self):

    self.somevar = 42

>>> f = FooBar()

>>> f.somevar

42

还可以有多个参数,且参数可选

__del__析构方法,在对象就要被垃圾回收之前调用,但发生调用的具体时间是不可知的,应尽量避免使用。

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

如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么就会去它的超类A里面找

构造方法用来初始化新创建对象的状态,如果一个类的构造方法被重写,那么就需要调用超类的构造方法

考虑Bird

Class Bird

Def __init__(self):

   Self.hungry = True

def eat(self):

   if self.hungry

      print ‘Aaaah...’

      self.hungry = False

    else :

       print ‘No, thanks’

这个类定义所有的鸟都具有的一些最基本的能力,吃

>>> b = Bird()

>>> b.eat()

Aaaah...

>>> b.eat()

No, thanks

现在考虑子类SongBird,它添加了唱歌的行为

class SongBird(Bird):

def __init__(self):

   self.sound = ‘Squawk!’

def sing(self):

   print self.sound

SongBirdBird类一样使用

>>> sb =SongBird()

>>> sb.sing()

Squawk

SongBirdBird子类,继承了eat方法,但是调用eat方法就会产生一个问题:

SongBird没有hungry属性,因为构造方法被重写,新的构造方法没有任何关于初始化hungry特性的代码,因此,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化,两种方法实现:调用超类构造方法的未绑定版本,或者使用super函数

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

class SongBird(Bird):

def __init__(self):

   Bird.__init__(self)

   self.sound = ‘Squawk!’

def sing(self):

   print self.sound

在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(绑定方法),但是如果直接调用类的方法,那么就没有实例被绑定,这样可以自由提供需要的self参数(成为未绑定方法)

2.3 super函数

Super函数只能在新式类中起作用

当前类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法,那么可以直接使用super(SongBird, self)

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

class Bird

def __init__(self):

   self.hungry = True

def eat(self):

   if self.hungry

      print ‘Aaaah...’

      self.hungry = False

    else :

       print ‘No, thanks’

class SongBird(Bird):

def __init__(self):

   Supe(SongBird, self).__init__()

   self.sound = ‘Squawk!’

def sing(self):

   print self.sound

3.成员访问

魔法方法集合,它可以创建行为类似于序列或映射的对象

规则,是管理某种形式的行为的规则,说明了应该实现何种方法和这些方法应该做什么。

因为Python中的多态是基于对象的行为的(而不是基于祖先,例如他所属的类或超类),在其他语言中对象可能被要求属于某一个类,或者被要求实现某个接口,但Python中只是简单地要求它遵循几个给定的规则,因此成为了一个序列,需要的只是遵循序列的规则

3.1基本的序列和映射规则

序列和映射是对象的集合,为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的则需要使用4

__len__(self):这个方法应该返回集合中所含项目的数量,对于序列就是元素个数;对于映射,则是键-值对数量,如果__len__返回0(并且没有实现重写该行行为的__nonzero__),对象会被当作一个布尔变量中的假值(空的列表,元组,字符串和字典也一样)进行处理

__getitem__(self, key):这个方法返回与所给键对应的值,对于一个序列,键应该是一个0~n-1的整数(或者负数),n是序列长度,对于映射来说,可以使用任何种类的键

__setitem__(self, key, value):这个方法应该按一定的方式存储和key相关的value,该值随后可使用__getitem__来获取,当然只能为可以修改的对象定义这个方法

__delitem__(self, key):这个方法在对一部分对象使用del语句时被调用,同时必须删除和元素相关的键,这个方法也是位可修改的对象定义的(并不是删除全部的对象,而只删除一些需要移除的元素)

对这些方法的附加要求:

对于一个序列来说,如果键是负整数,那么要从末尾开始计数,x[-n]x[len(x)-n]是一样的

如果键是不合适的类型(例如对序列使用字符串作为键),会引发一个TypeError异常

如果序列的索引是正确的类型,但超出了范围,应引发一个IndexError异常

 

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

继承,3个关于序列和映射规则(UserListUserStringUserDict)可以立即使用的实现

如果希望实现一个和内建列表行为相似的序列,可以使用子类list

当子类化一个内建类型--比如list的时候,也就间接地将object子类化了,因此类就自动成为新式类,这就意味着可以使用像super函数这样的特性

带有访问计数的列表

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)

CounterList类严重依赖于它的子类化超类(list)行为,CounterList类没有重写任何的方法(和appendextendindex一样)都能被直接使用,在两个被重写的方法中,super方法被用来调用相应的超类的方法,只在__init__中添加了所需的初始化counter特性的行为,并在__getitem__中更新了counter特性

重写__getitem__并非获取用户访问的万全之策,因为还有其他访问列表内容的途径,比如通过pop方法

>>> c1 = CounterList(range(10))

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

>>> c1.reverse() 

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

>>> del c1[3:6]

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

>>> c1.counter               0

>>> c1[4] + c1[2]           9

>>> c1.counter             2

CounterList在很多方面和列表的作用一样,但它有一个counter特性(被初始化为0),每次列表元素被访问时,它都会自增,所以在执行加法c1[4] + c1[2]后,这个值自增两次,变为2

4.更多魔力

Python参考手册》3.4

5.属性

访问器是一个简单的方法,它能够使用getHeight, setHeight来得到或者重绑定一些特性(可能是类的私有属性)

class Rectangle:

def __init__(self):

    self.width = 0

    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

>>> r.getSize()                 (10, 5)

>>> r.setSize((150, 100))

>>> r.width                     150

getSizesetSize方法一个名为size的假想特性的访问器方法,size是由widthheight构成的元组

Python能隐藏访问器方法,让所有特性看起来一样,这些通过访问器定义的特性称为属性。

在新式类中使用property函数

5.1 property函数

__metaclass__ = type

class Rectangle:

def __init__(self):

    self.width = 0

    self.height = 0

def setSize(self, size):

    self.width, self.height = size

def getSize(self):

    return self.width, self.height

    size = property(getSize, setSize)

Property函数创建了一个属性,其中访问器函数被用作参数(先是取值,然后赋值),这个属性命为size,这样一来就不再需要担心怎么实现,可以用同样的方法处理widthheightsize

>>> r = Rectangle()

>>> r.width = 10

>>> r.height = 5

>>> r.size

(10, 5)

>>> r.size = 150, 100

>>> r.width

150

size特性仍然取决于getSizesetSize中的计算

Property函数可以用0,1,2,3或者4个参数来调用,如果没有参数,产生的属性既不可读也不可写。如果只使用一个参数调用(一个取值方法),产生的属性是只读的第3个参数(可选)是一个用于删除特性的方法(它不要参数),第4个参数(可选)是一个文档字符串

Property的四个参数分别被叫做fget,fset,fdeldoc--,如果想要一个属性是只写的,并且又一个文档字符串,能使用他们作为关键字参数

5.2静态方法和类成员变量

静态方法和类成员方法在创建时分别被装入Staticmethod类型和Classmethod类型的对象中,

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

__metaclass__ = type

class MyClass:

def smeth():

    print ‘This is a static method’

smeth = staticmethod(smeth)

def cmeth(cls):

    print ‘This is a class method of’, cls

cmeth = classmethod(cmeth)

 

如上为手动包装和替换的技术

Python2.4中,为这样的包装方法引入了一个叫做装饰器的新语法(能够对任何可调用的对象进行包装,既能够用于方法也能够用于函数),使用@操作符,在方法(或函数)上方将装饰器列出,从而指定一个或更多的装饰器(多个装饰器子啊应用时的顺序与指定顺序相反)。

__metaclass__ = type

class MyClass:

    @staticmethod

def smeth():

    print ‘This is a static method’

@classmethod

def cmeth(cls):

    print ‘This is a class method of’, cls

定义这些方法以后,可以如下使用

>>> MyClass.smeth()

This is a static method

>>> MyClass.cmeth()

This is a class method of 

静态方法和类成员方法在Python中并不是想了都很重要,主要原因是大部分情况下可以使用函数或者绑定方法代替。

5.3 __getattr__, __setattr__和它的朋友们

拦截(intercept)对象的所有特性访问是可能的,这样可以用旧式类实现属性(因为property方法不能使用),为了在访问特性的时候可以执行代码,必须使用一些魔法方法(旧式类中只需后3个)

__getattribute__(self, name):当特性name被访问时自动被调用(只能在新式类中使用)

__getattr__(self, name):当特性name被访问且对象没有相应的特性时被自动调用

__setattr__(self, name, value):当试图给特性name赋值时会被自动调用

__delattr__(self, name):当试图删除特性name时被自动调用

尽管和使用property函数相比有点复杂(而且某些方面效率更低),但这些特殊方法是很强大的,因为可以对处理很多属性的方法进行再编码

class Rectangle:

def __init__(self):

    self.width = 0

    self.height = 0

def __setattr__(self, name, value):

    If name == ‘size’:

       self.width, self.height = size

    else:

       Self.__dict__[name] = value

def __getattr__(self, name):

    If name == ‘size’:

       return self.width, self.height

    else:

       raise AttributeError

__setattr__方法在所涉及到的特性不是size时也会被调用,因此,这个方法必须把两方面都考虑进去,如果属性是size,那么就像前面那样执行操作,否则就要使用特殊方法__dict__,该特殊方法包含一个字典,字典里面是所有实例的属性,为了避免__setattr__方法被再次调用(会使程序陷入死循环),__dict__方法被用来代替普通的特性赋值操作

__getattr__方法只在普通的特性没有被找到时调用,这就是说如果给定的名字不是size,这个特性不存在,这个方法会引发一个AttributeError异常,如果希望类和hasattr或者是getattr这样的内建函数一起正确地工作,__getattr__方法就很重要,如果使用的是size属性,那么就会使用在前面的实现中找到的表达式

就像死循环陷阱和__setattr__有关系一样,还有一个陷阱和__getattribute__有关系,因为__getattribute__拦截所有特性的访问(在新式类中),也拦截对__dict__的访问,

访问__getattribute__中与self相关的特性时,使用超类的__getattribute__方法(使用super函数)是唯一安全的途径


你可能感兴趣的:(Python,迭代器,对象,class,python,函数)