美文网首页程序员
带你学python基础:面向对象编程

带你学python基础:面向对象编程

作者: 程序员欧阳 | 来源:发表于2019-02-18 22:54 被阅读4次

    面向对象编程是个啥呢,其实,在传统的语言中,比如 C 语言,是不存在面向对象编程这个概念的,那时候的语言只有面向过程编程,也就是我们写代码从头写到底,最多也就是有函数。所以,这样的代码风格是比较难维护的。

    后来,随着编程语言的改进,在很多的语言都有了面向对象的思想,比如 C++、Java、C#等,而 Python也是如此。

    一、那什么是面向对象呢?

    拿个简单的例子说说,比如我们一个人,有头、身体、腿、手等,这些东西在面向对象的思想中,都可以把他们拆分为一个一个的对象,而不会把人就看做一个对象。

    在我们经常玩的游戏中,每一个英雄,每一个兵器,都是一个个的对象,每个事物都是对象。

    在面向对象编程中,我们还会谈到另外一个概念:

    那么什么是类呢,类是一个抽象的概念,我们知道有动物,动物下面有各种各样不同的动物,狗,老虎等。所以,是就是动物,也就是不同类型的动物的总称,也是抽象。而对象就是具体的类别的动物。

    类是对象的类型,具有相同属性和行为事物的统称。类是抽象的,在使用的时候通常会找到这个类的一个具体存在。

    万物皆对象,对象拥有自己的特征行为

    打个比方,我们每个人都可以看做是一个对象,而我们每个人都有我们自己的不同的特征,同时,我们也会产生我们的各种各样的行为。

    这个图是不是看了就知道对象的特性了。

    相信讲了这么多了,我们应该知道什么是类和对象了。下面我们讲一下,如何定义类。

    二、定义类

    首先,我们通过一个案例来说说如何定义类,最后再给出定义类的方法。

    # 定义类
    class pen():
        def __init__(self, str, len):
            self.str = str
            self.len = len  # 实例变量通过init初始化声明
    
        # 定义类变量
        width = 5
    
        '''
        获取信息
        '''
        def getStr(self):
            print('str:%s,len:%s' % (self.str, self.len))
            print('width:', pen.width)
    
    
    pen = pen('初始化', 10)
    pen.getStr()
    
    

    上面定义了一个类,这个类名为pen,然后,我们在类中定义了它的特征属性strlen,同时,我们还定义了一个行为(获取信息)。

    通过这个例子,我们就可以看出怎么定义类的。

    定义类规则

    class 类名:
        属性列表
        方法列表
    

    在上面这个例子中,我们发现这里存在两种变量,一种是实例属性,一种是类属性。下面我们就说说这两种变量有什么区别。

    • 类变量:也可以说类属性,类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。如果需要用在函数中使用类名.类属性访问,如例子中的width = 5
    • 实例变量:也可以说实例属性,定义在方法中的变量,只作用于当前实例的类, 如例子中的len

    好了,我们知道怎么定义类和定义类变量和实例变量,那么如何访问这些变量呢?

    三、访问变量

    方法

    实例对象.属性
    

    举例
    例如,我们需要访问上面的pen的变量,则可以使用下面的方式。

    # 访问变量
    print(pen.len)
    print(pen.width)
    print(pen.str)
    

    当然,你可能会想,还有其他方式吗,确实,还有其他方式,Python也提供了类似JavaScript的访问方式。

    getattr(obj, name[, default]) #访问对象的属性
    hasattr(obj,name) # 检查是否存在一个属性
    setattr(obj,name,value) # 设置一个属性。如果属性不存在,会创建一个新属性
    delattr(obj, name) # 删除属性
    

    举例

    # -*- coding:utf-8 -*-
    
    # 定义类
    class pen():
        def __init__(self, str, len):
            self.str = str
            self.len = len  # 实例变量通过init初始化声明
    
        # 定义类变量
        width = 5
    
        '''
        获取信息
        '''
    
        def getStr(self):
            print('str:%s,len:%s' % (self.str, self.len))
            print('width:', pen.width)
    
    
    pen = pen('初始化', 10)
    
    # 通过内置方法访问属性
    print(getattr(pen, 'len'))
    print(hasattr(pen, 'len'))
    
    setattr(pen, 'len', 20)
    print(pen.len)
    
    delattr(pen, 'len')
    print(pen.len)
    
    

    内置类属性

    另外,Python本身还提供了自己内置的类属性,分别有下面这些。

    __dict__ : 类的属性(包含一个字典,由类的属性名:值组成) 实例化类名.__dict__
    __doc__ :类的文档字符串   (类名.) 实例化类名.__doc__
    __name__: 类名,实现方式 类名.__name__
    __bases__ : 类的所有父类构成元素(包含了以个由所有父类组成的元组)
    

    举例
    我们还是以上面的例子来讲

    print(pen.__dict__)  #会将实例对象的属性和值通过字典的形式返回
    print(pen.__doc__)
    

    特殊说明

    在前面的例子中,我们看到了initself这两个关键字,下面讲解一下。

    __init__():是一个特殊的方法属于类的专有方法,被称为类的构造函数或初始化方法,方法的前面和后面都有两个下划线。

    这是为了避免Python默认方法和普通方法发生名称的冲突。每当创建类的实例化对象的时候,__init__()方法都会默认被运行。作用就是初始化已实例化后的对象,这就是构造函数的意思。

    在方法定义中,第一个参数self是必不可少的。类的方法和普通的函数的区别就是self,self并不是Python的关键字,你完全可以用其他单词取代他,只是按照惯例和标准的规定,推荐使用self

    既然是面向对象编程,那么,接下来肯定要说一下面向对象的三大特性了。

    四、面向对象的三大特性

    封装

    封装这个特性,其实在前面就已经接触到了,只是没有明白的说而已。

    封装字面上的意思就是把东西包裹起来,那么,在面向对象的编程中,其实封装也就是这个意思,常见的,比如,前面我们说的的类 class,我们把一些对象的属性和行为包裹在一个类里面,这就是封装的特性

    继承

    我们都知道,我们有父子关系,很多时候,儿子都会去继承父亲的财产的,好像都是这样的吧,哈哈。

    在面向对象的编程中也是这么个意思,但是不叫父亲和儿子,我们把父亲叫做父类,儿子称为子类,我们子类去继承父类的财产,这个就是一个继承的特性

    那我们如何用 Python 来表达这种关系呢,下面,我们用一个例子先讲一下,后面再讲规则。

    父亲,儿子和女儿的故事

    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
    
        def __init__(self, money, house):
            self.money = money
            self.house = house
    
        def wealth(self):
            print('父亲给我 %d w, %d 套房子' % (self.money, self.house))
    
    
    class Son(Father):
        '''
            定义一个儿子类,继承自父亲类
        '''
    
        def __init__(self, money, house):
            super().__init__(money, house)
    
    
    class Daughter(Father):
        '''
            定义一个女儿类,继承自父亲类
        '''
    
        def __init__(self, money, house):
            super().__init__(money, house)
    
    
    # 上面定义了一个父亲类,一个儿子类,一个女儿类。
    # 儿子类和女儿类都继承自父亲类,所以,他们就拥有了父亲的财,在编程中也就是拥有了变量和方法。
    son = Son(100, 10)
    daughter = Daughter(200, 5)
    
    # 继承自父亲类,所以自己不定义这个wealth方法,也会自动继承这个方法
    son.wealth()
    daughter.wealth()
    

    通过这个例子,定义了一个父亲类,一个儿子类,一个女儿类。

    儿子类和女儿类都继承自父亲类,所以,他们就拥有了父亲的财,在编程中也就是拥有了变量和方法。

    在上面的例子中发现,我们只要在定义类的时候,在括号()中写上父类的名字,这就是继承了。

    其中,我们也要注意,一个 super() 方法,它的作用是用来继承父类的属性。

    所以,下面我们就大概知道继承的规则怎么写了。

    规则

    class DerivedClassName(Base1, Base2, Base3):
        <statement-1>
        .
        .
        .
        <statement-N>
    

    注意:圆括号中父类的顺序,如果继承的父类中有相同的方法名,而在子类中使用时未指定,python将从左至右查找父类中是否包含方法,在圆括号中有多个类名时,我们称为:多继承。也就是说,我们从多个父类继承了。

    好了,继承我们就说到这里了。下面我们再说说最后一个面向对象的特性:多态

    多态

    在谈到多态时,我们就不得不提到另外一个概念了,这个概念就是重写。例如,我们的容貌有一些地方是会跟父母很相像的,但是,我们也会有很多我们自己的特点。在面向对象编程里面也是这样的,我们会继承父类的特性,但是,在继承的同时,我们也会发生改变的,这就是重写

    那么如何实现重写呢?接着看!

    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
    
        def __init__(self, money, house):
            self.money = money
            self.house = house
    
        def wealth(self):
            print('父亲给我 %d w, %d 套房子' % (self.money, self.house))
    
        def character(self):
            print('我很高!')
    
    
    class Son(Father):
        '''
            定义一个儿子类,继承自父亲类
        '''
    
        def __init__(self, money, house):
            super().__init__(money, house)
    
        def character(self):
            print('我很胖!')
    
    
    class Daughter(Father):
        '''
            定义一个女儿类,继承自父亲类
        '''
    
        def __init__(self, money, house):
            super().__init__(money, house)
    
        def character(self):
            print('我很瘦!')
    
    
    # 上面定义了一个父亲类,一个儿子类,一个女儿类。
    # 儿子类和女儿类都继承自父亲类,所以,他们就拥有了父亲的财,在编程中也就是拥有了变量和方法。
    father = Father(350, 20)
    son = Son(100, 10)
    daughter = Daughter(200, 5)
    
    # 继承自父亲类,所以自己不定义这个wealth方法,也会自动继承这个方法
    son.wealth()
    daughter.wealth()
    
    # 多态
    father.character()
    son.character()
    daughter.character()
    

    在继承的那个例子的基础上,我们又在父亲类中加了一个 character 方法,然后儿子类和女儿类在继承这个方法的同时,还对这个 character 方法进行了修改。

    所以,此时,儿子类和女儿类在调用这两个方法时,显示的内容就不一样了。

    也就是说,当子类和父类都存在相同的 character()方法时,子类的 character() 覆盖了父类的 character(),在代码运行时,会调用子类的 character()

    这样,我们就获得了继承的另一个好处:多态

    多态的好处就是,当我们需要传入更多的子类,例如新增 Teenagers、Adult 等时,我们只需要继承 Father 类型就可以了,而 character()方法既可以直不重写(即使用Father的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Father的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:

    • 对扩展开放(Open for extension):允许子类重写方法函数
    • 对修改封闭(Closed for modification):不重写,直接继承父类方法函数

    ok,面向对象的三大特性就讲到这里。接下来讲讲关于类的其他知识!

    五、类属性与实例属性

    我们在前面的几个例子中,我们发现,在类中我们都定义了一些属性或者说变量。那什么是类属性,什么是实例属性呢?

    类属性是类本身的属性,该类的实例都能调用,而实例属性是某个具体的实例特有的属性,不会影响到类,也不会影响到其他实例。

    1.实例属性

    关于实例属性,我们只要记住下面三点就可以了。

    • __init__(self,...)中初始化
    • 内部调用时都需要加上self.
    • 外部调用时用对象名.属性名调用

    2.类属性

    • 内部类名.类属性名调用
    • 外部既可以用类名.类属性名,又可以用对象名.类属性名来调用,但是,后者是实例属性的值

    下面我们看一个简单例子:

    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
        age = 40  # 定义一个类属性
    
        def __init__(self, money, house):
            self.money = money  # 实例属性
            self.house = house
    
        def wealth(self):
            print('父亲给我 %d w, %d 套房子,类属性: %d ,实例属性:%d' % (self.money, self.house, Father.age, self.age))
    
        def character(self):
            print('我很高!')
    
    
    father = Father(350, 20)
    
    father.house = 4  # 修改实例变量的值,对象名.属性
    Father.age = 41  # 修改类变量的值:类名.属性 或 对象名.属性(但后者修改的值是实例属性)
    father.age = 42 
    
    father.wealth()
    

    下面,我们再用类属性实现一个数值自增

    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
        age = 40  # 定义一个类属性
    
        def __init__(self, money, house):
            self.money = money  # 实例属性
            self.house = house
            Father.age += 1 # 类属性自增
    
    
    father = Father(350, 20)
    print(Father.age)
    
    father2 = Father(350, 20)
    print(Father.age)
    

    但是,在类中,我们不能用 self.age 来自增,这样是不行的,就像在类外不能用对象名.类变量来改变值一样。

    下面,我们用self.age 来自增试试什么效果。

    # -*- coding:utf-8 -*-
    
    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
        age = 40  # 定义一个类属性
    
        def __init__(self, money, house):
            self.money = money  # 实例属性
            self.house = house
            self.age += 1 # 类属性自增
    
    
    
    father = Father(350, 20)
    print(Father.age)
    
    father2 = Father(350, 20)
    print(Father.age)
    

    这段代码就将 Father 改为 self。但输出结果却不改变,如下

    接下来,我们再深入的分析一下,看类属性和实例属性到底有什么联系?

    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
        age = 40  # 定义一个类属性
    
        def __init__(self, money, house):
            self.money = money  # 实例属性
            self.house = house
    
    
    father1 = Father(350, 20)
    father2 = Father(350, 20)
    
    father1.age += 1
    print(father1.age, father2.age, Father.age)
    Father.age += 1
    print(father1.age, father2.age, Father.age) # father2.age 因为这个实例属性不存在,所以找类属性为41
    Father.age += 1
    print(father1.age, father2.age, Father.age)
    

    从这个结果我们可以看出,类属性自增,实例属性是不会跟着自增的,实例属性自增,每个实例属性之间也是独立的,但是,当实例属性不存在时,编译器是会去找类属性的值。

    所以说,在Python中属性的查找机制自下而上的,即首先在实例属性中查找,如果实例属性不存在,再到类属性中查找

    六、访问权限(来自:https://www.cnblogs.com/Lambda721/p/6130213.html

    在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

    但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

    >>> bart = Student('Bart Simpson', 98)
    >>> bart.score
    98
    >>> bart.score = 59
    >>> bart.score
    59
    

    如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__score = score
    
        def print_score(self):
            print('%s: %s' % (self.__name, self.__score))
    

    改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

    >>> bart = Student('Bart Simpson', 98)
    >>> bart.__name
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute '__name'
    

    这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

    但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

    class Student(object):
        ...
    
        def get_name(self):
            return self.__name
    
        def get_score(self):
            return self.__score
    

    如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

    class Student(object):
        ...
    
        def set_score(self, score):
            self.__score = score
    

    你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

    class Student(object):
        ...
    
        def set_score(self, score):
            if 0 <= score <= 100:
                self.__score = score
            else:
                raise ValueError('bad score')
    

    需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

    有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

    双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

    >>> bart._Student__name
    'Bart Simpson'
    

    但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

    总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

    最后注意下面的这种错误写法:

    >>> bart = Student('Bart Simpson', 98)
    >>> bart.get_name()
    'Bart Simpson'
    >>> bart.__name = 'New Name' # 设置__name变量!
    >>> bart.__name
    'New Name'
    

    表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:

    >>> bart.get_name() # get_name()内部返回self.__name
    'Bart Simpson'
    

    例子:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    class Student(object):
    
        def __init__(self, name, score):
            self.__name = name
            self.__score = score
    
        def get_name(self):
            return self.__name
    
        def get_score(self):
            return self.__score
    
        def set_score(self, score):
            if 0 <= score <= 100:
                self.__score = score
            else:
                raise ValueError('bad score')
    
        def get_grade(self):
            if self.__score >= 90:
                return 'A'
            elif self.__score >= 60:
                return 'B'
            else:
                return 'C'
    
    bart = Student('Bart Simpson', 59)
    print('bart.get_name() =', bart.get_name())
    bart.set_score(60)
    print('bart.get_score() =', bart.get_score())
    
    print('DO NOT use bart._Student__name:', bart._Student__name)
    

    终于到最后一个知识点了,这篇文章写的真的久!

    七、类方法与静态方法

    这个知识点就说说,因为没什么好说的。

    普通方法我们都知道怎么定义了。

    1.普通方法

    def fun_name(self,...):
        pass
    

    2.静态方法

    • 通过装饰器 @staticmethod 装饰
    • 不能访问实例属性
    • 参数不能传入self
    • 与类相关但是不依赖类与实例的方法

    3.类方法

    • 通过装饰 @classmethod 修饰
    • 不能访问实例属性
    • 参数必须传入cls
      必须传入cls参数(此类对象和self代表实例对象),并且用此来调用类属性:cls.类属性名。

    最后,再总结一下。

    • 静态方法与类方法都可以通过类或者实例来调用,其两个的特点都是不能够调用实例属性。
    • 静态方法不需要接收参数,使用类名.类属性

    下面,再举一个例子看看。

    # -*- coding:utf-8 -*-
    
    # 定义类
    class Father():
        '''
            定义一个父亲类
        '''
        age = 40  # 定义一个类属性
    
        def __init__(self, money, house):
            self.money = money  # 实例属性
            self.house = house
    
        # 创建普通方法
        def getMoney(self):
            # 类属性的使用通过类名.属性名使用 这是规范
            # 私有属性在类里面使用正常使用
            print('我有:%d w' % (self.money))  # 在方法里面使用实例属性
    
        # 创建一个静态方法
        @staticmethod
        def aa():  # 不需要传递实例
            # 静态方法不能访问实例属性
            # 静态方法只能访问类属性
            print('我:%d 岁' % Father.age)  # 在方法里面使用实例属性
    
        # 类方法
        @classmethod
        def bb(cls, n):  # class  也不是关键字
            # 类方法不能访问实例属性
            cls.age = n
            print('我:%d 岁' % cls.age)  # 就用cls.类属性
    
    
    father1 = Father(350, 20)
    father2 = Father(350, 20)
    
    # 通过对象来调用静态方
    father1.aa()
    # 通过对象来调用类方法
    father1.bb(18)
    
    # 静态方法和类方法的调用,推荐使用类名的方式去调用
    # 通过类名来调用静态方法
    Father.aa()
    # 通过类名来调用类方法
    Father.bb(18)
    

    八、总结

    这一节讲了很多,需要好好消化。

    相关文章

      网友评论

        本文标题:带你学python基础:面向对象编程

        本文链接:https://www.haomeiwen.com/subject/uibdeqtx.html