李宁老师已经在「极客起源」 微信公众号推出《Python编程思想》电子书,囊括了Python的核心技术,以及Python的主要函数库的使用方法。读者可以在「极客起源」 公众号中输入 160442 开始学习。
《Python编程思想》总目录
类是面向对象的核心组成部分,类是一种自定义类型,并且可以将类作为变量的类型、以及函数参数和返回值的类型。
1. 如何定义类
在面向对象的程序设计过程中有两个重要概念:类(class)和对象( object),对象也被称为实例(instance),其中类是拥有类似特性的对象的抽象,可以把类理解成某种概念,对象才是一个具体存在的实体。
从这个意义上看,日常所说的具体的人就是对象,而“人类”相当于类。
Python定义类的简单语法如下:
class类名:
执行语句...
零个到多个类变量...
零个到多个方法...
类名只要是一个合法的标识符即可,但这仅仅满足的是 Python的语法要求。如果从程序的可读性方面来看,Python的类名最好是由一个或多个有意义的单词连缀而成的,每个单词首字母大写(大驼峰命名法),其他字母全部小写,单词与单词之间不要使用任何分隔符。
从上面定义来看,Python的类定义有点像函数定义,都是以冒号(:)作为类体的开始,以缩进的部分作为类体的。区别只是函数定义使用def关键字,而类定义则使用 class关键字。
Python的类定义由类头(指 class关键字和类名部分)和统一缩进的类体构成,在类体中最主要的两类成员就是变量和方法。如果不为类定义任何变量和方法,那么这个类就相当于一个空类。如果空类不需要其他可执行语句,则可使用pass语句作为占位符。例如,如下类定义是允许的。
class MyClass
pass
通常来说,空类没有太大的实际意义。类中各成员之间的定义顺序没有任何影响。各成员之间可以相互调用。
成员变量属于类本身,用于定义该类本身所包含的状态数据。而实例变量则属于该类的对象,用于定义对象所包含的状态数据。方法则用于定义该类的对象的行为或功能实现。
Python是一门动态语言,因此它的类所包含的类变量可以动态增加或删除。程序在类体中为新变量赋值就是增加类变量,程序也可在任何地方为已有的类增加变量。程序可通过del语句删除已有类的类变量。
与类变量类似,Python对象的实例变量也可以动态增加或删除。只要对新实例变量赋值就是增加实例变量,因此程序可以在任何地方为已有的对象增加实例变量。程序可通过del语句删除已有对象的实例变量。
在类中定义的方法默认是实例方法,定义实例方法的方式与定义函数的方式基本相同,只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例)上。因此实例方法至少应该定义一个参数,该参数通常会被命名为self。
注意:
实例方法的笫1个参数并不一定要叫self,其实完全可以叫任何参数名,只是约定俗成地把该参数命名为self,这样具有最好的可读性。
在实例方法中有一个特别的方法:init。这个方法被称为构造方法。构造方法用于构造该类的对象,Python通过调用构造方法返回该类的对象(创建Python对象时不需要使用new)。
构造方法是一个类创建对象是第1个要执行的方法,因此 ,Python还提供了一个功能。如果开发者没有为该类定义任何构造方法,那么 Python会自动为该类定义一个只包含一个self参数的构造方法。
下面程序将定义一个 Product类。
示例代码:Product.py
class Product :
'我们编写的第1个Python类'
# 下面定义了一个类变量
type = 'Mobile'
def __init__(self, name = 'iPhone12 Pro', price=8000):
# 下面为Product对象增加2个实例变量
self.name = name
self.price = price
# 下面定义了一个buy方法
def buy(self, price):
print(f"您已经花了{price}元购买了一部{self.name}手机")
上面的Product类代码定义了一个构造方法,该构造方法只是方法名比较特殊,该方法的第1个参数同样是self,被绑定到构造方法初始化的对象。与函数类似的是, Python也允许为类定义说明文档,该文档同样被放在类声明之后、类体之前,如上面程序中第1行的字符串所示。
在定义类之后,接下来即可使用该类了。 Python的类大致有如下作用。
-
定义变量
-
创建对象
-
派生子类
下面先介绍如何创建类的实例(对象),以及如何使用对象。
2. 对象的创建和使用
创建对象的根本途径是构造方法,调用某个类的构造方法即可创建这个类的对象, Python无须使用new调用构造方法。如下代码示范了调用 Product类的构造方法(Product类的代码见前面的内容)。
# 调用Product类的构造方法,返回一个product
# 将该Product对象赋值给product变量
product = Product()
在创建对象之后,接下来即可使用该对象了。 Python的对象大致有如下作用。
-
操作对象的实例变量(包括访问实例变量的值、添加实例变量、删除实例变量);
-
调用对象的方法;
对象访问方法或变量的语法如下:
对象.变量|方法(参数)
在这种方式中,对象是主调者,用于访问该对象的变量或方法。下面代码通过Product对象来调用 Product的实例和方法(Product类的源代码见前面的内容)。
# 输出product的name、price实例变量
print(product.name, product.price)
# 访问product的name实例变量,直接为该实例变量赋值
product.name = 'iPhone12 pro max'
product.price = 12000
# 调用product的buy()方法,声明buy()方法时定义了2个形参
# 但第1个形参(self)是自动绑定的,因此调用该方法只需为第2个形参指定一个值
product.buy(12000)
# 再次输出p的name、age实例变量
print(product.name, product.price)
上面程序开始访问了product对象的name和price两个实例变量。这两个变量是何时定义的呢?留意在Product的构造方法中有如下2行代码
self. name= name
self. price = price
这两行代码用传入的name和price参数(这两个参数都有默认值)对self的name、price变量赋值。由于 Python的第1个self参数是自动绑定的(在构造方法中自动绑定到该构造方法初始化的对象),而这2行代码就是对self的name、price两个变量赋值,也就是对该构造方法初始化的对象(product对象)的name和age变量赋值,即为product对象增加了name和price两个实例变量。
上面代码中通过Product对象调用了buy方法,在调用该方法时必须为方法的形参赋值。但buy方法的第1个形参self是自动绑定的,它会被绑定到方法的调用者(product)上,因此程序只需要为buy方法传入1个浮点数作为参数值,这个浮点数将被传给price参数。
大部分时候,定义一个类就是为了重复创建该类的对象,同一个类的多个对象具有相同的特征,而类则定义了多个对象的共同特征。从某个角度来看,类定义的是多个对象的特征,因此类不是个具体存在的实体,对象才是一个具体存在的实体。
3. 动态增加实例变量
由于 Python是动态语言,因此程序完全可以为product对象动态增加实例变量。只要为它的新变量赋值即可,动态删除实例变量需要使用del语句。
为Product对象增加一个size实例变量
# 为product对象增加一个size实例变量
product.size = '6.7英寸'
print(product.size)
# 删除product对象的name实例变量
del product.name
# 再次访问pproduct的name实例变量
# print(product.name) # AttributeError
上面程序先为product对象动态增加了一个size实例变量。只要对roduct对象的 price实例变量赋值,就是新增一个实例变量。
接下来程序中调用del删除了product对象的name实例变量,当程序再次访问print( p name)时就会导致 Attribute error错误,并提示:" Product' object has no attribute name
Python是动态语言,当然也允许为对象动态增加方法。比如上面程序中在定义 Product类时只定义了一个buy方法,但程序完全可以为product对象动态增加方法。
但需要说明的是,为product对象动态增加的方法,Python不会自动将调用者自动绑定到第一个参数(即使将第1个参数命名为self也没用)。例如如下代码。
# 先定义一个函数
def info(self):
print("---info函数---", self)
# 使用info对Product的info方法赋值(动态绑定方法)
product.info = info
# Python不会自动将调用者绑定到第一个参数,
# 因此程序需要手动将调用者绑定为第一个参数
product.info(product)
# 使用lambda表达式为product对象的bar方法赋值(动态绑定方法)
product.info = lambda self: print('--lambda表达式--', self)
product.info(product)
上面两行粗体字代码分别使用函数、 lambda表达式为p对象动态增加了方法,但对于动态增加的方法,Python不会自动将方法调用者绑定到它们的第1个参数,因此程序必须手动为第1个参数传入参数值,如上面程序中所示。如果希望动态增加的方法也能自动绑定到第1个参数,则可借助于 types模块下的 Method Type进行包装。例如如下代码。
def intro_func(self, price):
print(f"您已经花了{price}元购买了一部手机")
# 导入MethodType
from types import MethodType
# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
product.intro = MethodType(intro_func, product)
# 第一个参数已经绑定了,无需传入
product.intro("生活在别处")
正如从上面代码所看到的,通过 Method Type包装 intro_func函数之后(包装时指定了将该函数的第1个参数绑定为product),为product对象动态增加的 info方法的第1个参数已经绑定,因此,程序通过product调用 intro方法时无须传入第一个参数的值。只需要像定义类时已经定义了 info方法一样。
-----------------支持作者请转发本文,也可以加李宁老师微信:unitymarvel,或扫描下面二维码加微信--------
欢迎关注 极客起源 微信公众号,更多精彩视频和文章等着你哦!