好文档 - 专业文书写作范文服务资料分享网站

Python项目开发实战

天下 分享 时间: 加入收藏 我要投稿 点赞

Python项目开发实战 1.5.2 定义并使用类和对象 Python使用一种传统的、基于类的方法支持面向对象编程。Python类支持多继承和操作符重载(但不支持方法重载),以及常用的封装机制和消息传递。尽管一些命名习惯可以为特性提供一层微弱的保护并建议客户端不应该在何时直接使用特性,但是Python类不直接实现数据隐藏。Python支持类方法和类数据,以及属性(properties)和插槽(slots)的概念。类拥有构造函数(__new__())和初始化函数(__init__())。尽管并不保证被调用,Python也有析构函数机制(__del__())。对于定义在类内部的方法和数据,类也充当它们的命名空间。 对象是类的实例。尽管不常用,但是实例在创建之后可以添加自己的属性。 类的定义使用class关键字,后面跟着类名和用括号括起来的超类的列表。类定义包含一些类数据和方法定义。一个类方法定义把指向调用实例的引用作为第一个参数,通常被称为self。一个简单的类定义看起来如下所示: class MyClass(object): instance_count = 0 def __init__(self, value): self.__value = value MyClass.instance_count += 1 print(\def aMethod(self, aValue): self.__value *= aValue def __str__(self): return \def __del__(self): MyClass.instance_count -= 1 类名通常以一个大写字母开头。在Python 3中,除非特别声明,超类总是object。所以前面示例使用object作为超类实际上是多余的。由于没有出现在任何类方法中,因此instance_count数据项是一个类特性。__init__()函数是一个初始化器(在Python中,除非是从一个内置类继承,构造函数很少被用到)。它设置了实例变量self.__value的值,添加之前定义的类变量instance_count,然后打印一条信息。value前的双下划线表明它实际上是一个私有数据,不应该被直接使用。在对象被构建之后,__init__()方法立刻被Python自动调用。实例方法aMethod()修改在_init__()方法中创建的实例特性。__str__()方法是一个用来返回一个格式化字符串的特殊方法。例如,传递到打印函数的对象会以一种有意义的方法被打印出来。当对象被销毁时,析构函数__del__()减少类变量instance_count。 可以如下所示创建一个类的实例: myInstance = MyClass(42) 该操作在内存中创建了一个实例,然后把新实例作为self,42作为value来调用MyClass.__init__()。 可以如下所示使用点符号调用aMethod()方法: myInstance.aMethod(66) 28 第1章 Python核心知识回顾 这可以被转换成更加显式的调用: MyClass.aMethod(myInstance, 66) 并且产生想要的行为。这样,__value特性的值被调整了。 如果打印实例,则可以看到__str__()方法起的作用: print(myInstance) 该操作会打印下面这条信息: A MyClass instance with value: 2772 也可以在创建或销毁一个实例之前和之后打印instance_count的值: print(MyClass.instance_count) inst = MyClass(44) print(MyClass.instance_count) del(inst) print(MyClass.instance_count) 这会显示计数被增加然后又被减少(在垃圾回收过程中,在析构器调用之前可能会有一点小小的延迟,这应该只是一小会儿)。 __init__()、__del__()和__str__()方法不是仅有的特殊方法。它们中的一些都使用双下划线来标识(它们有时也被称为双下划线(dunder)方法)。操作符重载是通过一组这些特殊的方法来支持的,包括__add__()、__sub__()、__mul__()和__div__()等。其他方法为实现Python协议,比如迭代或上下文管理,做了准备。可以在自己的类中重写这些方法。你永远都不要定义自己的以双下划线开始的函数,否则Python未来的改进和增强可能会破坏你的代码。 可以在子类中重写方法,并且新的定义可以通过使用super()函数触发被继承版本的方法。如下所示: class SubClass(Parent): def __init__(self, aValue): super().__init__(aValue) 对super().__init__()的调用会转换成对父类的__init__()方法的调用。使用super()可以避免问题,尤其是在多继承的情况下。在多继承的情况下,一个类可能被继承多次,但是通常你不想它被多次初始化。 插槽是一种节省内存的设备。可以使用__slots__这个特殊特性并提供一个对象特性名 29 注意:相比于Python 2,在Python 3中使用super()被大大简化了。Python 2中的super()看起来就像super(SubClass, self).__init__(aValue),但这种方式用起来非常不直观。 Python项目开发实战 列表来激活它们。通常,__slots__是一个不成熟的优化。只有当有一个明确的、已知的需求时,才应该使用它。 属性是另一个数据特性中可用的特性。即使没有使用常用的方法语法,它们也会强制通过一组方法访问特性。这样就允许你让这个特性只读(或者甚至是只写)。可以通过一个示例透彻地理解它。这个示例创建一个Circle类,包括radius特性和area()方法。由于你希望radius的值永远为正,因此你不希望用户可以直接改变它的值,因为它们可能会传一个负值。即使area()被实现为一个方法,你也希望它看起来像一个只读的数据特性。可以通过把它作为radius和area属性来同时达到这两个目的。 试一试:在类中创建属性(testCircle.py) 在这个“试一试”中,首先会创建一个简单的Circle1类。它只有一个特性和两个可调用的方法:setRadius()和area()。然后,会创建第二个类Circle2。它把radius和area变成属性。最后,你会看到如何使用属性来简化使用客户端代码中的类。 (1) 启动你最喜欢的编程编辑器或IDE,创建一个名为testCircle.py的新文件(或者加载从本书网站上下载的文件)。 (2) 输入下面的代码: class Circle1: def __init__(self, radius): self.__radius = radius def setRadius(self,newValue): if newValue >= 0: self.__radius = newValue else: raise ValueError(\def area(self): return 3.14159 * (self.__radius ** 2) class Circle2: def __init__(self, radius): self.__radius = radius def __setRadius(self, newValue): if newValue >= 0: self.__radius = newValue else: raise ValueError(\radius = property(None, __setRadius) @property def area(self): return 3.14159 * (self.__radius ** 2) (3) 保存代码。 (4) 启动Python解释器,输入下面代码来使用Circle1: >>> import testCircle as tc 30 第1章 Python核心知识回顾 >>> c1 = tc.Circle1(42) >>> c1.area() 5541.76476 >>> print(c1.__radius) Traceback (most recent call last): File \AttributeError: 'Circle1' object has no attribute '__radius' >>> c1.setRadius(66) >>> c1.area() 13684.766039999999 >>> c1.setRadius(‐4) Traceback (most recent call last): File \File \else: raise ValueError(\ValueError: Value must be positive (5) 用下面的代码来试试Circle2: >>> c2 = Circle2(42) >>> c2.area 5541.76476 >>> print(c2.radius) Traceback (most recent call last): File \AttributeError: unreadable attribute >>> c2.radius = 12 >>> c2.area 452.38896 >>> c2.radius = ‐4 Traceback (most recent call last): File \File \else: raise ValueError(\ValueError: Value must be positive >>> 示例说明 在文件testCircle.py中创建了两个类。第一个类Circle1实现了你想要强制用户使用setRadius()方法修改半径值的目的。通过在特性self.__radius前面加上双下划线前缀实现了这一点。这是Python所用的私有化方法。然后你创建了setRadius()方法,它会在使用给定值之前检验它的有效性。如果值是负的,它会抛出一个错误。你也提供了一个area()方法,这样用户可以使用通常的方法调用技术来计算面积。 第二个类Circle2使用相当不同的方式处理这些需求。它使用Python中的属性定义特性创建了一个只写特性radius。它也创建了一个作为只读特性的area方法。就像在解释器中练习使用类看到的那样,用户在使用Circle2时的代码更加直观。这关键在于你调用的property()类型函数,如下所示: 31 Python项目开发实战 radius = property(None, __setRadius) 这行代码接受的参数是一组函数,它们用来执行读、写和删除操作(以及文档字符串)。每个函数的默认值都是None。这里创建了radius属性,设置读函数为None,但是写函数为__setRadius()方法(现在是私有的)。其他参数值都被默认地设置为None。这样做的结果是:用户可以把它当成一个公共的数据特性访问,但是当给radius赋值时,就必须调用__setRadius()方法来保护这个值。任何试图读取(或删除)这个特性的操作都会被忽略,因为这些操作实际上操作的都是None。 area属性稍有差别,它使用了一个Python属性修饰符(@property)。属性修饰符是一种创建只读属性的快捷方式。这是属性的一种常用方式。 下面介绍交互式会话,我们创建了一个Circle1实例,并且使用area()方法打印出面积。然后试图通过访问__radius直接打印半径,但是Python假称它没有这个特性(由于双下划线的私有设置)并且抛出一个AttributeError异常。但是当使用setRadius()方法时,一切就很顺利。再次打印面积时,修改已经生效了。最后,我们试图把一个负值赋给半径,正如我们所料,setRadius()方法抛出了一个ValueError异常。这个异常还带有一个自定义的错误消息:“Value must be positive”。 在使用Circle2的会话中,我们可以看到代码变得更加简洁。我们仅使用area属性名就可以获得面积,并且也可以给radius属性名赋值。当试图将一个负值赋给它时,赋值方法抛出了一个ValueError异常。尽管错误消息会稍有不同,但直接打印半径又产生了一个AttributeError异常。 属性需要程序员这边做少量的额外工作,但却可以极大地简化类的使用。 在这一节中,我们已经看到了如何使用函数和类来扩展Python的功能。在下一节中,你将看到如何把这些扩展封装在可复用的模块和包中。 1.6 创建和使用模块和包 在大多数编程环境中,模块对于正式的编程任务来说是非常重要的。它们允许程序被拆分为易于管理的块并且提供项目间代码复用的机制。在Python中,模块只是简单的以.py结尾的源文件,它们位于任何Python可以找到的地方。在实际中,这意味着文件必须位于当前的工作目录或在sys.path变量列出的文件夹中。通过在系统的环境变量PYTHONPATH中指定你自己的文件夹或在运行时动态地添加,可以把它们添加到这个路径中。 尽管模块提供了一个很有用的包装方法来封装小数量的源文件用于复用,但是它们并不能完全满足更大的项目,例如,GUI框架或数学函数库。对于这些,Python提供了包(package)的概念。一个包本质上是包含许多模块的文件夹。唯一的要求是,文件夹需要包含一个名为__init__.py的文件,这个文件可以为空。对于用户来说,包看起来特别类似于模块,包中的子模块看起来就像模块的特性。 32

Python项目开发实战

Python项目开发实战1.5.2定义并使用类和对象Python使用一种传统的、基于类的方法支持面向对象编程。Python类支持多继承和操作符重载(但不支持方法重载),以及常用的封装机制和消息传递。尽管一些命名习惯可以为特性提供一层微弱的保护并建议客户端不应该在何时直接使用特性,但是Python类不直接实现数据隐藏。Python支持类方法和类数据,以及属性(properties)和
推荐度:
点击下载文档文档为doc格式
3ewa54p63e9y6yn8bcwn
领取福利

微信扫码领取福利

微信扫码分享