类
建议学习者从本章开始一边看书一边敲代码,然后反复推敲,不要追求速度,并且要多上网搜索,理解其中含义。
类也是对世界的抽象结果,所以,读者还要注意“抽象思维”方法在编程中的应用。
4.1基本概念
class,类
oop,面向对象编程
4.1.1问题空间(problem space)
问题空间是问题解决者对一个问题所达到的全部认识状态,它是由问题解决者利用问题所包含的信息和已贮存的信息主动构成的。
一个问题一般从以下三个方面来定义:
初始状态——开始时不完全的信息或令人不满意的状态。
目标状态——希望得到的信息或状态。
操作——为了从初始状态迈向目标状态,可能采取的步骤。
4.1.2对象
对象(object),是面向对象(object oriented)中的术语。
python中的一切都是对象。不管是字符串、函数、模块还是类都是对象,“万物皆对象”。
OOP大师Grandy Booch的定义:
对象:一个对象有自己的状态,行为和唯一标识;所有相同类型的对象所具有的结构和行为在它们共同的类中被定义。
状态(state):包括这个对象已有的属性(通常是类里面已经定义好的)和对象具有的当前属性值(这些属性往往是动态的)。
行为(behavior):是指一个对象如何影响外界及被外界影响,表现为对象自身状态的改变和信息的传递。
标识(identity):是指一个对象所具有的区别于所有其他对象的属性(本质上指内存中所创建的对象的地址)。
任何一个对象都要包括两部分:属性(是什么)和方法(能做什么)。
4.1.3面向对象
Object-oriented programming 是一种程序设计范式。
当我们提到面向对象的时候,它不仅指一种程序设计方法,更多意义上是一种程序开发方式。在这一方面,我们必须了解更多关于面向对象系统分析和面向对象设计方面的知识。
4.1.4类
类class描述了所创建的对象共同的属性和方法。
类有接口和结构,接口描述了如何通过方法与类及其实例互操作,结构描述了一个实例中数据如何划分为多个属性。
类的出现,为面向对象编程的三个最重要的特性(封装性、继承性、多态性)提供了实现的手段。
4.1.5编写类
4.2详解类
4.2.1新式类和旧式类
python3中不存在这个问题。
如何定义:
>>> class BB(object):
... pass
(object)这是一种继承的操作。
python3中,所有的类都是object的子类,就不用彰显出继承关系。
4.2.2创建类
类的名称一般用大写字母开头。如果是两个单词,则首字母都大写。
函数的首字母不要大写。
注意的是,类中的方法的参数跟以往函数的参数样式有区别。那就是每个方法必须包括self参数,并且作为默认的第一个参数。
def __init__ #初始化函数
所谓初始化,就是让类有一个基本的面貌,是在类被实例化的时候就执行这个函数,从而将初始化的一些属性可以放到这个函数里面。
4.2.3类中的函数(方法)
class Person:
def __init__(self,name):
self.name = name
def getName(self):
return self.name
def color(self,color):
print("%s is %s" %(self.name,color))
girl=Person('canglaoshi')
name=girl.getName()
print("the person's name is:",name)
girl.color("white")
print("-----")
print(girl.name)
输出:
the person's name is: canglaoshi
canglaoshi is white
-----
canglaoshi
4.2.4类和实例
类提供默认行为,是实例的工厂。
类由一些语句组成,但是实例通过调用类生成。
命名类必须用class。
4.2.5self的作用
类里面的函数,第一个参数是self,而且不能省略。
在类的内部,就是将所有传入的数据都赋给一个变量,通常这个变量的名字是self。这是习惯,而且是共识。
self就是一个实例(准确的说是实例的引用变量)。
4.2.6文档字符串
class Person:
"""This is my class."""
def __init__(self,name):
self.name = name
用三重引号来写说明。
4.3 辨析有关概念
4.3.1类属性和实例属性
一个类实例化后,实例是一个对象,有属性。类也是一个对象,也有属性。
>>> class A:
... x=7 #不可变对象(整数)
...
>>> A.x
7
>>> foo=A()
>>> foo.x
7
>>> foo.x+=1 #本质上相当于建立一个新的属性将旧的同名属性“覆盖”
>>> foo.x
8
>>> A.x
7
>>> del foo.x #删除这个新的属性,旧的属性显现出来。
>>> foo.x
7
>>> A.x+=1
>>> A.x
8
>>> foo.x
8
综上,“类属性不受实例属性影响,但实例属性受到类属性左右。”
>>> class B:
... y=[1,2,3] #可变对象,列表
...
>>> B.y
[1, 2, 3]
>>> bar=B()
>>> bar.y
[1, 2, 3]
>>> bar.y.append(4)
>>> bar.y
[1, 2, 3, 4]
>>> B.y
[1, 2, 3, 4]
>>> B.y.append("aa")
>>> B.y
[1, 2, 3, 4, 'aa']
>>> bar.y
[1, 2, 3, 4, 'aa']
当类中变量引用的是可变对象时,类属性和实例属性都能直接修改这个对象,从而影响另一方的值。
>>> A.y="hello"
>>> foo.y
'hello'
>>> foo.z="python"
>>> foo.z
'python'
>>> A.z
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'z'
增加一个类属性,同时在实例属性中也增加了一样的名称和数据的属性。
反过来,如果增加实例属性,类并没有收纳通过实例增加的这个属性。
4.3.2数据流转
4.3.3命名空间
namespace,定义类时,所有位于class语句中的代码都在某个命名空间中执行,即类命名空间。
命名空间是从所定义的命名到对象的映射集合。
命名空间因为对象的不同有所区别,可以分为以下几种:
内置命名空间Built-in Namespaces:python运行起来它们就存在了。内置函数的命名空间都属于内置命名空间,所以,我们可以在任何程序中直接运行它们。
全局命名空间Module:Global Namespaces:每个模块创建它自己所拥有的全局命名空间,不同模块的全局命名空间彼此独立,不同模块中相同名称的命名空间,也会因为模块的不同而不互相干扰。
本地命名空间Function&Class:Local Namespaces:模块中有函数或者类,每个函数或者类所定义的命名空间就是本地命名空间。如果函数返回了结果或抛出异常,则本地命名空间也结束了。
访问本地命名空间的方法print(locals())
访问全局命名空间的方法print(globals())
4.3.4作用域
作用域是指python程序能够直接访问到的命名空间。“直接访问”在这里意味着访问命名空间中的命名时无须加入附加的修饰符。
如果要将某个变量在任何地方都使用,且能够关联,那么在函数内就使用global声明,就是曾经讲过的全局变量。
4.4继承
Inheritance,父类别,也叫超类。
从逻辑上说,继承的目的也不是为了复用代码,而是为了理顺关系。
在python中因为存在Duck Type,接口定义的重要性大大降低,继承的作用也进一步被削弱了。
4.4.1基本概念
如果子类里面有一个和父类同样名称的方法,那么就把父类中的同一个方法遮盖住了,显示的是子类中的方法,这叫做方法的重写。
4.4.2多重继承
所谓多重继承就是指某一个类所继承的父类不止一个,而是多个。
class HotGirl(Person,Girl):
pass
4.4.3多重继承的顺序
print(C.__mro__) #打印出类的继承关系,顺序
python3的类中,都是按照“广度优先”原则搜寻属性和方法的。
2017年2月13日
4.4.4super函数
初始化函数的继承跟一般方法的继承还有点不同。
问题:因为在子类中重写了某个方法之后,父类中同样的方法被遮盖了,那么如何再把父类的该方法调出来使用呢?
python中有这样一种被提倡的方法:super函数。
class Girl(Person):
def __init__(self):
super(Girl,self).__init__()
self.breast = 90
super函数的参数,第一个是当前子类的类名字,第二个是self,然后是点号,点号后面是所要调用的父类的方法。
4.5方法
方法是类的属性,但不是实例属性。通过实例调用方法,我们称这个方法绑定在实例上。
4.5.1绑定方法
通过实例调用。
4.5.2非绑定方法
在子类中,父类的方法就是非绑定方法,因为在子类中,没有建立父类的实例,却要用父类的方法。
4.5.3静态方法和类方法
在python中:
@staticmethod表示下面的方法是静态方法。
@classmethod表示下面的方法是类方法。
class StaticMethod:
@staticmethod
def foo():
print("This is a static method foo().")
class ClassMethod:
@classmethod
def bar(cls):
print("This is a class method bar().")
print("bar() is part of class:",cls.__name__)
先看静态方法,方法后面的括号内没有self,正因为这个不同,叫它静态方法,如果没有self,那么也就无法访问实例变量、类和实例的属性了,因为它们都是借助self来传递数据的。
类方法也没有self参数,但必须有cls参数,在类方法中能够访问类属性,但是不能访问实例属性。
这两种方法都可以通过调用,即绑定实例,也可以通过类来调用(一般方法必须通过绑定实例调用)。
深入理解:
Difference between @staticmethod and @classmethod in Python
PYTHON中STATICMETHOD和CLASSMETHOD的差异
4.6多态和封装
4.6.1多态
多态polymorphism,是指面向对象程序执行时,相同的信息可能会送给多个不同的类别对象,系统可以依据对象所属类别,引发对应类别的方法而有不同的行为。
repr()函数,能够针对输入的任何对象返回一个字符串。
在程序设计中,鸭子类型(duck typing)是动态类型的一种风格,这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。可以这样认为,当看到一只鸟走起来像鸭子,游泳起来像鸭子,叫起来也像鸭子时,那么这只鸟就可以被称为鸭子。
类型检查是毁掉多态的利器。
4.6.2封装和私有化
在程序设计中,封装(encapsulation)是对对象的一种抽象。
python中的私有化方法比较简单,就是在准备私有化的属性(包括方法、数据)名字前面加双下划线。类的里面可以调用,类的外面无法调用。
如果要调用私有的属性,可以使用property函数。
class ProtectMe:
def __init__(self):
self.me="qiwsir"
self.__name="kivi"
@property
def name(self):
return self.__name
4.7特殊属性和方法
在任何类中,都有一些特殊的属性和方法,它们通常是双下划线开头和结尾。
4.7.1__dict__
要访问类或者实例的属性必须通过“object.attribute”的方式。
思考:类或者实例的属性在python中是怎么存储的?如何修改、增加、删除属性。
用dir()能够查看类的属性和方法。
>>> class Spring():
... season="the spring of class"
...
>>> Spring.__dict__
mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__module__': '__main__', 'season': 'the spring of class'})
>>> Spring.__dict__["season"]
'the spring of class'
>>> s=Spring()
>>> s.__dict__
{}
>>> s.season
'the spring of class'
>>> s.season="the spring of instance"
>>> s.__dict__
{'season': 'the spring of instance'}
>>> s.season
'the spring of instance'
>>> Spring.__dict__
mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__module__': '__main__', 'season': 'the spring of class'})
>>> Spring.__dict__["season"]
'the spring of class'
>>> del s.season
>>> s.season
'the spring of class'
>>> s.__dict__
{}
>>> Spring.flower="peach"
>>> Spring.__dict__
mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, 'flower': 'peach', '__module__': '__main__', 'season': 'the spring of class'})
>>> Spring.__dict__["flower"]
'peach'
>>> s.__dict__
{}
>>> class Spring():
... def tree(self,x):
... self.x=x
... return self.x
...
>>> Spring.__dict__
mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'Spring' objects>, '__weakref__': <attribute '__weakref__' of 'Spring' objects>, '__module__': '__main__', 'tree': <function Spring.tree at 0x7faccd16e620>})
>>> Spring.__dict__["tree"]
<function Spring.tree at 0x7faccd16e620>
>>> t=Spring()
>>> t.__dict__
{}
>>> t.tree("xiangzhangshu")
'xiangzhangshu'
>>> t.__dict__
{'x': 'xiangzhangshu'}
这也印证了实例t和self的关系,即实例方法(t.tree(‘xiangzhangshu’))的第一个参数(self,但没有写出来)绑定实例t,透过self.x来设定值,给t.dict添加属性值。
4.7.2__slots__
slots能够限制属性的定义,但是这不是它存在的终极目标,它存在的终极目标应该是在编程中非常重要的一个方面:优化内存使用。
>>> class Spring():
... __slots__=("tree","flower")
...
>>> dir(Spring)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']
>>> Spring.__slots__
('tree', 'flower')
>>> t=Spring()
>>> t.__slots__
('tree', 'flower')
slots属性挤掉了原来的dict属性。并且实例化后,实例的slots和类的完全一样,这和dict大不一样。
类给属性赋值,实例无法修改,实例给属性赋值,类可以修改。
4.7.3_getattr__、setattr和其他类似方法
class Rectangle():
"""
the width and length of Rectangle
"""
def __init__(self):
self.width = 0
self.length=0
def setSize(self,size):
self.width,self.length=size
def getSize(self):
return self.width,self.length
size=property(getSize,setSize)
if __name__=="__main__":
r=Rectangle()
r.width=3
r.length=4
print(r.getSize())
r.setSize((30,40))
print(r.width)
print(r.length)
print(r.size)
class Rectangle():
"""
the width and length of Rectangle
"""
def __init__(self):
self.width = 0
self.length=0
def __setattr__(self,name,value):
if name == "size":
self.width,self.length=value
else:
self.__dict__[name]=value
def __getattr__(self,name):
if name == "size":
return self.width,self.length
else:
return AttributeError
if __name__=="__main__":
r=Rectangle()
r.width=3
r.length=4
print(r.size)
r.size=(30,40)
print(r.width)
print(r.length)
print(r.size)
4.7.4获得属性顺序
通过实例获取其属性,如果在dict中有,就直接返回其结果;如果没有,会到类属性中找。再没有,就要调用getattr()方法。
4.8迭代器
对于序列(列表、元组)、字典和文件都可以使用iter()方法生成迭代对象,然后用next()方法访问。用dir()查看这几个不同类型的属性,会发现它们都有一个iter的东西。它是对象的一个特殊方法,是迭代规则的基础。
注意:在python3中,迭代器对象实现的next()方法,而不是next()方法。
4.8.1__iter__()
类型是list,tuple,file,dict的对象有iter()方法,标志着它们能够迭代。
在使用列表的时候,需要将列表的内容一次性都读入到内存中,这样就增加了内存的负担,如果列表太大,就有内存溢出的危险这时就需要迭代对象。
4.8.2range()和xrange()
关于列表和迭代器之间的区别还有两个非常典型的内建函数:range()和xrange()。
range返回的是一个列表,xrange返回的是可迭代的对象。
迭代器不能回退。
4.9生成器
generator生成器必须是可迭代的。
4.9.1简单的生成器
>>> my_generator=(x*x for x in range(4))
>>> my_list=[x*x for x in range(4)]
()生成器解析式
[]列表解析式
生成器解析式是迭代器,只将所需要的读入内存里,占内存少。
sum(i*i for i in range(10))
4.9.2 定义和执行过程
yield在汉语中有“生产,出产”之意,在python中它是生产器的标志。
含有yield关键词的函数是一个生成器类型的对象,这个生成器对象可迭代的。生成器是一种用普通函数语法定义的迭代器。
只要用了yield语句,普通函数就成了生成器,也就具备了迭代器的功能特性。
yield语句的作用就是在调用的时候返回相应的值。
4.9.4 生成器方法
Help on built-in function send:
send(...) method of builtins.generator instance
send(arg) -> send 'arg' into generator,
return next yielded value or raise StopIteration.
send()必须在生成器运行后挂起才能使用,即yield至少被执行一次。
throw(type, value=None, traceback=None):用于生成器内部(生成器的当前挂起处或未启动时在定义处)抛出一个异常(在yield表达式中)。
close():调用时不用参数,用于关闭生成器。