python面向对象编程基础

面向对象

把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。

类和对象

简单地说,class是object的模板,而object是class的实例,进一步讲,其实class是抽象的概念,而object是具体的恭喜。在面向对象的编程中,一切都可对象,对象都有属性和行为,每个对象都是独一的,而且每个对象一定属于某个类。当我们把一大堆拥有共性的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出class(类)。

定义类(class)并创建和使用对象

# 定义类
class student(object):
    # __init__是一个特殊方法,用于在创建对象时进行初始化操作
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def study(self, course_name):
        print('%s正在学习%s.' % (self.name, course_name))

    def watch_movie(self):
        if self.age < 18:
            print('%s_in' % self.name)
        else:
            print('%s_out.' % self.name)


# 创建和使用对象
def main():
    # 创建对象并传参
    st1 = student('张三', 18)
    # 向对象发消息
    st1.study('Python程序设计')
    st1.watch_movie()


if __name__ == '__main__':
    main()

访问可见性

在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头。

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    # AttributeError: 'Test' object has no attribute '__bar'
    test.__bar()
    # AttributeError: 'Test' object has no attribute '__foo'
    print(test.__foo)


if __name__ == "__main__":
    main()

但是,python只是给私有的属性和方法活了一个名字来妨碍对他们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们。

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    test._Test__bar()
    print(test._Test__foo)


if __name__ == "__main__":
    main()

在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。

@property

之前我们讨论过Python中属性和方法访问权限的问题,虽然我们不建议将属性设置为私有的,但是如果直接将属性暴露给外界也是有问题的,比如我们没有办法检查赋给属性的值是否有效。我们之前的建议是将属性命名以单下划线开头,通过这种方式来暗示属性是受保护的,不建议外界直接访问,那么如果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性的访问既安全又方便。

getter作用:在获取某个属性值(面积)之前想要做别的事情(获取长宽),就给这个属性添加 getter。
setter作用:在给属性(面积)赋值之前想要做别的事情(不许修改面积),就给这个属性添加 setter。

getter
第一步:在需要添加getter的属性名前加_
第二步:定义getter对应的函数(1.需要@property装饰器 2.函数名就是不带_的属性名 3.函数需要一个返回值)
第三步:获取属性的值通过: 对象.不带_属性名 (本质就是在调用getter对应的函数,取到属性值就是函数的返回值)

setter
如果想要给属性添加 setter 必须先给属性添加 getter
第一步:添加 getter
第二步:定义 setter 对应的函数 (1.需要 @getter函数名.setter 装饰器 2.函数名就是不带_的属性名 3.需要一个参数不需要返回值,这个参数就是尝试给属性赋的值)
第三步:给属性赋值:对象.不带_属性名 = 值 (本质就是在调用setter对应的函数)

被 @property 装饰的方法是获取属性值的方法,被装饰方法的名字会被用做 属性名。
被 @属性名.setter 装饰的方法是设置属性值的方法。
被 @属性名.deleter 装饰的方法是删除属性值的方法。

# 定义类时,不加object为经典类,加了object为新式类
# 在python3.6之后两者没有区别,都遵循广度优先原则
class person(object):
    def __init__(self, name, age):
        self._name = name
        self._age = age

    # getter
    @property
    def name(self):
        return self._name

    # getter
    @property
    def age(self):
        return self._age

    # setter
    @age.setter
    def age(self, age):
        self._age = age

        def play(self):
            if self._age <= 16:
                print('%s正在玩飞行棋.' % self._name)
            else:
                print('%s正在玩斗地主.' % self._name)


def main():
    person = person('王大锤', 12)
    person.play()
    person.age = 22
    person.play()
    # person.name = '白元芳'  # AttributeError: can't set attribute

    if __name__ == '__main__':
        main()

_ _ slots_ _

__slots__属性的作用是声明并限定类成员,并拒绝类创建__dict__和__weakref__属性以节约内存空间。
__slots__在继承中有两种表现:

子类未声明__slots__时,不继承父类的__slots__,即此时子类实例可以随意赋值属性
子类声明__slots__时,继承父类的__slots__,即此时子类的__slots__为其自身+父类的__slots__。

class person(object):
    # 限定person只能绑定_name,_age,_gender属性
    __slots__ = ('_name', '_age', '_gender')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        if self._age <= 16:
            print('%s正在玩飞行棋.' % self._name)
        else:
            print('%s正在玩斗地主.' % self._name)


def main():
    person = person('王大锤', 22)
    person.play()
    person._gender = '男'
    # AttributeError: 'Person' object has no attribute '_is_gay'
    # person._is_gay = True


静态方法和类方法

之前,我们在类中顶一个方法都是对象方法,也就是说这些方法都是发送给对象的消息。实际上,我们卸载类中的方法并不需要都是对象方法,比如,我们定义一个三角形类,通过传入三边长来构造,并提供计算周长和面积的方法,但是传入的三条边长未必能构造出三角形对象,因此我们可以先写一个方法来验证三条边长是否可以构建三角形,这个方法显然就不是对象方法,因为调用这个方法时三角形对象还没创建,所以这个方法属于三角形类而不属于三角形对象。(@staticmethod)

from math import sqrt


class Triangle(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    @staticmethod
    def is_valid(a, b, c):
        return a + b > c and b + c > a and a + c > b

    def perimeter(self):
        return self._a + self._b + self._c

    def area(self):
        half = self.perimeter() / 2
        return sqrt(half * (half - self._a) *
                    (half - self._b) * (half - self._c))


def main():
    a, b, c = 3, 4, 5
    # 静态方法和类方法都是通过给类发消息来调用
    if Triangle.is_valid(a, b, c):
        t = Triangle(a, b, c)
        print(t.perimeter())
    else:
        print('无法构成三角形.')


if __name__ == '__main__':
    main()

和静态方法比较类似,python还可以在类中定义类方法,类方法的第一个参数cls代表是当前类相关的信息的对象(类本身就是一个对象,有的地方也成为了class的元数据对象),通过这个参数我们可以获得和类相关的信息并且可以创建出类的对象。

from time import time, localtime, sleep


class Clock(object):
    """数字时钟"""

    def __init__(self, hour=0, minute=0, second=0):
        self._hour = hour
        self._minute = minute
        self._second = second

    @classmethod
    def now(cls):
        ctime = localtime(time())
        return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

    def run(self):
        """走字"""
        self._second += 1
        if self._second == 60:
            self._second = 0
            self._minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """显示时间"""
        return '%02d:%02d:%02d' % \
               (self._hour, self._minute, self._second)


def main():
    # 通过类方法创建对象并获取系统时间
    clock = Clock.now()
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == '__main__':
    main()

类之间的关系

类之间的关系有三种:is-a/has-a/use-a关系。
is-a关系也叫继承或泛化,比如学生和人的关系。
has-a关系通常称之为关联,比如部门和员工的关系。
use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

继承和多态

python可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来。提供继承信息的我们成为父类,得到继承信息的是子类,自理除了继承父类的属性和方法,还可以定义自己特有的属性和方法,所有子类比父类拥有更过的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这就是面向对象编程的一个常见的行为,里氏替换原则。

class person(object):

    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, age):
        self._age = age

    def play(self):
        print('%s正在愉快的玩耍.' % self._name)

    def watch_av(self):
        if self._age >= 18:
            print('%s正在' % self._name)
        else:
            print('%s只能' % self._name)


class student(person):
    def __init__(self, name, age, grade):
    	# 需要继承父类构造函数中的内容,且子类需要在父类的基础上补充时,使用super().__init__()方法。
        super().__init__(name, age)
        self._grade = grade

    @property
    def grade(self):
        return self._grade

    @grade.setter
    def grade(self, grade):
        self._grade = grade

    def study(self, course):
        print('%s的%s正在学习%s.' % (self._grade, self._name, course))

子类在继承父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作是方法重写。通过override重写我们可以让父类的同一行为在子类中拥有不同的版本,我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这就是多态(poly-morphism)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZRX_GIS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值