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() 方法, 如果子类自己没有, 才会去父类中寻找.

 

参考资料:

1. 《面向对象三大特性介绍》-- heroliy的博客园

2. 《python 实例方法,类方法,静态方法的区别与作用》-- 蔷薇&Nina的博客园

3. 《面向对象 - 属性》-- 原创作者sjmicosoft

4. 《面向对象程序设计概述 牛咏梅》-- 对立·统一 转载

posted @ 2018-10-03 00:22  咕噜噜~  阅读(348)  评论(0编辑  收藏  举报