Python指南:面向对象程序设计

Python 是一种多泛型语言,它没有强制程序员使用某种特定的程序设计风格,而是允许程序员采用过程型、函数型或面向对象的程序设计风格,也可以是这些编程风格的有效组合。对于大多数程序而言,尤其对中等规模或大规模的程序,采用面向对象的程序设计风格提供了很多优势。

1、面向对象方法

接下来将基于使用程序对圆进行描述这一问题,来解释纯过程型程序设计方法存在的问题。用于描述一个圆所需要的最少数据包括圆心坐标(x, y)以及圆的半径,简单的方法是使用一个三元组对圆进行描述,比如:circle = (25, 80, 12)

如果只给出这个表示,那么我们可以理解为x = 25, y = 80, radius = 12, 也可以理解为x = 25, radius = 80, y = 12,二义性是其一个不足之处。另外一个不足在于,只能通过索引位置对其中的值进行存取,还是得提前知道元组中每个位置所代表的含义。

可以使用 namedtuple 解决获取元素顺序的问题(假设distance_from_origin函数已定义):

import collections

Circle = collections.namedtuple('Circle', 'x, y, radius')
circle = Circle(13, 84, 9)
# 假定 distance_from_origin 函数已经定义
distance = distance_from_origin(circle.x, circle.y)

虽然可以解决元素顺序的问题,但是如果半径给定的值为负值,这个数据是不合理的,假设会在 distance_from_origin 函数中进行检查,但这只有在调用的时候才能检查,并不能在创建 Circle 对象的时候进行验证,这就是纯过程型程序设计的弊端。

对象:我们之前见过的 dict、int、str 等数据类型其实是一个类,我们也可以称之为一个 对象。

对象中通常包含属性——方法是可调用的属性,其他属性则是数据。方法其实也是一个函数,只不过其第一个参数是调用该方法的实例本身(self)。在属性名前以两个下划线引导,Python就会阻止无心的访问,因此可以认为是私有的。

面向对象的优势之一是如果我们有一个类,就可以对其进行专用化,这意味着创建一个新类,新类继承原始类的所有属性(数据和方法),通常可以添加或替换原始类中的某些方法,或添加更多的实例变量。

2、自定义类

创建自定义类的两种语法:

class className:
    suit

class className(base_classes):
    suit

2.1 属性与方法

我们从简单的类 Point 开始,该类存放坐标 (x, y),定义于文件 Shape.py 中,其完整实现如下:

import math

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y


    def distance_from_origin(self):
        return math.hypot(self.x, self.y)


    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


    def __repr__(self):
        return "Point({0.x!r}, {0.y!r})".format(self)


    def __str__(self):
        return "({0.x!r}, {0.y!r})".format(self)

Point 类有两个数据属性(self.x和self.y),还包含5个方法(不包括继承来的方法),其中4个属于特殊方法。导入 Shape 模块后, Point 类就可以像其他类一样进行使用,其属性可以直接存取。

01.Point继承关系

对 Point 类的基本使用:

import Shape

a = Shape.Point()
repr(a) # returns: 'Point(0, 0)'

b = Shape.Point(3, 4)
str(b)    # returns: '(3, 4)' 
b.distance_from_origin()    # returns: 5.0

对方法进行调用时,Python 会自动提供第一个参数——这个参数是对对象自身的对象引用(在 C++ 与 Java 中称为 this)。我们必须在参数列表中包含这一参数,根据约定,这一参数称为self。所有的对象属性都必须由self进行限定。

创建一个对象,需要两个步骤:调用特殊方法 __new__() 来创建该对象,之后调用特殊方法 __init__() 对其进行初始化。

2.2 继承与多态

接下来我们新建一个继承自 Point 类的 Circle 类,添加一个额外的属性(radius)以及3个新方法,此外重新实现Point类的几个方法:

class Circle(Point):
    def __init__(self, radius, x=0, y=0):
        super().__init__(x, y)
        self.radius = radius


    def edge_distance_from_origin(self):
        return abs(self.distance_from_origin() - self.radius)


    def area(self):
        return math.pi * (self.radius ** 2)


    def circumference(self):
        return 2 * math.pi * self.radius


    def __eq__(self, other):
        return self.radius == other.radius and self.x == other.x and self.y == other.y


    def __repr__(self):
        return "Circle({0.radius!r}, {0.x!r}, {0.y!r})".format(self)


    def __str__(self):
        return repr(self)

Circle 类的继承关系如下:

02.Circle继承关系

在 __init__() 方法中,使用 super() 来调用基类的 __init__() 方法,从而创建并初始化 self.x 属性与 self.y 属性。下面给出两个使用实例:

p = Shape.Point(28, 45)
c = Shape.Circle(5, 28, 45)
p.distance_from_origin()    # returns: 53.0
p.distance_from_origin()    # returns: 53.0

多态意味着,给定类的任意对象在使用时都可以看做该类的任意某个基类的对象,这也是为什么在创建子类时只需要实现我们需要的附加方法,必须重新实现的也只是那些需要替代的方法。

2.3 使用特性进行属性存取控制

property() 修饰器函数是一个内置函数,至多接受4个参数:一个获取者函数,一个设置者函数,一个删除者函数以及一个 docstring

为将属性转换为可读/可写的特性,我们必须创建一个私有属性,其中实际上存放了数据并提供获取者方法与设置者方法。接下来我们对 Circle 类的 radius 属性进行验证。

class Circle(Point):
    def __init__(self, radius, x=0, y=0):
        super().__init__(x, y)
        self.radius = radius

    @property
    def radius(self):
        """ The circle's radius

        >>>circle = Circle(-2)
        Traceback(most recent call last):
        ...
        AssertionError:radius must be nonzero and non-negative
        >>>circle = Circle(4)
        >>>circle.radius = -1
        Traceback(most recent call last):
        ...
        AssertionError:radius must be nonzero and non-negative
        >>>circle.radius = 6
        """
        return self.__radius

    @radius.setter
    def radius(self, radius):
        assert radius > 0, "radius must be nonzero and non-negative"
        self.__radius = radius

    ...

测试:

import Shape
circle = Shape.Circle(-4)

执行之后会报错:

AssertionError: radius must be nonzero and non-negative

这样就会在创建 Circle 对象时进行验证了。

本文介绍了 Python 对面向对象程序设计的基础知识。展示了纯过程型程序设计的一些不足,并使用面向对象来克服这些不足。

原文发布于微信公众号 - C与Python实战(CPythonPractice)

原文发表时间:2018-05-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Brian

C++11基础学习系列二

---- 概述 在C++11基础学习系列一中介绍一些c++11一些基础知识。基础学习系列二进一步讲解C++11. string string不可思议,在C++中...

2635
来自专栏机器学习从入门到成神

C/C++学习之路(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

3572
来自专栏LeetCode

LeetCode 169. Majority Element

思路:数组中有一个数字的出现次数超过一半,也就是说这个数字的出现次数比其他的所有的数字的出现次数之和还要多。因此我们可以考虑遍历数组的时候保存两个值,一个是数组...

1311
来自专栏C/C++基础

C++11——引入的新关键字

auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用。在C++11中被赋予了新的含义和作用,用于类型推断。

1184
来自专栏java一日一条

java提高篇之详解内部类

内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二)。

992
来自专栏用户3030674的专栏

java接口

接口中常量的修饰关键字:public,static,final(常量) 函数的修饰关键字:public,abstract 如果没有写全,系统在编译时会自动加上 ...

1382
来自专栏纯洁的微笑

一个高频面试题,考考大家对 Java String 常量池的理解。

作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串...

2042
来自专栏鸿的学习笔记

Python和Scala的函数定义

之前的文章我们简单地看了下Scala和Python的变量定义,再来看看如何将代码块组织在一起变成一个函数吧。

752
来自专栏机器学习从入门到成神

关于Java中==与equals的解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

730
来自专栏Vamei实验室

Java基础11 对象引用

我们之前一直在使用“对象”这个概念,但没有探讨对象在内存中的具体存储方式。这方面的讨论将引出“对象引用”(object reference)这一重要概念。 ...

2098

扫码关注云+社区

领取腾讯云代金券