我们已经知道在Python中“一切皆对象”,每个对象都有特定的类型,现在让我们来尝试创建自己的类型——这需要使用class关键字来定义新的“类”(Class),类是用来生成对象的“模板”,对象则是其所属类的“实例”——以下是在交互模式中自定义Thing类,并调用其默认构造器生成一个Thing类的实例对象(注意:自定义类的命名规范要求单词首字母大写):
In[1]:classThing: ...:"""最简单的自定义类""" ...: In[2]:type(Thing) Out[2]:type In[3]:t=Thing() In[4]:type(t) Out[4]:__main__.Thing
可以看到,Thing对象属于type类型,是type类的一个实例;t对象属于Thing类型,是Thing类的一个实例——当你在程序中定义自己的类来生成实例对象,就算是“面向对象编程”(Object-Oriented Programming,简称OOP)。面向对象的编程方式使用类来模拟和组织现实世界的事物,可以令程序结构更灵活、条理更清晰。
上面定义的Thing类所生成的实例对象并不能做什么事情,让我们再来创建一个包含了具体子语句的“船”类并生成两个“船”对象:
In[5]:classShip: ...:"""船类""" ...:def__init__(self,name=None): ...:"""初始化船实例""" ...:self.name=name#船名 ...:self.crew=0#船员人数 ...:defjoin(self,number): ...:"""船员加入""" ...:self.crew+=number ...:returnself.crew ...: In[6]:s1=Ship("郑和") In[7]:s1.crew=200 In[8]:s2=Ship("戚继光") In[9]:s2.join(100) Out[9]:100 In[10]:s2.crew Out[10]:100
Ship类定义了一个特殊的“初始化”方法__init__,这样就能在调用构造器生成实例时加入新的实例“属性”(Property),所谓实例属性就是实例对象的“成员变量”,例如Ship类的实例增加了name和crew属性——从现实概念来理解,任何船都有船名和船员人数这两个数据,但每艘船又有各自的具体数据值。实例属性和实例方法是最常见的两种类成员,Python规定特殊类成员名以两个下划线开始和结束,其他类成员名遵循标准的变量命名规范,注意这里有一个细节概念:作为类成员的__init__属于函数,作为实例成员的__init__则属于方法,在类中定义函数时约定首个参数为“self”,它会指向所生成的实例对象以便操作其成员,对应的实例方法则无此参数,所以调用Ship构造器时只需传入一个参数(也可以不传入任何参数,因为name指定了默认值)。除了实例属性,你也可以定义新的实例方法,让实例能够做更多的事情——例如“船”类还有一个“船员加入”方法。
In[11]:help(Ship) HelponclassShipinmodule__main__: classShip(builtins.object) |船类 | |Methodsdefinedhere: | |__init__(self,name=None) |初始化船实例 | |join(self,number) |船员加入 | |---------------------------------------------------------------------- |Datadescriptorsdefinedhere: | |__dict__ |dictionaryforinstancevariables(ifdefined) | |__weakref__ |listofweakreferencestotheobject(ifdefined) In[12]:type(Ship.__init__) Out[12]:function In[13]:type(s2.__init__) Out[13]:method In[14]:s1.__dict__ Out[14]:{'crew':200,'name':'郑和'}
实例对象之所以拥有不必自定义而默认存在的特殊成员,是因为面向对象编程的一个重要特性“继承”(Inheritance)——使用继承机制能够将复杂的系统有机地组织起来,所有类都是同一个庞大家族的成员——定义类时可以在类名后加括号指定“基类”,新类将成为其“子类”;如果不指定基类,就默认为最基本的“object”类的子类。子类会继承基类的现有成员,子类定义属性和方法时如果与基类成员同名,就会“覆盖”基类成员。例如下面的程序定义了“船”类及其子类“战舰”类:
"""ship.py船的家族""" classShip: """船类""" def__init__(self,name=None): """初始化船实例""" self.name=name#船名 self.crew=0#船员人数 defjoin(self,number): """船员加入""" self.crew+=number returnself.crew classWarship(Ship): """战舰类""" def__init__(self,name=None,level=None): super().__init__(name)#先调用基类初始化方法 self.level=level#舰级 if__name__=="__main__": ws1=Warship("蓝色空间","恒星级") ws1.join(500) print("{}战舰{}号,现有舰员{}人。".format(ws1.level,ws1.name,ws1.crew))
可以注意到Warship类重新定义了__init__,这就会覆盖Ship类中的__init__,所以先调用基类的__init__才能继承到基类定义的实例属性name和crew。
接下来的示例是一个简单的计算器:
"""tkcalc.pyw简单的计算器 """ importtkinterastk classCalc(tk.Tk): """计算器窗体类""" def__init__(self): """初始化实例""" tk.Tk.__init__(self) self.title("计算器") self.memory=0#暂存数值 self.create() defcreate(self): """创建界面""" btn_list=["C","M->","->M","/", "7","8","9","*", "4","5","6","-", "1","2","3","+", "+/-","0",".","="] r=1 c=0 forbinbtn_list: self.button=tk.Button(self,text=b,width=5, command=(lambdax=b:self.click(x))) self.button.grid(row=r,column=c,padx=3,pady=6) c+=1 ifc>3: c=0 r+=1 self.entry=tk.Entry(self,width=24,borderwidth=2, bg="yellow",font=("Consolas",12)) self.entry.grid(row=0,column=0,columnspan=4,padx=8,pady=6) defclick(self,key): """响应按钮""" ifkey=="=":#输出结果 result=eval(self.entry.get()) self.entry.insert(tk.END,"="+str(result)) elifkey=="C":#清空输入框 self.entry.delete(0,tk.END) elifkey=="->M":#存入数值 self.memory=self.entry.get() if"="inself.memory: ix=self.memory.find("=") self.memory=self.memory[ix+2:] self.title("M="+self.memory) elifkey=="M->":#取出数值 ifself.memory: self.entry.insert(tk.END,self.memory) elifkey=="+/-":#正负翻转 if"="inself.entry.get(): self.entry.delete(0,tk.END) elifself.entry.get()[0]=="-": self.entry.delete(0) else: self.entry.insert(0,"-") else:#其他键 if"="inself.entry.get(): self.entry.delete(0,tk.END) self.entry.insert(tk.END,key) if__name__=="__main__": Calc().mainloop()