C中文网python编程基础chapter-8-Python类和对象

2019-09-04

在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() 方法的调用者。 方法的第一个参数所代表的对象是不确定的,但它的类型是确定的,即它所代表的只能是当前类的实例;只有当这个方法被调用时,它所代表的对象才被确定下来谁在调用这个方法,方法的第一个参数就代表谁。

4. Python类调用实例方法

5. Python静态方法和类方法的区别和应用(无师自通)

6. Python @函数装饰器及用法(超级详细)

7. 浅谈Python类命名空间

8. Python类变量和实例变量(详解版)

9. Python property函数:定义属性

10. Python封装机制及实现方法

11. Python继承机制及其使用

12. Python父类方法重写(入门必读)

13. Python super函数:调用父类的构造方法

14. Python slots:限制类实例动态添加属性和方法

15. Python type函数:动态创建类

16. Python metaclass详解

17. Python多态原理及实现(入门必读)

18. Python issubclass和isinstance函数:检查类型

19. Python枚举类定义和使用(详解版)