在C语言中文网上,学习了python编程的基础课程,此为学习笔记记录,备查,备翻阅复习。
1. Python class定义类,Python类的定义(入门必读)
在面向对象的程序设计过程中有两个重要概念:类(class)和对象(object,也被称为实例,instance),其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。从这个意义上看,日常所说的人,其实都是人的对象,而不是人类。
Python 定义类的简单语法如下:
class 类名:
执行语句...
零个到多个类变量...
零个到多个方法...
类名只要是一个合法的标识符即可,但这仅仅满足的是 Python 的语法要求:如果从程序的可读性方面来看,Python 的类名必须是由一个或多个有意义的单词连缀而成的,每个单词首字母大写,其他字母全部小写,单词与单词之间不要使用任何分隔符。 从上面定义来看,Python 的类定义有点像函数定义,都是以冒号(:)作为类体的开始,以统一缩进的部分作为类体的。区别只是函数定义使用 def 关键字,而类定义则使用 class 关键字*。
Python 类所包含的最重要的两个成员就是变量和方法,其中类变量属于类本身,用于定义该类本身所包含的状态数据:而实例变量则属于该类的对象,用于定义对象所包含的状态数据:方法则用于定义该类的对象的行为或功能实现。
在类中定义的方法默认是实例方法,定义实例方法的方法与定义函数的方法基本相同,只是实例方法的第一个参数会被绑定到方法的调用者(该类的实例),因此实例方法至少应该定义一个参数,该参数通常会被命名为 self。
在实例方法中有一个特别的方法:__init__,这个方法被称为构造方法。构造方法用于构造该类的对象,Python 通过调用构造方法返回该类的对象(无须使用 new)。(Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义,本教程后面还会详细介绍这些特殊的方法。)
下面程序将定义一个 Person 类:
class Person :
'这是一个学习Python定义的一个Person类'
# 下面定义了一个类变量
hair = 'black'
def __init__(self, name = 'Charlie', age=8):
# 下面为Person对象增加2个实例变量
self.name = name
self.age = age
# 下面定义了一个say方法
def say(self, content):
print(content)
上面的 Person 类代码定义了一个构造方法,该构造方法只是方法名比较特殊:__init__,该方法的第一个参数同样是 self,被绑定到构造方法初始化的对象。
- 定义变量;
- 创建对象;
- 派生子类;
2. Python类对象的创建和使用
上面段落中,我们已经创建了名为 Python 的类:
如下代码示范了调用 Person 类的构造方法:
# 调用Person类的构造方法,返回一个Person对象
# 将该Person对象赋给p变量
p = Person()
创建对象之后,接下来即可使用该对象了。Python 的对象大致有如下作用:
- 操作对象的实例变量(包括访问实例变量的值、添加实例变量、删除实例变量)。
- 调用对象的方法。
对象访问方法或变量的语法是:对象.变量|方法(参数)
。在这种方式中,对象是主调者,用于访问该对象的变量或方法。
下面代码通过 Person 对象来调用 Person 的实例和方法:
# 输出p的name、age实例变量
print(p.name, p.age) # Charlie 8
# 访问p的name实例变量,直接为该实例变量赋值
p.name = '李刚'
# 调用p的say()方法,声明say()方法时定义了2个形参
# 但第一个形参(self)是自动绑定的,因此调用该方法只需为第二个形参指定一个值
p.say('Python语言很简单,学习很容易!')
# 再次输出p的name、age实例变量
print(p.name, p.age) # 李刚 8
这两行代码用传入的 name、age 参数(这两个参数都有默认值)对 self 的 name、age 变量赋值。由于 Python 的第一个 self 参数是自动绑定的(在构造方法中自动绑定到该构造方法初始化的对象),而这两行代码就是对 self 的 name、age 两个变量赋值,也就是对该构造方法初始化的对象(p 对象)的 name、age 变量赋值,即为 p 对象增加了 name、age 两个实例变量。 上面代码中通过 Person 对象调用了 say() 方法,在调用方法时必须为方法的形参赋值。但 say() 方法的第一个形参 self 是自动绑定的,它会被绑定到方法的调用者(p),因此程序只需要为 say() 方法传入一个字符串作为参数值,这个字符串将被传给 content 参数。 大部分时候,定义一个类就是为了重复创建该类的对象,同一个类的多个对象具有相同的特征,而类则定义了多个对象的共同特征。从某个角度来看,类定义的是多个对象的特征,因此类不是一个具体存在的实体,对象才是一个具体存在的实体。完全可以这样说:你不是人这个类,我也不是人这个类,我们都只是人的对象。
由于 Python 是动态语言,因此程序完全可以为 p 对象动态增加实例变量只要为它的新变量赋值即可;也可以动态删除实例变量(使用 del 语句即可删除)。例如如下代码:
# 为p对象增加一个skills实例变量
p.skills = ['programming', 'swimming']
print(p.skills)
# 删除p对象的name实例变量
del p.name
# 再次访问p的name实例变量
print(p.name) # AttributeError
上面程序先为 p 对象动态增加了一个 skills 实例变量,即只要对 p 对象的 skills 实例变量赋值就是新增一个实例变量。接下来程序中调用 del 删除了 p 对象的 name 实例变量,当程序再次访问 print(p.name) 时就会导致 AttributeError 错误,并提示:'Person' object has no attribute 'name'
。
Python对象动态添加变量和方法
Python 是动态语言,当然也允许为对象动态增加方法。比如上面程序中在定义 Person 类时只定义了一个 say() 方法,但程序完全可以为 p 对象动态增加方法。 但需要说明的是,为 p 对象动态增加的方法,Python 不会自动将调用者自动绑定到第一个参数(即使将第一个参数命名为 self 也没用)。例如如下代码:
# 先定义一个函数
def info(self):
print("---info函数---", self)
# 使用info对p的foo方法赋值(动态绑定方法)
p.foo = info
# Python不会自动将调用者绑定到第一个参数,
# 因此程序需要手动将调用者绑定为第一个参数
p.foo(p) # ①
# 使用lambda表达式为p对象的bar方法赋值(动态绑定方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p) # ②
上面的第 5 行和第 11 行代码分别使用函数、lambda 表达式为 p 对象动态增加了方法,但对于动态增加的方法,Python 不会自动将方法调用者绑定到它们的第一个参数,因此程序必须手动为第一个参数传入参数值,如上面程序中 ① 号、② 号代码所示。
如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于 types 模块下的 MethodType 进行包装。例如如下代码:
def intro_func(self, content):
print("我是一个人,信息为:%s" % content)
# 导入MethodType
from types import MethodType
# 使用MethodType对intro_func进行包装,将该函数的第一个参数绑定为p
p.intro = MethodType(intro_func, p)
# 第一个参数已经绑定了,无需传入
p.intro("生活在别处")
正如从上面代码所看到的,通过 MethodType 包装 intr_func 函数之后(包装时指定了将该函数的第一个参数绑定为 p),为 p 对象动态增加的 intro() 方法的第一个参数己经绑定,因此程序通过 p 调用 intro() 方法时无须传入第一个参数,就像定义类时己经定义了 intro() 方法一样。
3. python self用法详解
对于在类体中定义的实例方法,Python 会自动绑定方法的第一个参数(通常建议将该参数命名为 self),第一个参数总是指向调用该方法的对象。根据第一个参数出现位置的不同,第一个参数所绑定的对象略有区别:
- 在构造方法中引用该构造方法正在初始化的对象
- 在普通实例方法中引用调用该方法的对象。
由于实例方法(包括构造方法)的第一个 self 参数会自动绑定,因此程序在调用普通实例方法、构造方法时不需要为第一个参数传值。self 参数(自动绑定的第一个参数)最大的作用就是引用当前方法的调用者,比如前面介绍的在构造方法中通过self 为该对象增加实例变量。也可以在一个实例方法中访问该类的另一个实例方法或变量。假设定义了一个 Dog 类,这个 Dog 对象的 run() 方法需要调用它的 jump() 方法,此时就可通过 self 参数作为 jump() 方法的调用者。 方法的第一个参数所代表的对象是不确定的,但它的类型是确定的,即它所代表的只能是当前类的实例;只有当这个方法被调用时,它所代表的对象才被确定下来谁在调用这个方法,方法的第一个参数就代表谁。