Python 面向对象(上)
一. 什么是面向对象?
1. 在了解面向对象之前,首先我们需要知道两个概念:
(1)什么是函数?
函数是对功能或动作的一种封装.
函数的语法结构如下:
def func(arg1): '''函数的内部有函数体''' print("这里是函数内部") func(arg2)
上面的结构中, func是函数名, arg1是形参, 在函数的内部是函数体. 在定义了函数以后, 我们只需要在下面 用函数名接上一个小括号( func() ) 就可以调用函数了, 小括号内部的 arg2 表示实参.
(2)什么是变量?
在程序设计中, 变量是一个可以存储值的字母或名称, 它是程序运行过程中产生的值, 被用来提供给后面的程序使用. 如 a = 10, 我们把 10 这个值赋给变量 a.
2. 面向过程与面向对象
在了解了以上两个概念以后, 我们现在可以开始讨论什么是面向对象了.
(1)从我们最初接触编程开始, 我们就使用一些关键词, 诸如 if, else, while 等等, 来帮助我们规范程序的逻辑, 按照我们的想法使程序运行下去. 实际上, 这种编程思想就是"面向过程", 我们可以用更加正式的语言来描述面向过程:
面向过程: 一切以事物的流程为核心. 根据业务逻辑(即事物的发展过程, 动作的先后顺序)从上到下写垒代码, 其核心在于"流程(过程)".
优点: 把一个事件步骤化,流程化, 程序编写相对简单.
缺点: 可扩展性差(甚至可以说是极差).
(2)于是我们现在引出一个概念 -- 面向对象.
面向对象: 一切以对象为中心. 对象是属性和动作的集合体. 面向对象就是指对函数进行分类和封装.
优点: 可扩展性强(体现在继承和多态上)
缺点: 编程的复杂程度高于面向过程
简单来说, 最开始接触编程时, 我们的思维,我们的思想是 专注于事物发展过程,动作先后顺序的. 然而, 面向对象却与之截然不同, 这种思维专注于事物这个对象本身, 处于这种思维之下, 可以认为世间万物皆是对象. 我们曾经关注的那些事物的动作和发展顺序, 在面向对象的思维中, 他们都是对象的属性, 于是, 无数包含着各种属性的对象就组成了我们的整个世界.
二. 什么是面向对象编程?
1. 类(Class)
类(Class): 用来描述具有相同的属性和方法的对象的集合体. 它定义了该集合体中每个对象所共有的属性和方法. 对象是类的实例.
class Foo: pass # class -- 关键字, 表示创建类 # Foo -- 类名
2. 实例化
实例化:创建一个类的实例,类的具体对象
class Foo: def __init__(self, property1, property2) self.property1 = property1 self.property2 = property2 obj = Foo("属性1", "属性2") # Foo() -- 类的实例化 # obj = Foo() -- 把Foo实例化为obj, obj被称为实例对象(简称为"对象") # "属性1", "属性2" 分别被传给参数 property1, property2
3. 成员
成员: 在类中定义的变量和方法都被称为成员.
成员分类: 变量, 方法, 属性
class Foo: class_variable = "这里是类变量" def __init__(self, property): """__init__用于初始化一个类""" self.property = property self.instance_variable = "实例变量" """这里的self.xxx都属于实例变量""" def instance_method(self): """实例方法必须有参数self""" print("这里是实例方法") @classmethod def class_method(cls): """类方法必须有参数cls,这里cls传递的是类名Foo""" obj = cls("普通属性") print("这里是类方法", obj) @staticmethod def static_method(): """静态方法对参数没有固定要求,根据实际需要即可""" print("这里是静态方法") @property def dynamic_property(self): return "这里是动态属性"
3.1 变量: 实例变量, 类变量(静态变量)
(1)实例变量
实例变量:定义在方法中的变量,只作用于当前实例的类.
class Foo: def __init__(self, property): """__init__用于初始化一个类,它是构造方法""" self.property = property self.instance_variable = "实例变量" """这里的self.xxx都属于实例变量""" obj = Foo("普通属性") # 类Foo实例化为对象obj s = obj.instance_variable # 对象obj访问实例变量 print(s) # 输出结果为: 实例变量
(2)类变量(静态变量)
类变量:在Java中类变量也被称为静态变量. Python中类变量在整个实例化的对象中是公用的. 类变量定义在类中且在函数体之外. 约定俗成, 类变量通常不作为实例变量使用.
class Foo: class_variable = "这里是类变量" obj = Foo() # 实例化 print(Foo.class_variable) # 这里用类名Foo去访问类变量class_variable # 输出结果: # 这里是类变量
3.2 方法: 在类的内部定义的函数. 分为实例方法, 类方法和静态方法
(1)实例方法
定义: 第一个参数必须是实例对象, 该参数名一般约定为“self”, 通过它来传递实例的属性和方法(也可以传类的属性和方法)
调用: 约定俗成由实例对象调用
class Foo: def instance_method(self): """实例方法必须有参数self""" print("这里是实例方法") obj = Foo() # 类Foo实例化为对象obj obj.instance_method() # 实例对象调用实例方法 # 输出结果: # 这里是实例方法
(2)类方法
定义:使用装饰器@classmethod. 第一个参数必须是当前类对象, 该参数名一般约定为“cls”, 通过它来传递类的属性和方法(不能传实例对象的属性和方法)
调用:实例对象和类对象都可以调用.
class Foo: @classmethod def class_method(cls): """类方法必须有参数cls,这里cls传递的是类名Foo""" s = cls("普通属性") print("这里是类方法: ", s) obj = Foo() obj.class_method() # 输出结果: # 这里是类方法: <__main__.Foo object at 0x0000016B9B8DAD68>
(3)静态方法
定义:使用装饰器@staticmethod. 参数随意, 没有强制要求必须是“self”或“cls”参数, 但是静态方法的方法体中不能使用类或实例的任何属性和方法.
调用:实例对象和类对象都可以调用.
class Foo: @staticmethod def static_method(): """静态方法对参数没有固定要求,可以根据实际需要进行设置""" print("这里是静态方法") obj = Foo() obj.static_method() # 输出结果: # 这里是静态方法
3.3 属性(两种形式): 字段的访问形式, 方法的表现形式
class Person: """字段的访问形式""" def __init__(self, name, gender): self.name = name self.gender = gender """方法的表现形式""" @property def age(self): """这里是关于年龄的函数体""" return 18
(1)字段的访问形式
class Person: """字段的访问形式""" def __init__(self, name, gender): self.name = name self.gender = gender p = Person("张三", "男") print(p.name) # 输出结果: 张三 print(p.gender) # 输出结果: 男
字段的访问形式是最常用,最普遍的
(2)方法的表现形式
class Person: """方法的表现形式""" @property def age(self): """这里是关于年龄的函数体""" return 18 p = Person() print(p.age) # 输出结果: 18
由于对象的某些属性并不一定是固定不变的, 它可能会随着条件的改变而发生改变, 于是, 我们可以 "用方法来描述我们的属性信息".
需要格外注意的是:
"属性--方法的表现形式"有以下特点:
1. 必须在方法的上方用 @property 来进行声明
2. 该方法有且仅有一个参数 self
3. 该方法必须有返回值
4. 私有成员
私有成员只能在自己的类中访问, 在类的外部无法直接访问.
class Liu_de_hua: # 创建一个类 nickname = "华仔" # 类变量 gender = "男" __real_age = 57 # 私有成员 def __init__(self, age, wife=None): # 构造方法 self.age = age # 实例变量 self.wife = wife self.__wife = "朱丽倩" # 私有成员 def act(self): # 实例方法 print("我会表演") def date(self): # 实例方法 print("我常常和%s约会" % self.__wife) # 在实例方法内部访问私有成员 ldh = Liu_de_hua(48) print(ldh.wife) # 实例对象访问实例变量 ldh.date() # 实例对象访问实例方法 print(ldh.__wife) # 尝试用实例对象访问私有成员 # 执行结果: # None # 我常常和朱丽倩约会 # 报错: 'Liu_de_hua' object has no attribute '__wife'
私有成员的特征:
1. 私有成员必须以 双下划线 开头, 如上面代码中的 __real_age 和 __wife .
2. 我们可以在类的内部随意访问私有成员, 但在外部则不行.
三. 面向对象三大特性: 封装, 继承, 多态
关于面向对象更加准确更加规范的语言表述, 大家可以点击这里进行查看
1. 封装: 把内容(属性或方法)封装到一个对象中, 之后调用的时候直接通过对象即可调用它们.
(1)将内容封装到对象中
class Person: def __init__(self, name, year_of_birth, gender): self.name = name self.year_of_birth = year_of_birth self.gender = gender p1 = Person("刘德华", "1961", "男") p2 = Person("王菲", "1969", "女") # 实例化对象p1, 将 '刘德华', '1961', '男' 分别封装到了对象p1(对象p1等同于self)内的 'name', 'year_of_birth', 'gender' 属性中 # p2也是同样的运行过程
(2)用对象去调用被封装的内容
class Person: def __init__(self, name, year_of_birth, gender): self.name = name self.year_of_birth = year_of_birth self.gender = gender p1 = Person("刘德华", "1961", "男") p2 = Person("王菲", "1969", "女") print(p1.name) # 输出结果: 刘德华 print(p2.name) # 输出结果: 王菲
2. 继承: 子类继承父类, 子类可以继承父类中除了私有内容外的其他所有内容. Python支持多继承.
class Foo1: __private1 = "我是父类1的私有内容1" class_variables1 = "我是父类1的类变量1" def instance_method1(self): print("我是实例方法1, 我在父类1的内部") class Foo2: __private2 = "我是父类2的私有内容2" class_variables2 = "我是父类2的类变量2" def instance_method2(self): print("我是实例方法2, 我在父类2的内部") class Bar(Foo1, Foo2): def func(self): print("我是子类") b = Bar() # 实例化 print(b.class_variables1) # 子类调用类变量1 b.instance_method1() # 子类调用实例方法1 print(b.class_variables1) # 子类调用类变量2 b.instance_method2() # 子类调用实例方法2 # 以上执行结果为: # 我是父类1的类变量1 # 我是实例方法1, 我在父类1的内部 # 我是父类1的类变量1 # 我是实例方法2, 我在父类2的内部
需要注意的是: 子类无法调用父类的私有内容, 例如在上面代码中, 如果用对象b去访问__private1则会报错.
3. 多态: 一个对象有多种形态.
在Python中, 多态无处不在, 因而我们无法用语言去准确描述什么是python中的多态. 或许这既是python的优点也是它的缺点吧.
我们只能用例子来简单说明一下python中的多态, 辅助理解:
class Animal: def chi(self): print("吃是动物的本能") class Haski(Animal): def chi(self): print("哈士奇逗比作死地吃") class Monkey(Animal): def chi(self): print("猴子张牙舞爪地吃") class si_yang_yuan: def wei_yang(self, animal): animal.chi() # 把动物全部实例化 dong_wu = Animal() er_ha = Haski() hou_zi = Monkey() # 把饲养员实例化 syy = si_yang_yuan() # 饲养员喂动物 syy.wei_yang(dong_wu) syy.wei_yang(er_ha) syy.wei_yang(hou_zi) # 执行结果: # 吃是动物的本能 # 哈士奇逗比作死地吃 # 猴子张牙舞爪地吃
从上面的例子中可以看出:
当子类和父类存在相同的 chi() 方法时, 子类的 chi() 覆盖了父类的 chi(). 当子类调用chi()时, 会首先调用子类自己的 chi() 方法, 如果子类自己没有, 才会去父类中寻找.
参考资料:
2. 《python 实例方法,类方法,静态方法的区别与作用》-- 蔷薇&Nina的博客园