python如何将函数和数据整合在一起,并且通过一个对象的名称访问它们。
如何和为什么使用类与对象,以及他们如何使编程人员易于多种情形下编写和使用程序。
3.1考虑编程
现在要在python中创建一个对对象的描述,您已有足够的只是获得两个视图。第一个是数据视图,除了顶层或者全局作用域的数据外,可以根据需要使用和清除它们。另一个函数视图,他们没有固有的数据,而是操作提供给他们的数据。
3.1.1对象的含义
任何一个数据都有对象,每个对象都由3部分组成:标识,类型和值。对象的标识代表该对象在内存中存储的位置(不可改变的),对象的类型表明它可以拥有数据和值的类型,在对象中,可变类型的值可以更改,不可变类型的值不能更改。
简单些的解释是参考本书中已经介绍的对象。例如,整型,字符串,列表等都是对象。可以在程序中很方便地使用这些对象,但是将关系紧密的对象整合在一起岂不是更有意义?这就是类的由来,类允许定义一组对象,并将他们封装到一个方便的空间去。
3.1.2已经了解的对象
3.1.3如何使用对象
3.2定义类
当思考包含几百行python代码的小程序如何运行时,经常可以发现程序将数据组织成组的形式——当访问某个数据时,将影响与该数据一起协作的数据。经常会碰到有依赖关系的完整数据列表,如列表1的第一个元素和列表2和列表3中的第一个元素匹配。有时,必须通过创造性地将这些列表组合起来才能解决这个问题。python运用了创建用作占位符的整个类的概念,类起了占位符的作用,当一个类被调用时,它创建了绑定到一个名称的对象。
3.2.1如何创建对象
定义一个类
使用关键字class,在后面紧跟一个名称来完成。
class Fridge:
由类创建对象
>>>f=Fridge()
此时还没有定义任何复杂的类,Fridge类基本是空的,它作为一个起点。然而,即使他是空的,也应当注意到已经创建了一个可用的空类,它几乎不进行任何操作。
花上几分钟看_init_和self部分,则是类的两个非常重要的特征,当python创建对象时,_init_方法传递给对象第一个参数。而self实际上是代表该实例本身的变量。
编写内部方法
这个内部方法,不能判断当前传入的类型是否有效,应当用接口函数去检测。在允许的每个地方做检查是一个好想法,但在当前这个例子中,不打算在此处检查,因为只会以非常简单的方式使用_add_multi方法。
编写接口方法
为了更快捷,现在可以不输入文档字符串,此处的方法使您在遇到问题时,可以更好地理解代码的实际操作。
这些方法需要缩进在Fridge类的定义中,看上去每行的初始位置开始的任何代码实际上是前一行的延续,应当输入到同一行上:
def add_one(self, food_name): if type(food_name) != type(""): raise TypeError,"add_one requires a string,given a %s"% type (food_name) else: self._add_multi(food_name,1) return True def add_many(self, food_dict): if type(food_dict) != type({}): raise TypeError("add_many requires a dictionary,got a %s" food_dict) for item in food_dict.keys(): self._add_multi(item, food_dict[item]) return
add_one和add_many的目的是类似,并且每个方法都有可以确保他们被正确使用的代码,他们都可以使用add-multi来完成主要工作。现在,如果add-multi的工作方式分发生改变,开发人员可以节省时间,因为它将自动改变使用它的两个方法的行为方式。
现在已经编写了足够的代码,可以把食物放入Frige对象中,但是没有方法可以将放入冰箱的食物拿出来。可以直接访问object.items字典,但是除了测试的时候,这种不是一个好主意。但是现在就是测试,为何不这样做呢?
>>>f = Fridge({"eggs":6,"milk":4,"cheese":3}) >>>f.items {'cheese':3,'eggs':6,'milk':4} >>>f.add_one("grape") True >>>f.items {'cheese':3,'eggs':6,'grape':1,'milk':4} >>>f.add_many({"mushroom":5,"tomato":3}) >>>f.items {"tomato":3,'cheese':3,'grape':1,'mushroom':5,'eggs':6,'milk':4}
目前为止输入的代码都能正常工作了,接下来需要增加可以判断冰箱中是否存在某物的方法。
编写代码真是某物是否在冰箱中存在很重要,因为它可以用于取出食物的方法中,如get_one,get_many,get_ingredients,从而使这些方法可以检查冰箱中是否有足够多所需的食物,这正是has和has_various方法的用途。
def has(self,food_name,quantity=1): return self.has_various({food_name:quantity}) def has_various({self,foods): try: for food in foods.keys(): if self.items[food] < foods[food]: return False return True except keyError: return False
使用更多的方法:
现在可以使用python-i或者Run with Interpreter命令调用ch6.py文件,这样可以使用调价到Fridge类的任何代码。如果出现错误而不是>>>提示符,注意抛出的异常,并试图修复缩进问题,拼写错误或者其他基本错误。
Fridge类可按下述方法使用:
>>>f = Fridge({"eggs":6,"mike":4,"cheese":3}) >>>if f.has("cheese",2): ... print("its time to make an omelet") ... its time to make an omelet
实例说明:现在已经定义了新的方法,f对象可以使用它们,当用鸡蛋牛奶以及奶酪重新创建f对象时,就从新的fridge类创建对象,因此它拥有新添加的可用方法。
最后,我们应当讨论从冰箱中取食物的方法了。与向冰箱中添加食物的方法类似,由一个核心方法完成主要工作,所有接口方法都是依赖于这个方法。
def _get_multi(self,food_name,quantity): try: if(self.items[food_name] is None): return False; if(quantity>self.items[food_name]): return False; self.items[food_name] = self.items[food_name]-quantity except KeyError: return False return quantity
定义后,可以创建文档字符串指定的方法,其中每个方法都是使用_get_multi,因此都可以最少的额外代码从冰箱中取出食物:
3.2.2对象和它们的作用域
函数为他们使用的名称创建了自己的空间,也就是作用域。当函数被调用时,声明了名称并赋予值之后,只要函数还在使用,任何对该名称做出的修改会持续下去。然而,在函数结束运行后,并在此被调用,之前电泳过程中所做的工作都丢失了,改函数必须重新开始执行。
对象中的值可被存储的,并关联在self中,就是说self是指向对象的。
创建另外一个类:
我们创建了一个fridge类,现在创建omelet类:
现在有一个类,它目的很明确。omelet类有接口方法,使得它可与fridge对象协作,它仍具备创建指定煎蛋卷的功能。
下面代码都必须在omelet类定义下缩进一个级别:
def _ingredients_(self): return self.needed_ingredients def get_kind(self): return self.kind def set_kind(self,kind): possible_ingredients = self._known_kinds(kind) if possible_ingredients == False: return False else: self.kind = kind self.needed_ingredients = possible_ingredients def set_new_kind(self,name,ingredients): self.kind = name self.needed_ingredients = ingredients return def _known_kind(self,kind): if kind == "cheese": return {"eggs":2,"mike":1,"cheese":1} elif kind == "mushroom": return {"eggs":2,"mike":1,"cheese":1,"mushroom":2} elif kind == "onion": return {"eggs":2,"mike":1,"cheese":1,"onion":1} else: self.kind = kind self.needed_ingredients = ingredients return def get_kown_kinds(self,fridge): self.from_fridge = fridge.get_ingredients(self) def mix(self): for ingredient in self.from_fridge.keys(): print("Mixing %d %s for the %s omelet"% self.from_fridge[ingredient],ingredient,self.kind) self.mixed = true def make(self): if self.mixed ==true: print("cooking the %s omelet!"% self.kind) self.cooked = true
现在有一个omelet类可以创建omelet对象,omelet类与煎蛋卷的过程有相同的特性。并且Omelet的外在表现集中到几个简单的接口
现在有两个类,用python -i或者run with Interpreter命令加载他们后,可制作一个煎蛋卷:
我们还可以制作多个煎蛋卷:
这样的编程方式,我们称之为面向对象,为什么会用于编制大型系统。