13ch

初览:

新式类 class MyNewObjectType(object):

旧式类class MyNewObjectType:

我们推荐使用新式类,它的默认父类是object。

实例化一个类只需直接使用(=)

类可以很简单,仅仅作为一个名称空间使用(namespace),就像C里的结构体(struct)或者是Pascal里的记录集(record)

class MyDate():
    pass
>>>mathObj = MyDate()
>>>mathObj.x = 4
>>>mathObj.y = 5
>>>mathObj.x+mathObj.y
9

2.方法

每一个方法的第一个参数都是self

核心笔记:

类名通常由大写字母打头,这是标准惯例,数据值(属性)应该使用名词作为名字,方法应该使用谓词作为名字。

13.2.3常用术语

抽象/实现

1.抽象指对现实世界问题和实体建模,抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。对某种抽象的实现就是对此数据及

相关接口的实现(realization)

2.封装/接口

封装描述了对数据/信息进行隐藏,而仅通过接口操作。

3.合成

用类来组成类

4.派生/继承

父类派生子类,子类继承父类

5.泛化/特化

泛化是子类和父类相同之处,特化是子类和父类不同之处

6.多态

对象之间可以通过他们共同的属性和动作来操作和访问,而不需要考虑具体的子类。

如果说继承是子类使用父类的方法,那么多态则是父类使用子类的方法。

7.自省/反射

自省或反射都是指对象在运行期间获得自身信息的特性。

13.3类

类之中也可以声明函数/类/闭包。

13.4类属性

属性就是属于另一个对象的数据或函数,可以通过句点访问,属性本身也可以拥有属性。

我们可以通过dir(class)或者__dict__属性来查看类的属性(前者是属性的名字列表,后者是字典,包含了属性对应的数据值)

13.4.1类的数据属性

即和类绑定的数据,在JAVA/C++中相当于为变量声明加上了static关键字。

13.4.2方法

绑定(binding):

方法只能在实例化之后调用

13.4.4更多类属性的例子.__name__ 类的名字(字符串)||用于不想引用类而仅仅想使用类的名字的情况

.__doc__ 类的文档(字符串)||不会被子类继承

.__bases__ 类的上一个父类(元组)

.__dict__ 类的属性(列表)|| 1.访问一个类属性会先在当前类的__dict__中寻找,没找到则在上一个父类的__dict__中寻找,采用的是深度优先策略。

2.对子类的的修改不会影响父类的__dict__

.__module__  指示类的定义所在的模块

.__class__ 实例对应的类

13.5实例

__init__ 是实例化以后第一个执行的方法,类似于java中的构造函数。

__new__ 正是用来实例化类的,在__init__之前执行。

__del__ 解构器(destructor),当实例对象所有的引用都被清除后才会执行。(引用计数为0)

我们通过一个例子来了解这三个方法

class C(P):
    def __init__(self):
        print 'initialized'
    def __del__(self):
        P.__del__(self)
        print 'deleted'
>>> c1 = c()
initialized
>>> c2 = c1
>>> c3 = c1
>>> id(c1), id(c2), id(c3)
(11938912, 11938912, 11938912)
>>> del c1
>>> del c2
>>> del c3
deleted

可见del class并不表示调用了__del__, 而仅仅是减少引用次数

核心笔记:
python没有机制跟踪一个类有多少实例被创建,所以我们可以使用静态成员(类属性)来记录。

类属性和实例属性

class Foo(object):
    x = 1.5
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x = 1.7
>>> foo.x
1.7
>>> Foo.x
1.5                >>> del foo.x      (删除的是实例属性)
>>> foo.x
1.5                ---- 实例的实例属性会覆盖同名的类属性,删除这个实例属性后类属性会重见天日。
在另一种情况下,我们通过类的实例改变了类属性而不是覆盖它,
class Foo(object):
    x = {2003: 'poe2'}
>>>foo = Foo()
>>>foo.x[2004] = 'valid path'
>>>foo.x
{2003: 'poe2', 2004: 'valid path'}
>>>Foo.x
{2003: 'poe2', 2004: 'valid path'}
>>>del foo.x  这会引发一个error,因为试图删除的是类属性

核心提示:

即使我们可以通过类的实例改变类属性,我们还是应该通过类名类改变类属性。

13.7绑定和方法调用

绑定:绑定发生在实例化的过程中,方法和实例会进行绑定

核心笔记:

self就是用于在类的实例的方法中引用这个实例自身。

当你调用绑定方法时,self是自动给出的,你不必显示给出。

调用非绑定方法:

调用非绑定方法的一个场景是,当你在派生一个子类的时候,你要覆盖掉父类的方法,这时父类的方法还没有

实例化,所以这是在调用非绑定方法,这时self参数需要给出。

比如:

class NewClass(BaseClass):
    def __init__ (self, nm, ph, em):
        BaseCalss.__init__(self, nm, ph)
        self.email = em
这样就不用复制粘贴父类的大段代码

13.8静态方法和类方法

书上讲不清楚,查到的也将不情况,关于调用:

  • 类方法和静态方法都可以被类和类实例调用,类实例方法仅可以被类实例调用
  • 类方法的隐含调用参数是类,而类实例方法的隐含调用参数是类的实例,静态方法没有隐含调用参数

含义一样但称呼不统一的概念:成员方法,类成员方法,类实例方法,实例方法,即之前所述有绑定(binding)性质的“方法”,在类中简单实用def 创建的方法。

网上有的资料说静态方法不可以调用实例参数,但是给静态方法传入一个实例对象就可以调用相应的参数了啊。(应该是可以但是建议不使用的)

类比方面看到的说法有:

1.静态方法有点像函数工具库的作用,而类成员方法则更接近类似Java面向对象概念中的静态方法。

2.类方法可以被对象调用,也可以被实例调用;传入的都是类对象,主要用于工厂方法,具体的实现就交给子类处理

13.9组合

就是使用类帮助设计类

13.11继承

通过super(thisclass, self).method()调用被覆盖的父类方法,

当你覆盖__init__方法时,父类的__init__不会执行(java中则是总是会执行)

但实际上你常常需要父类的方法来初始化以进一步处理,所以你需要使用super(thisclass,self).__init__()

python中的super的特点是不需要提供父类的名称。

13.11.3 继承标准类型

class RoundFloat(float):
    def __new__ (cls, val):
        return super(RoundFloat, cls).__new__(cls, round(val, 2))
使用内建函数(round)来重写float的__new__

class SortedKeyDict(dict):
    def keys(self):
        return sorted(self.keys())

13.11.4多重继承

1.方法解释顺序(Method Resolution Order)

经典类和新式类在查找方法时有所不同。

给出代码背景

 1 class P1: #(object):
 2     def foo(self):
 3         print "called P1-foo()"
 4 class P2: #(object):
 5     def foo(self):
 6         print "called P2-foo()"
 7     def bar(self):
 8         print "called P2-bar()"
 9 class C1(P1, P2):
10     pass
11 class C2(P1, P2):
12     def bar(self):
13         print "called C2-bar()"
14 class GC(C1, C2):
15     pass

经典类的解释使用了深度优先原则,想要执行特定的方法,需要使用非绑定引用C2.bar(gc)

1 >>> gc = GC()
2 >>> gc.foo() # GC ==> C1 ==> P1
3 called P1-foo()
4 >>> gc.bar() # GC ==> C1 ==> P1 ==> P2
5 called P2-bar()

新式类的解释使用了广度优先原则,想执行特定的方法,需要使用非绑定引用P2.bar(gc)

1 >>> gc = GC()
2 >>> gc.foo() # GC ==> C1 ==> C2 ==> P1
3 called P1-foo()
4 >>> gc.bar() # GC ==> C1 ==> C2
5 called C2-bar()

经典类和新式类的MRO之所以不同是因为新式类都有同一个父类object,如果使用深度优先算法,

则容易搜索到Object里未经覆盖的方法。

新式类里的__mro__属性可以返回查找的顺序

13.12类 实例 和其他对象的内建函数

13.12.1 issubclass(sub, sup)

如果sub是sup的子集则返回True,sup有可能是一个父类元组(tuple)

13.12.2 isinstance(obj1,obj2)

如果obj1 是obj2及其子集的实例时返回True,obj2有可能是一个元组(tuple)

13.12.3 hasattr(), getattr(), setattr(), delattr()

*attr()系列可以在各种对象下使用,不限于类和实例,但是在类和实例中使用频繁故也列出。

hasattr(object, name)函数是boolean型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先做一个检查。

getattr(object, name[, default])会在你试图读取一个不存在的属性时,引发 AttributeError异常,除非给出那个可选的默认参数。

setattr(object, name, value)将要么加入一个新的属性,要么取代一个已存在的属性。

delattr(object, name)函数会从一个对象中删除属性。

13.12.4 dir()

 1 >>> import struct
 2 >>> dir()   # show the names in the module namespace
 3 ['__builtins__', '__doc__', '__name__', 'struct']
 4 >>> dir(struct)   # show the names in the struct module
 5 ['Struct', '__builtins__', '__doc__', '__file__', '__name__',
 6  '__package__', '_clearcache', 'calcsize', 'error', 'pack', 'pack_into',
 7  'unpack', 'unpack_from']
 8 >>> class Shape(object):
 9         def __dir__(self):
10             return ['area', 'perimeter', 'location']
11 >>> s = Shape()
12 >>> dir(s)
13 ['area', 'perimeter', 'location']

13.12.5 super()

see Guido van Rossum’s essay on type and class unification(有一个super()的python实现)

13.12.6 vars()

vars()返回一个字典,它包含了对象储存于其__dict__中的属性和值。如果没有这个属性会引发TypeError异常。

如果vars()没有参数,则会返回包含本地名字空间的属性和值的字典

13.13 用特殊方法定制类

之前提到的__init__和__del__可以认为是构造器和解构器,还有很多可自定义的特殊方法。

用来定制类的特殊方法(书上有一个较长的表格,不在这里列出来。)

这些特殊方法中数值类型的部分可以模拟很多数值操作,实现这些特殊方法将会重载操作符。

关于特殊方法的命名:星号用来表示一个方法的多个版本,其中有r的方法表示 obj OP self,没有r则表示self OP obj,

有i表示 self = self OP boj。

13.13.1 简单定制(RoundFloat2)

继续之前定制的四舍五入浮点数的方法。

 1 class RoundFloatManual(object):
 2     def __init__ (self, val):
 3         assert isinstance(val, float), 
 4             "Value must be a float!"
 5         self.value = round(val, 2)
 6 现在这个方法还不会返回任何东西,我们需要实现 __str__() (与print搭配)
 7 __repr__() (字符串对象表示)
 8 def __str__ (self):
 9     return "%.2f" % self.value
10 另外我们需要完善__repr__,我们可以复制str的代码,但更好的做法是
11 __repr__ = __str__ 这样更利于维护

13.13.2 数值定制(Time60)

我们创建一个简单的操作时间的应用
class Time60(object):
    def __init__ (self, hr, min):
        self.hr = hr
        self.min = min
1.显示
def __str__ (self):
    return '%d:%d' % (self.hr, self.min)
2.加法
def __add__ (self, other):
    return self.__class__ (self.hr + other.hr, self.min + other.min)
在类中我们一般不直接调用类名,而是使用self.__class__完成self的实例化3.原位加法
完成加法操作的同时赋值。
def __iadd__ (self, orther):
    self.hr += other.hr
    self.min += other.min
    return self

13.13.3迭代器

1 from random import choice
2 class RandSeq(object):
3     def __init__ (self, seq):
4         self.data = seq
5     def __iter__ (self):
6         return self
7     def next(self):
8         return choice(self.date)
RandSeq
class AnyIter(object):
    def __init__ (self, data, safe = False):
        self.safe = safe
        self.iter = iter(data)
    def __iter__ (self):
        return self
    def next(self, howmany = 1):
        retval = []
        for eachItem in range(howmany):
            try:
                retval.append(self.iter.next())
            except StopIteration:
                if self.safe:
                    break
                else:
                    raise
            return retval
anylter

正确运行时

>>>a = AnyIter(range(10))
>>>i = iter(a)
>>>for j in range(1,5):
>>>    print j, ':', i.next(j)
1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]

howmany的赋值超过可迭代项时会引发一个StopIteration, 我们可以使用safe关键词来返回越界前得到的所有元素

>>>a = AnyIter(range(10), True)
>>>i = iter(a)
>>>i.next(14)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

13.13.4 多类型制定(NumStr)

现在我们实现一个包含如下功能的类

1.初始化:对数字和字符串实现初始化,默认为0和空字符串

2.加法:定义加法操作符使得NumStr1=[n1::s1],NumStr2=[n2::s2], NumStr1+NumStr2 = [n1+n2::s1+s2]

3.乘法:NumStr1*NumStr2=[n1*n2::s1*s2]

4.False值:当数字和字符串为0和""时,这个实例的值为False

5.比较:修改__cmp__(),让n1>n2且s1>s2时返回1,n1>n2且s1<s2时返回0,两者相等亦返回0

 1 class NumStr(object):
 2     def __init__ (self, num=0, string=''):
 3         self.__num = num
 4         self.__string = string
 5     def __str__ (self):
 6         return '[%d :: %r]' % 
 7                (self.__num, self.__string)
 8     __repr__ = __str__
 9     def __add__ (self, other):
10         if isinstance(other, NumStr):
11             return self.__class__ (self.__num + other.__num,
12                                    self.__string + other.__string)
13         else:
14             raise TypeError, 
15                   'Illegal argument type for built-in operation'
16 
17     def __mul__ (self, num):
18         if isinstance(num, int):
19             return self.__class__ (self.__num *num,
20                                    self.__string *num)
21         else:
22             raise TypeError, 
23                   'Illegal argument type for built-in operation'
24     def __nonzero__ (self):
25         return self.__num or len(self.__string)
26     def __norm_cval(self, cmpres):
27         return cmp(cmpres, 0)
28     def __cmp__ (self, other):
29         return self.__norm_cval(
30             cmp(self.__num, other.__num)) + 
31             self.__norm_cval(
32                 cmp(self.__string, other.__string))
numstr.py

我们使用双下划线(__)来命名属性,这样当导入一个模块时不能直接存取这些数据,而必须通过函数。

我们使用了[%d :: %r] 以调用__repr__方法来指明数据的类型是一个字符串。

(我无法确认的是,书中暗示cmp并不总是返回1和-1,但是和0的比较是返回1和-1的)

13.14私有化

默认情况下,属性在Python中都是公开的,大多数面对对象语言提供类“访问控制符”来限定成员函数的访问。

1.双下划线(__)

当属性或方法以双下划线开头命名时(self.__num)访问就变成了self._NumStr_num,这样可以避免覆盖父类中同名的属性/方法。

2.单下划线(_)

防止模块的属性用"from module import *"来加载

13.15授权*

包装(wrapping)就是修改一个对象的功能,一般用于“wrapping a type”(包装类型),因为Python 2.2之前基本类型不是类(无法被继承),

对于类,我们使用继承(派生)来修改一个类。

实现授权的方法是在__init__()中保存一个被包装对象的副本self.__data,并覆盖__getattr__(self, attr),使其返回 getattr(self.__data,attr)

当一个属性不是实例属性并且也无法在类的树形结构中找到时,就会调用__getattr__()来寻找。

书上给出了三个例子。

 13.16新式类的高级特性

工厂函数:int(),long(),float(),complex(),str(),unicode(),list(),tuple(),type(),

basestring(),dict(),bool(),set(), frozenset(),object(),classmethod(),staticmethod(),super(),property(),file()

新的类型判断方式:

if isinstance(obj,int)

if isinstance(obj,(int, long))

if type(obj) is int   [is是更为严格的匹配,而instance中的obj只要是相关实例和子类都会返回True]

13.16.2 __slots__类属性

访问实例inst的属性foo 可以使用:inst.foo,也可以使用:inst.__dict__['foo']

因为__dict__包含所有的实例属性,所以当你有一个属性数量很少但实例很多的类时,会消耗很多内存。

这时我们可以使用__slot__属性来替代__dict__。

 1 >>> class SlottedClass(object):
 2     __slots__ = ("foo","bar")
 3  
 4      
 5 >>> c = SlottedClass()
 6 >>> c.foo = 42
 7 >>> c.xxx = "do not think so"
 8  
 9 Traceback (most recent call last):
10   File "<pyshell#52>", line 1, in <module>
11     c.xxx = "do not think so"
12 AttributeError: 'SlottedClass' object has no attribute 'xxx'
View Code

这种特性的主要目的是节约内存,其副作用是某种类型的“安全”,它能防止用户随心所欲的动态增加实例属性。

13.16.3 __getattribute__()特殊方法Python类有一个名为__getattr__()的特殊方法,只有当属性不能在实例的__dict__或它的类,或者祖先类中找到时,才被调用。

__getattribute__()的应用则更为广泛,只要访问属性就会被调用。

如果类中同时定义了__getattr__(),__getattribute__(),那么除非后者中调用了__getattr__()或者后者raise AttributeError异常,否则前者不会被调用。

为了避免死循环,你应该return object.__getattribute__(self,obj)

13.16.4描述符(descriptor)

描述符可以为对象属性提供强大的API,当需要访问属性时,可以根据情况使用描述符(如果有)或者采用常规方式(句点属性标识符)来访问它们。

1.__get__(), __set__(), __delete__()特殊方法

严格来说,描述符实际上可以是任何新式类,只要这种类实现了三个特殊方法__get__(), __set__(), __delete__(),中的一个,这三个特殊方法充当描述符协议的作用。

__get__()可用于得到一个属性的值,__set__()用于为一个属性进行赋值,在采用del语句(或其他使引用计数减少的方法)明确删除掉某个属性时会调用__delete__()方法。三者中后者很少被实现。

方法描述符/非数据描述符: 没实现__set__()方法。

数据描述符:同时覆盖了__get__(), __set__()方法。

2.__getattribute__()特殊方法

通过__getattribute__()寻找一个代理(或类属性),并通过这个代理(或类属性)访问属性。【It is the one
that  finds  a  class  attribute  or  an  agent  to  call  on  your  behalf  to  access  an
attribute】

给定一个类X和其实例x,x.foo转换为:type(x).__dict__['foo'].__get__(x, type(x))

X.foo转换为:X.__dict__['foo'].__get__(None, X)

---恢复内容结束---

初览:

新式类 class MyNewObjectType(object):

旧式类class MyNewObjectType:

我们推荐使用新式类,它的默认父类是object。

实例化一个类只需直接使用(=)

类可以很简单,仅仅作为一个名称空间使用(namespace),就像C里的结构体(struct)或者是Pascal里的记录集(record)

class MyDate():
    pass
>>>mathObj = MyDate()
>>>mathObj.x = 4
>>>mathObj.y = 5
>>>mathObj.x+mathObj.y
9

2.方法

每一个方法的第一个参数都是self

核心笔记:

类名通常由大写字母打头,这是标准惯例,数据值(属性)应该使用名词作为名字,方法应该使用谓词作为名字。

13.2.3常用术语

抽象/实现

1.抽象指对现实世界问题和实体建模,抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。对某种抽象的实现就是对此数据及

相关接口的实现(realization)

2.封装/接口

封装描述了对数据/信息进行隐藏,而仅通过接口操作。

3.合成

用类来组成类

4.派生/继承

父类派生子类,子类继承父类

5.泛化/特化

泛化是子类和父类相同之处,特化是子类和父类不同之处

6.多态

对象之间可以通过他们共同的属性和动作来操作和访问,而不需要考虑具体的子类。

如果说继承是子类使用父类的方法,那么多态则是父类使用子类的方法。

7.自省/反射

自省或反射都是指对象在运行期间获得自身信息的特性。

13.3类

类之中也可以声明函数/类/闭包。

13.4类属性

属性就是属于另一个对象的数据或函数,可以通过句点访问,属性本身也可以拥有属性。

我们可以通过dir(class)或者__dict__属性来查看类的属性(前者是属性的名字列表,后者是字典,包含了属性对应的数据值)

13.4.1类的数据属性

即和类绑定的数据,在JAVA/C++中相当于为变量声明加上了static关键字。

13.4.2方法

绑定(binding):

方法只能在实例化之后调用

13.4.4更多类属性的例子.__name__ 类的名字(字符串)||用于不想引用类而仅仅想使用类的名字的情况

.__doc__ 类的文档(字符串)||不会被子类继承

.__bases__ 类的上一个父类(元组)

.__dict__ 类的属性(列表)|| 1.访问一个类属性会先在当前类的__dict__中寻找,没找到则在上一个父类的__dict__中寻找,采用的是深度优先策略。

2.对子类的的修改不会影响父类的__dict__

.__module__  指示类的定义所在的模块

.__class__ 实例对应的类

13.5实例

__init__ 是实例化以后第一个执行的方法,类似于java中的构造函数。

__new__ 正是用来实例化类的,在__init__之前执行。

__del__ 解构器(destructor),当实例对象所有的引用都被清除后才会执行。(引用计数为0)

我们通过一个例子来了解这三个方法

class C(P):
    def __init__(self):
        print 'initialized'
    def __del__(self):
        P.__del__(self)
        print 'deleted'
>>> c1 = c()
initialized
>>> c2 = c1
>>> c3 = c1
>>> id(c1), id(c2), id(c3)
(11938912, 11938912, 11938912)
>>> del c1
>>> del c2
>>> del c3
deleted

可见del class并不表示调用了__del__, 而仅仅是减少引用次数

核心笔记:
python没有机制跟踪一个类有多少实例被创建,所以我们可以使用静态成员(类属性)来记录。

类属性和实例属性

class Foo(object):
    x = 1.5
>>> foo = Foo()
>>> foo.x
1.5
>>> foo.x = 1.7
>>> foo.x
1.7
>>> Foo.x
1.5                >>> del foo.x      (删除的是实例属性)
>>> foo.x
1.5                ---- 实例的实例属性会覆盖同名的类属性,删除这个实例属性后类属性会重见天日。
在另一种情况下,我们通过类的实例改变了类属性而不是覆盖它,
class Foo(object):
    x = {2003: 'poe2'}
>>>foo = Foo()
>>>foo.x[2004] = 'valid path'
>>>foo.x
{2003: 'poe2', 2004: 'valid path'}
>>>Foo.x
{2003: 'poe2', 2004: 'valid path'}
>>>del foo.x  这会引发一个error,因为试图删除的是类属性

核心提示:

即使我们可以通过类的实例改变类属性,我们还是应该通过类名类改变类属性。

13.7绑定和方法调用

绑定:绑定发生在实例化的过程中,方法和实例会进行绑定

核心笔记:

self就是用于在类的实例的方法中引用这个实例自身。

当你调用绑定方法时,self是自动给出的,你不必显示给出。

调用非绑定方法:

调用非绑定方法的一个场景是,当你在派生一个子类的时候,你要覆盖掉父类的方法,这时父类的方法还没有

实例化,所以这是在调用非绑定方法,这时self参数需要给出。

比如:

class NewClass(BaseClass):
    def __init__ (self, nm, ph, em):
        BaseCalss.__init__(self, nm, ph)
        self.email = em
这样就不用复制粘贴父类的大段代码

13.8静态方法和类方法

书上讲不清楚,查到的也将不情况,关于调用:

  • 类方法和静态方法都可以被类和类实例调用,类实例方法仅可以被类实例调用
  • 类方法的隐含调用参数是类,而类实例方法的隐含调用参数是类的实例,静态方法没有隐含调用参数

含义一样但称呼不统一的概念:成员方法,类成员方法,类实例方法,实例方法,即之前所述有绑定(binding)性质的“方法”,在类中简单实用def 创建的方法。

网上有的资料说静态方法不可以调用实例参数,但是给静态方法传入一个实例对象就可以调用相应的参数了啊。(应该是可以但是建议不使用的)

类比方面看到的说法有:

1.静态方法有点像函数工具库的作用,而类成员方法则更接近类似Java面向对象概念中的静态方法。

2.类方法可以被对象调用,也可以被实例调用;传入的都是类对象,主要用于工厂方法,具体的实现就交给子类处理

13.9组合

就是使用类帮助设计类

13.11继承

通过super(thisclass, self).method()调用被覆盖的父类方法,

当你覆盖__init__方法时,父类的__init__不会执行(java中则是总是会执行)

但实际上你常常需要父类的方法来初始化以进一步处理,所以你需要使用super(thisclass,self).__init__()

python中的super的特点是不需要提供父类的名称。

13.11.3 继承标准类型

class RoundFloat(float):
    def __new__ (cls, val):
        return super(RoundFloat, cls).__new__(cls, round(val, 2))
使用内建函数(round)来重写float的__new__

class SortedKeyDict(dict):
    def keys(self):
        return sorted(self.keys())

13.11.4多重继承

1.方法解释顺序(Method Resolution Order)

经典类和新式类在查找方法时有所不同。

给出代码背景

 1 class P1: #(object):
 2     def foo(self):
 3         print "called P1-foo()"
 4 class P2: #(object):
 5     def foo(self):
 6         print "called P2-foo()"
 7     def bar(self):
 8         print "called P2-bar()"
 9 class C1(P1, P2):
10     pass
11 class C2(P1, P2):
12     def bar(self):
13         print "called C2-bar()"
14 class GC(C1, C2):
15     pass

经典类的解释使用了深度优先原则,想要执行特定的方法,需要使用非绑定引用C2.bar(gc)

1 >>> gc = GC()
2 >>> gc.foo() # GC ==> C1 ==> P1
3 called P1-foo()
4 >>> gc.bar() # GC ==> C1 ==> P1 ==> P2
5 called P2-bar()

新式类的解释使用了广度优先原则,想执行特定的方法,需要使用非绑定引用P2.bar(gc)

1 >>> gc = GC()
2 >>> gc.foo() # GC ==> C1 ==> C2 ==> P1
3 called P1-foo()
4 >>> gc.bar() # GC ==> C1 ==> C2
5 called C2-bar()

经典类和新式类的MRO之所以不同是因为新式类都有同一个父类object,如果使用深度优先算法,

则容易搜索到Object里未经覆盖的方法。

新式类里的__mro__属性可以返回查找的顺序

13.12类 实例 和其他对象的内建函数

13.12.1 issubclass(sub, sup)

如果sub是sup的子集则返回True,sup有可能是一个父类元组(tuple)

13.12.2 isinstance(obj1,obj2)

如果obj1 是obj2及其子集的实例时返回True,obj2有可能是一个元组(tuple)

13.12.3 hasattr(), getattr(), setattr(), delattr()

*attr()系列可以在各种对象下使用,不限于类和实例,但是在类和实例中使用频繁故也列出。

hasattr(object, name)函数是boolean型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先做一个检查。

getattr(object, name[, default])会在你试图读取一个不存在的属性时,引发 AttributeError异常,除非给出那个可选的默认参数。

setattr(object, name, value)将要么加入一个新的属性,要么取代一个已存在的属性。

delattr(object, name)函数会从一个对象中删除属性。

13.12.4 dir()

 1 >>> import struct
 2 >>> dir()   # show the names in the module namespace
 3 ['__builtins__', '__doc__', '__name__', 'struct']
 4 >>> dir(struct)   # show the names in the struct module
 5 ['Struct', '__builtins__', '__doc__', '__file__', '__name__',
 6  '__package__', '_clearcache', 'calcsize', 'error', 'pack', 'pack_into',
 7  'unpack', 'unpack_from']
 8 >>> class Shape(object):
 9         def __dir__(self):
10             return ['area', 'perimeter', 'location']
11 >>> s = Shape()
12 >>> dir(s)
13 ['area', 'perimeter', 'location']

13.12.5 super()

see Guido van Rossum’s essay on type and class unification(有一个super()的python实现)

13.12.6 vars()

vars()返回一个字典,它包含了对象储存于其__dict__中的属性和值。如果没有这个属性会引发TypeError异常。

如果vars()没有参数,则会返回包含本地名字空间的属性和值的字典

13.13 用特殊方法定制类

之前提到的__init__和__del__可以认为是构造器和解构器,还有很多可自定义的特殊方法。

用来定制类的特殊方法(书上有一个较长的表格,不在这里列出来。)

这些特殊方法中数值类型的部分可以模拟很多数值操作,实现这些特殊方法将会重载操作符。

关于特殊方法的命名:星号用来表示一个方法的多个版本,其中有r的方法表示 obj OP self,没有r则表示self OP obj,

有i表示 self = self OP boj。

13.13.1 简单定制(RoundFloat2)

继续之前定制的四舍五入浮点数的方法。

 1 class RoundFloatManual(object):
 2     def __init__ (self, val):
 3         assert isinstance(val, float), 
 4             "Value must be a float!"
 5         self.value = round(val, 2)
 6 现在这个方法还不会返回任何东西,我们需要实现 __str__() (与print搭配)
 7 __repr__() (字符串对象表示)
 8 def __str__ (self):
 9     return "%.2f" % self.value
10 另外我们需要完善__repr__,我们可以复制str的代码,但更好的做法是
11 __repr__ = __str__ 这样更利于维护

13.13.2 数值定制(Time60)

我们创建一个简单的操作时间的应用
class Time60(object):
    def __init__ (self, hr, min):
        self.hr = hr
        self.min = min
1.显示
def __str__ (self):
    return '%d:%d' % (self.hr, self.min)
2.加法
def __add__ (self, other):
    return self.__class__ (self.hr + other.hr, self.min + other.min)
在类中我们一般不直接调用类名,而是使用self.__class__完成self的实例化3.原位加法
完成加法操作的同时赋值。
def __iadd__ (self, orther):
    self.hr += other.hr
    self.min += other.min
    return self

13.13.3迭代器

1 from random import choice
2 class RandSeq(object):
3     def __init__ (self, seq):
4         self.data = seq
5     def __iter__ (self):
6         return self
7     def next(self):
8         return choice(self.date)
RandSeq
class AnyIter(object):
    def __init__ (self, data, safe = False):
        self.safe = safe
        self.iter = iter(data)
    def __iter__ (self):
        return self
    def next(self, howmany = 1):
        retval = []
        for eachItem in range(howmany):
            try:
                retval.append(self.iter.next())
            except StopIteration:
                if self.safe:
                    break
                else:
                    raise
            return retval
anylter

正确运行时

>>>a = AnyIter(range(10))
>>>i = iter(a)
>>>for j in range(1,5):
>>>    print j, ':', i.next(j)
1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]

howmany的赋值超过可迭代项时会引发一个StopIteration, 我们可以使用safe关键词来返回越界前得到的所有元素

>>>a = AnyIter(range(10), True)
>>>i = iter(a)
>>>i.next(14)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

13.13.4 多类型制定(NumStr)

现在我们实现一个包含如下功能的类

1.初始化:对数字和字符串实现初始化,默认为0和空字符串

2.加法:定义加法操作符使得NumStr1=[n1::s1],NumStr2=[n2::s2], NumStr1+NumStr2 = [n1+n2::s1+s2]

3.乘法:NumStr1*NumStr2=[n1*n2::s1*s2]

4.False值:当数字和字符串为0和""时,这个实例的值为False

5.比较:修改__cmp__(),让n1>n2且s1>s2时返回1,n1>n2且s1<s2时返回0,两者相等亦返回0

 1 class NumStr(object):
 2     def __init__ (self, num=0, string=''):
 3         self.__num = num
 4         self.__string = string
 5     def __str__ (self):
 6         return '[%d :: %r]' % 
 7                (self.__num, self.__string)
 8     __repr__ = __str__
 9     def __add__ (self, other):
10         if isinstance(other, NumStr):
11             return self.__class__ (self.__num + other.__num,
12                                    self.__string + other.__string)
13         else:
14             raise TypeError, 
15                   'Illegal argument type for built-in operation'
16 
17     def __mul__ (self, num):
18         if isinstance(num, int):
19             return self.__class__ (self.__num *num,
20                                    self.__string *num)
21         else:
22             raise TypeError, 
23                   'Illegal argument type for built-in operation'
24     def __nonzero__ (self):
25         return self.__num or len(self.__string)
26     def __norm_cval(self, cmpres):
27         return cmp(cmpres, 0)
28     def __cmp__ (self, other):
29         return self.__norm_cval(
30             cmp(self.__num, other.__num)) + 
31             self.__norm_cval(
32                 cmp(self.__string, other.__string))
numstr.py

我们使用双下划线(__)来命名属性,这样当导入一个模块时不能直接存取这些数据,而必须通过函数。

我们使用了[%d :: %r] 以调用__repr__方法来指明数据的类型是一个字符串。

(我无法确认的是,书中暗示cmp并不总是返回1和-1,但是和0的比较是返回1和-1的)

13.14私有化

默认情况下,属性在Python中都是公开的,大多数面对对象语言提供类“访问控制符”来限定成员函数的访问。

1.双下划线(__)

当属性或方法以双下划线开头命名时(self.__num)访问就变成了self._NumStr_num,这样可以避免覆盖父类中同名的属性/方法。

2.单下划线(_)

防止模块的属性用"from module import *"来加载

13.15授权*

包装(wrapping)就是修改一个对象的功能,一般用于“wrapping a type”(包装类型),因为Python 2.2之前基本类型不是类(无法被继承),

对于类,我们使用继承(派生)来修改一个类。

实现授权的方法是在__init__()中保存一个被包装对象的副本self.__data,并覆盖__getattr__(self, attr),使其返回 getattr(self.__data,attr)

当一个属性不是实例属性并且也无法在类的树形结构中找到时,就会调用__getattr__()来寻找。

书上给出了三个例子。

 13.16新式类的高级特性

工厂函数:int(),long(),float(),complex(),str(),unicode(),list(),tuple(),type(),

basestring(),dict(),bool(),set(), frozenset(),object(),classmethod(),staticmethod(),super(),property(),file()

新的类型判断方式:

if isinstance(obj,int)

if isinstance(obj,(int, long))

if type(obj) is int   [is是更为严格的匹配,而instance中的obj只要是相关实例和子类都会返回True]

13.16.2 __slots__类属性

访问实例inst的属性foo 可以使用:inst.foo,也可以使用:inst.__dict__['foo']

因为__dict__包含所有的实例属性,所以当你有一个属性数量很少但实例很多的类时,会消耗很多内存。

这时我们可以使用__slot__属性来替代__dict__。

 1 >>> class SlottedClass(object):
 2     __slots__ = ("foo","bar")
 3  
 4      
 5 >>> c = SlottedClass()
 6 >>> c.foo = 42
 7 >>> c.xxx = "do not think so"
 8  
 9 Traceback (most recent call last):
10   File "<pyshell#52>", line 1, in <module>
11     c.xxx = "do not think so"
12 AttributeError: 'SlottedClass' object has no attribute 'xxx'
View Code

这种特性的主要目的是节约内存,其副作用是某种类型的“安全”,它能防止用户随心所欲的动态增加实例属性。

13.16.3 __getattribute__()特殊方法Python类有一个名为__getattr__()的特殊方法,只有当属性不能在实例的__dict__或它的类,或者祖先类中找到时,才被调用。

__getattribute__()的应用则更为广泛,只要访问属性就会被调用。

如果类中同时定义了__getattr__(),__getattribute__(),那么除非后者中调用了__getattr__()或者后者raise AttributeError异常,否则前者不会被调用。

为了避免死循环,你应该return object.__getattribute__(self,obj)

13.16.4描述符(descriptor)

描述符可以为对象属性提供强大的API,当需要访问属性时,可以根据情况使用描述符(如果有)或者采用常规方式(句点属性标识符)来访问它们。

1.__get__(), __set__(), __delete__()特殊方法

严格来说,描述符实际上可以是任何新式类,只要这种类实现了三个特殊方法__get__(), __set__(), __delete__(),中的一个,这三个特殊方法充当描述符协议的作用。

__get__()可用于得到一个属性的值,__set__()用于为一个属性进行赋值,在采用del语句(或其他使引用计数减少的方法)明确删除掉某个属性时会调用__delete__()方法。三者中后者很少被实现。

方法描述符/非数据描述符: 没实现__set__()方法。

数据描述符:同时覆盖了__get__(), __set__()方法。

2.__getattribute__()特殊方法

通过__getattribute__()寻找一个代理(或类属性),并通过这个代理(或类属性)访问属性。【It is the one
that  finds  a  class  attribute  or  an  agent  to  call  on  your  behalf  to  access  an
attribute】

给定一个类X和其实例x,x.foo转换为:type(x).__dict__['foo'].__get__(x, type(x))

X.foo转换为:X.__dict__['foo'].__get__(None, X)

3.优先级别

类属性->数据描述符->实例属性->非数据描述符->默认为__getattr__()

描述符是一个类属性,所以所有的类属性都具有最高的优先级。实例属性默认是局部对象__dict__的值。

非数据描述符的目的仅仅是当实例属性的值不存在的时候提供一个值,这与以下情况类似:当在一个实例的__dict__中找不到某个属性时,才会调用__getattr__() 。

如果没有找到非数据描述符,那么__getattribute__()会抛出一个AttributeError异常,接着会调用__getattr__()作为最后一步操作,否则AttributeError会返回给用户。

4.描述符举例

 以下是一个简单的例子,用一个描述符禁止对属性进行访问或赋值请求,事实上,以下所有示例都忽视了全部请求,但我们可以通过示例逐渐掌握描述符的使用:

class DevNull1(object):

  def __get__ (self, obj, type=None):

    pass

  def __set__(self, obj, val):
    pass

我们建立了一个类,这个类使用了这个描述符,给它赋值并显示其值:

>>>class C1(object):

    foo = DevNull1()

>>>c1 = C1()

>>>c1.foo = 'bar'

>>>print c1.foo

>>>None

进一步的在描述符中加上一些输出语句

class DevNull2(object):

  def __get__ (self, obj, type=None):
    print 'Accessing attribute... ignoring'

  def __set__(self, obj, val):
    print 'Attempt to assign %r... ignoring' % (val)

现在来看修改后的结果

>>>class C2(object):

    foo = DevNull2()

>>>c2 = C2()

>>>c2.foo = 'bar'

Attempt to assign 'bar'... ignoring

>>>x = c2.foo

Accessing attribute... ignoring

>>>print 'c2.foo contains:', x

c2.foo contains:None

最后,我们在描述符所在的类中添加一个占位符(placeholder),占位符包含关于这个描述符的有用信息:

class DevNull3(object):
  def __init__ (self, name=None):

    self.name = name

  def __get__ (self, obj, type=None):

    print 'Accessing [%s]... ignoring' % (self.name)

  def __set__ (self, obj, val):
    print 'Assigning %r to [%s]... ignoring' % (val, self.name)

下面的输出结果表明优先级的重要性,一个完整的数据描述符比实例的属性具有更高的优先级:

>>>class C3(object):

  foo = DevNull3('foo')

>>> c3 = C3()

>>> c3.foo = 'bar'

Assigning 'bar' to [foo]... ignoring

>>>x = c3.foo

Accessing [foo]...ignoring

>>>print 'c3.foo contains:', x

c3.foo contains: None

>>>c3.__dict__['foo'] = 'bar'

>>>x = c3.foo

Accessing [foo]... ignoring

>>>print 'c3.foo contains:', x

c3.foo contains: None

>>>print 'c3.__dict__['foo'] contains: %r' %(c3.__dict__['foo'])

c3.__dict__['foo'] contains: 'bar'

我们为实例属性c3.foo赋值为一个字符串'bar'。但由于数据描述符比实例属性的优先级高,所以'bar'被覆盖或隐藏了。

同样地实例属性也将隐藏非数据描述符。

>>> class FooFoo(object):

    def foo(self):

      print 'Very important foo() method.'

>>> bar = FooFoo()

>>> bar.foo()

Very important foo() method

>>>bar.foo = 'It is no longer here.'

>>>bar.foo

'It is no longer here.'

>>>del bar.foo

>>>bar.foo()

Very important foo() method.

这是一个直白的示例。我们将foo作为一个函数调用,然后又用字符串来访问,我们当然也可以使用另一个函数覆盖

>>>def BarBar():

    print 'foo() hidden by BarBar()'

>>>bar.foo = BarBar

>>>bar.foo()

foo() hidden by BarBar()

>>>del bar.foo

>>>bar.foo()

Very important foo() method

需要强调的是,函数是非数据描述符。

接下来这个示例是利用描述符在一个文件系统上保存属性的内容

 1 import os
 2 import pickle
 3  
 4 class FileDescr(object):
 5     saved = []   #用以储存访问的文件
 6     def __init__(self, name = None):
 7         self.name = name
 8     def __get__(self, obj, typ = None): 
 9         if self.name not in FileDescr.saved:  #确保用户已经赋值
10             raise AttributeError,"%r used before assignment" % self.name
11         try:
12             f = open(self.name,"r")
13             val = pickle.load(f)
14             f.close()
15             return val
16         except(pickle.InpicklingError, IOError,
17                EOFError,AttributeError,
18                ImportError,IndexError),e:
19             raise AttributeError,"could not read %r" % (self.name)
20     def __set__(self, obj, val):   #将属性保存到文件中
21         f = open(self.name, "w")
22         try:
23             pickle.dump(val, f)
24             FileDescr.saved.append(self.name)
25         except (TypeError,pickle.PicklingError),e:
26             raise AttributeError,"could not pickle %r" % self.name
27         finally:
28             f.close()
29     def __delete__(self, obj):
30         try:
31             os.unlink(self.name)
32             FileDescr.saved.remove(self.name)
33         except (OSError, ValueError),e:
34             pass

接下来是使用这个类的示例

>>> class MyFileVarClass(object):
    foo = FileDescr("foo")
    bar = FileDescr("bar")
 
     
>>> fvc = MyFileVarClass()
>>> print fvc.foo
 
Traceback (most recent call last):
  File "<pyshell#121>", line 1, in <module>
    print fvc.foo
  File "C:Python27hello.py", line 10, in __get__
    raise AttributeError,"%r used before assignment" % self.name
AttributeError: 'foo' used before assignment
>>> fvc.foo = 42
>>> fvc.bar = "leanna"
>>> print fvc.foo,fvc.bar
42 leanna
>>> del fvc.foo
>>> print fvc.foo
 
Traceback (most recent call last):
  File "<pyshell#126>", line 1, in <module>
    print fvc.foo
  File "C:Python27hello.py", line 10, in __get__
    raise AttributeError,"%r used before assignment" % self.name
AttributeError: 'foo' used before assignment
>>> print fvc.bar
leanna

6.属性和property()内建函数

属性是一种特殊的描述符,“一般”情况下,当使用(.)访问实例属性时,就是在修改这个实例的__dict__属性。

表面上来看使用property()访问和使用(.)访问没什么不同,但实际上使用property()可以避免使用特殊方法__getattr__, __setattr__, __getattribute__。

property(fget = None, fset = None, fdel = None, doc = None)

其一般用法是将其写在一个类定义中,property()接受一些传进来的方法作为参数。

下面的例子在类中建立一个只读的整型属性,用逐位异或操作符将它隐藏起来

>>> class ProtectAndHideX(object):
    def __init__(self, x):
        assert isinstance(x, int),"x must be an integer!"
        self.__x = ~x
    def get_x(self):
        return ~self.__x
    x = property(get_x)
运行会发现只能保存第一次给出的值,改进后。
class HideX(object):
  def __init__(self, x):
    self.x = x
  def get_x(self):
    return ~self.__x
  def set_x(self,x):
    assert isinstance(x, int), '"x" must be an integer!'
    aself.__x = ~x
  x = property(get_x, set_x)
输出结果
>>> inst = HideX(20)
>>> print inst.x
-21
>>>inst.x = 30
>>>print inst.x
30
属性成功保存到x中并显示出来,是因为在调用构造器给x赋值前,在getter中已经将~x赋值给self.x
你还可以给自己写的属性添加一个文档字符串,参见以下例子:
from math import pi
def get_pi(dummy):
  return pi
class PI(object):
  pi = property(get_pi, doc='Constant "pi"')
我们在property中特意使用了一个函数而不是方法。注意在调用函数时self作为第一个参数被传入,所以我们必须添加一个伪变量把self丢弃。以下是本例的输出:
>>> inst = PI()
>>> inst.pi
3.1415926535897931
>>> print PI.pi__doc__
Constant"pi"
我们不必知道property()是如何将我们写的函数fgetfset映射为描述符__get__(), __set__()的,只需要把自己写的函数(或方法)调入property()中就可以了。
在你写的定义类中创建描述符方法的一个弊端是会弄乱类的名称空间,解决方法是:
1.借用一个函数的名称空间 2.编写一个用作内部函数的方法作为property()的(关键字)参数 3.(用locals())返回一个包含所有(函数/方法)名和对应对象的字典 4.把字典传入property() 5.去掉临时的名称空间
根据解决方法我们来修改这个类:
class HideX(object):
    def __init__ (self, x):
        self.x = x
    @property
    def x():
        def fget(self):
            return ~self.__x
        def fset(self, x):
            assert isinstance(x, int), '"x" must be an integer!'
            self.__x = ~x
    return locals()

我们的代码工作如初,但是显然有两点不同:1.类的名称空间更为简洁,只有['__doc__','__init__','__module__','x'] 2.用户不能再通过inst.set_x(40)给属性赋值,必须使用init.x = 40 。

13.16.5 元类和__metaclass__

1.元类(Metaclasses)是什么

 元类可以被视为是一个“类的类”,对于传统的类来说,它们的元类是types.ClassType,当某个类调用type()函数时,就可以看到它是谁的实例:

class C(object):

  pass

class CC:

  pass

>>>type(C)

<type 'type'>

>>>type(CC)

<type 'classobj'>

>>>import types

>>>type(CC) is types.ClassType

True

2.什么时候使用元类

在执行类的定义时,解释器必须知道这个类的正确的元类,解释器会先寻找类属性__metaclass__, 如果此属性存在,就将这个属性赋值给此类作为它的元类。如果此属性没有定义,它会向上查找父类中的__metaclass__。所有新风格的类如果没有父类,则会从对象或类型中继承(type(object)当然是类型)。如果还没有发现__metaclass__属性,解释器会检查名字为__metaclass__的全局变量;如果不存在这样的全局变量,那么这个类就是一个传统类,并用types.ClassType作为此类的元类。(如果你在定义类一个传统类之后设置__metaclass__ = type,那么其实你将一个传统类升级为新式类)

4.元类的创建

我们通过在元类创建一个类时显示时间标签来帮助我们了解元类的创建,示例1

 1 from time import ctime
 2  
 3 print "***welcome to metaclasses!"
 4 print "	metaclass declaration first"
 5 class MetaC(type):
 6     def __init__(cls, name, bases, attrd):
 7         super(MetaC, cls).__init__(name, bases, attrd)
 8         print "***created class %r at:%s" % (name, ctime())
 9 print "	class 'foo' declaration next"
10 class Foo(object):
11     __metaclass__ = MetaC
12     def __init__(self):
13         print "***instantiated class %r at:%s" % (self.__class__.__name__, ctime())
14 print "	class 'foo' instantiation next"
15 f = Foo()
16 print "	Done"

***welcome to metaclasses!
    metaclass declaration first
    class 'foo' declaration next
***created class 'Foo' at:Tue Jun 09 22:57:00 2015
    class 'foo' instantiation next
***instantiated class 'Foo' at:Tue Jun 09 22:57:00 2015
    Done

 示例2

元类代码

1 from warnings import warn
2 class ReqStrSugRepr(type):
3     def __init__(cls, name, bases, attrd):
4         super(ReqStrSugRepr, cls).__init__ (name, bases, attrd)
5         if '__str__' not in attrd:
6             raise TypeError("Class requires overriding of __str__()")
7         if '__repr__' not in attrd:
8             warn('Class suggests overriding of __repr__()
', stacklevel=3)

元类的实例

 1 class Foo(object):
 2     __metaclass__ = ReqStrSugRepr
 3     def __str__(self):
 4         return 'Instance of class:', self.__class__.__name__
 5     def __repr__ (self):
 6         return self.__class__.__name__
 7 print '*** Defined Foo class
'
 8 class Bar(object):
 9     __metaclass__ = ReqStrSugRepr
10     def __str__(self):
11         return 'Instance of class:', self.__class__.__name__
12 print '*** Defined Bar class
'
13 class FooBar(object):
14     __metaclass__ = ReqStrSugRepr
15 print '*** Defined FooBar class
'

关于元类的在线文档 PEPs252PEPs253"What's New in Python 2.2""Unifying Types and Classes in Python2.2"

13.17相关模块和文档

模块 说明
UserList 提供一个列表对象的封装类
UserDict 提供一个字典对象的封装类
UserString 提供一个字符串对象的封装类;它又包括一个MutableString子类,如果有需要,可以提供有关功能
tyeps 定义所有Python对象的类型在标准PYthon解释器中的名字
operator 标准操作符的函数接口
原文地址:https://www.cnblogs.com/autoria/p/4510663.html