Python 进阶_OOP 面向对象编程_实例属性和方法-阿里云开发者社区

开发者社区> 范桂飓> 正文

Python 进阶_OOP 面向对象编程_实例属性和方法

简介: 目录 目录 构造器和解构器 构造器 __init__ 真构造器 __new__ 解构器 __del__ 实例方法 Python 中的 抽象方法 实例属性 查看实例属性 实例属性和类属性的区别 访问不可变类属性 访问可变类属性 构造器和解构器 构造器 __init__() 类函数 __init__() 是 Python 类中预定义的方法,需要被重载才会生效。
+关注继续查看

目录

构造器和解构器

构造器 __init__()

类函数 __init__() 是 Python 类中预定义的方法,需要被重载才会生效。以双下划线 “__” 开头和结尾, 在 Python 中使用这种命名方式的方法会被理解为是一种特殊方法, Python 的特殊方法功能非常丰富, 种类也很多, 在声明变量名的时候要注意不要和特殊方法重名.

通常,构造器用于在 实例化对象被创建后,返回这个实例之前 的这段时间里,执行一些特定的任务或设置。例如:初始化实例对象属性(以 self 关键字调用的属性)。它在实例化一个新的对象时被自动调用,所以除了初始化实例属性之外,还常被用于运行一些初步的诊断代码。其调用的具体步骤:
1. 创建类的实例化对象
2. Python 解析器检查类是否实现了构造器
3. 若有,则执行构造器的实现,且要求创建对象的时候传入对应的实参。实例对象会传递给第一个形参 self 。
4. 若没有,则直接返回实例对象

一般建议在构造器中设定需要初始化的实例属性,而且构造器应该返回 None,即没有 return 语句。

真·构造器 __new__()

__init__() 相比 __new__() 才是真正的构造器,实际上,在 Python 解析器中是先调用了 __new__() 生成一个实例,再将该实例对象传入 __init__() 实现初始化操作。但 __new__() 很少需要我们去重载,一般只有在派生了不可变类型的子类后需要重载,EG. 派生 String/Int/Tuple 等

为什么说 __new__() 是真·构造器呢?
因为这个特殊的类方法是真的返回了一个类的实例对象,而不像 __init__() 是传入了一个实例化对象。

EXAMPLE:不可表类型的派生

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2)) #因为 __new__ 是一个类方法,所以我们要显式的传递一个类对象

类 RoundFloat 是类 float 的子类,我们通过重载父类的 __new__() 构造器来定制一个新的不可变类型(Python 2.2之后将类和类型统一了,所以可以继承 Python 的内置数据类型)。当实例化 RoundFloat 的对象时,实际上是实例化了Python 内置数据类型 Float 的对象,并对这个对象做了一些定制化的操作(round(val, 2))。
NOTE:即便我们也可以通过重载 __init__() 来实现这个结果,但这里却不能这么做。因为如果 __new__() 没有被重载的话,仍会默认调用父类 Float 的构造器,创建 Float 类型的对象,而不是创建现在的 RoundFloat 类型对象。这也是两者的本质区别。

解构器 __del__()

解构器 __del__() 会在实例对象被垃圾回收之前被 Python 解析器调用一次(只会调用一次,之后就回收实例对象)。解构器是实例对象释放资源前提供特殊处理功能的方法。一般我们很少会用到,但解构器是检查对象引用状态的好方法,加入对象仍然存在引用时,解构器是不会执行的。

注意不要随便重载解构器。

实例方法

实例方法的基本特征就是要传入一个实例对象作为实参。
在 Python 看来,实例方法严格来说属于类属性的一种,实例方法只能通过实例化对象来调用,无法通过类来直接调用。

In [59]: class AClass(object):
    ...:     LOG = 'Define a class'
    ...:     def my_no_actioon_method(self):
    ...:         pass
    ...:

In [60]: dir(AClass)
Out[60]:
['LOG',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_no_actioon_method']     # 通过 dir() 可以找到实例方法存在于类的属性列表中。

In [62]: AClass.my_no_actioon_method()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-62-33bc36ae78f7> in <module>()
----> 1 AClass.my_no_actioon_method()

TypeError: unbound method my_no_actioon_method() must be called with AClass instance as first argument (got nothing instead)

如果类直接调用实例方法的话,会触发 TypeError,因为实例方法需要绑定一个实例化对象,这就是 self 存在的意义。所有的含有 self 关键字的属性和方法都只能通过类的实例化对象来调用。所以,一般而言类中定义的实例方法都需要含有 self 形参,当然也可以通过别的方式来解除这种约束,这个我们后面会说到。

In [63]: a_object = AClass()

In [64]: a_object.my_no_actioon_method() #类方法是通过局点标识符来与它的实例化对象绑定的

Python 中的 “抽象方法”

Python 并不支持 Java 中的抽象方法,但可以通过在父类中 ``raise NotImplememtedError 来实现同样的效果。抽象方法的意义在于可以强制的规范在子类中必需定义与父类中方法同名的子类成员方法。

In [74]: class AClass(object):
    ...:     LOG = 'Define a class'
    ...:     def my_method(self):
    ...:         raise NotImplementedError()
    ...:
    ...:

In [75]: class BClass(AClass):
    ...:     pass
    ...:

In [76]: b_object = BClass()

In [77]: dir(b_object)
Out[77]:
['LOG',
 '__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'my_method']

In [78]: b_object.my_method()
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-78-49e04830d861> in <module>()
----> 1 b_object.my_method()

<ipython-input-74-d6213dfecb1b> in my_method(self)
      2     LOG = 'Define a class'
      3     def my_method(self):
----> 4         raise NotImplementedError()
      5
      6

NotImplementedError:

所以通过这种方式,程序员们只能乖乖在子类的重载这个同名方法了,这对程序的规范做了很好的约束。

In [79]: class BClass(AClass):
    ...:     def my_method(self):
    ...:         print "overload the function."
    ...:
    ...:

In [80]: b_object = BClass()

In [81]: b_object.my_method()
overload the function.

实例属性

在类定义中,通过 self 关键字来调用的属性,即属于实例对象的属性,类对象是不能调用的。类属性的创建实际上是通过调用实例方法 __setattr__ 来完成的。x.__setattr__('name', value) <==> x.name = value,除此之外还有内建函数 setattr(),setattr(object, name, value) <==> object.name = value

Python 可以在任意的时间内创建一个命名空间,所以我们可以在创建了实例对象之后定义实例属性,也可以在定义类的时候创建实例属性。后者创建实例属性最重要的方式就是构造器 __init__()

In [98]: class AClass(object):
    ...:     def __init__(self, name, age):
    ...:         self.name = name
    ...:         self.age = age
    ...:

In [99]: a_object = AClass('JMILKFAN', 24)

In [101]: a_object.name
Out[101]: 'JMILKFAN'

In [102]: a_object.age
Out[102]: 24

In [103]: a_object.sex = 'man'

In [104]:  a_object.sex
Out[104]: 'man'

当然,我们除了可以在构造器中创建实例属性之外,还可以在类体的任意地方创建,当我们并不推荐这么做。

查看实例属性

查看实例属性和查看类属性是一样的,可以通过 dir()instance.__dict__ 来查看。
EXAMPLE

In [105]: dir(a_object)
Out[105]:
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'age',
 'name',
 'sex']

In [106]: a_object.__dict__
Out[106]: {'age': 24, 'name': 'JMILKFAN', 'sex': 'man'}

再与查看类属性做一个对比:

In [108]: dir(AClass)
Out[108]:
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

In [109]: AClass.__dict__
Out[109]:
dict_proxy({'__dict__': <attribute '__dict__' of 'AClass' objects>,
            '__doc__': None,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref__' of 'AClass' objects>})

NOTE: 对于类或实力对象来说 __dict__ 都是可以手动修改的,但是并不建议我们手动去修改这个属性字典。

我们还能够通过内置方法 getattr() 或实例方法 __getattribute__() 来查看

In [119]: getattr(a_object, 'name')
Out[119]: 'JMILKFAN'

In [122]: a_object.__getattribute__('name')
Out[122]: 'JMILKFAN'

实例属性和类属性的区别

类属性与实例无关,类属性的值不会因为实例化对象而改变,除非在实例对象中 显式 的改变了 可变类属性 的值。类属型的值就像是静态成员那样被使用,我们一般采用 class.attr 的方式来调用类属性 attr,除此我们也能够使用实例对象来调用类属性 instance.attr,但前提是实例对象中没有同名的实例属性,否则 instance.attr 中的 attr 就是一个实例属性,返回实例属性的值。相反,实例属性只能由类实例对象来调用。

类属性类似于一个静态属性,在定义类属性的时候会在内存开辟静态空间,用于存放。而且这个静态空间是类的所有实例对象都可以访问的,所以类属性是被所有的由该类实例化的对象共享的。

In [150]: class AClass(object):
     ...:     version = 1.0
     ...:

In [151]: a_object = AClass()

In [152]: a_object.version
Out[152]: 1.0

In [153]: AClass.version += 1

In [154]: a_object.version
Out[154]: 2.0

类属性的改变会被实例对象感知。

访问不可变类属性

对于不可变类属性(int/str/tuple)而言,实例对象是无法改变类属性的值得。但如果类属性是可变数据类型,那么实例对象就可以显式的修改其的值。

In [124]: class AClass(object):
     ...:     version = 1.0
     ...:

In [125]: a_object = AClass()

In [126]: AClass.version
Out[126]: 1.0

In [127]: a_object.version
Out[127]: 1.0

In [128]: AClass.version = 1.1

In [129]: a_object.version
Out[129]: 1.1

In [130]: AClass.version
Out[130]: 1.1

In [131]: a_object.version
Out[131]: 1.1

In [132]: b_object = AClass()

In [133]: b_object.version
Out[133]: 1.1

In [134]: b_object.version = 1.2   # 这里并不没有修改类属性,而是创建了一个新的实例属性

In [135]: AClass.version
Out[135]: 1.1

In [136]: a_object.version
Out[136]: 1.1

从上面的例子可以看出,实例对象可以引用类属性(前提是实例对象没有同名的实例属性),却不能改变类属性,而是创建了一个新的实例属性并覆盖了同名的类属性。

访问可变类属性

In [143]: class AClass(object):
     ...:     aDict = {'name': 'jmilkfan'}
     ...:

In [144]: a_object = AClass()

In [146]: AClass.aDict
Out[146]: {'name': 'jmilkfan'}

In [147]: a_object.aDict
Out[147]: {'name': 'jmilkfan'}

In [148]: a_object.aDict['name'] = 'fanguiju'

In [149]: AClass.aDict
Out[149]: {'name': 'fanguiju'}

实例对象可以修改类属性的本质是,实例对象在没有创建一个新的实例属性的基础上进行了修改。

注意:一般认为通过实例对象来修改类属性是危险的,会有很多潜在的风险。所以建议使用类名来修改类属性,而不是实例对象名。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
python应用领域分析
python应用领域分析
8 0
Django框架介绍与安装
软件框架就是为实现或完成某种软件开发时,提供了一些基础的软件产品, 框架的功能类似于基础设施,提供并实现最为基础的软件架构和体系 通常情况下我们依据框架来实现更为复杂的业务程序开发 二个字,框架就是程序的骨架
5 0
ResNet实战:tensorflow2.X版本,ResNet50图像分类任务(大数据集)
本例提取了猫狗大战数据集中的部分数据做数据集,演示tensorflow2.X版本如何使用Keras实现图像分类,分类的模型使用ResNet50。本文实现的算法有一下几个特点: 1、自定义了图片加载方式,更加灵活高效,不用将图片一次性加载到内存中,节省内存,适合大规模数据集。 2、加载模型的预训练权重,训练时间更短。 3、数据增强选用albumentations。
7 0
想查看微信好友撤回的消息?Python帮你搞定
想查看微信好友撤回的消息?Python帮你搞定
14 0
InceptionV3实战:tensorflow2.X版本,InceptionV3图像分类任务(大数据集)
本例提取了猫狗大战数据集中的部分数据做数据集,演示tensorflow2.X版本如何使用Keras实现图像分类,分类的模型使用InceptionV3。本文实现的算法有一下几个特点: 1、自定义了图片加载方式,更加灵活高效,不用将图片一次性加载到内存中,节省内存,适合大规模数据集。 2、加载模型的预训练权重,训练时间更短。 3、数据增强选用albumentations。
6 0
ResNet实战:tensorflow2.X版本,ResNet50图像分类任务(小数据集)
本例提取了植物幼苗数据集中的部分数据做数据集,数据集共有12种类别,今天我和大家一起实现tensorflow2.X版本图像分类任务,分类的模型使用ResNet50。 通过这篇文章你可以学到: 1、如何加载图片数据,并处理数据。 2、如果将标签转为onehot编码 3、如何使用数据增强。 4、如何使用mixup。 5、如何切分数据集。 6、如何加载预训练模型。
4 0
python垃圾回收
吃太多,总会变胖,Python也是这样。当Python中的对象越来越多,它们将占据越来越大的内存。不过你不用太担心Python的体形,它会乖巧的在适当的时候“减肥”,启动垃圾回收(garbage collection),将没用的对象清除。在许多语言中都有垃圾回收机制,比如Java。尽管最终目的都是塑造苗条的提醒,但不同语言的减肥方案有很大的差异 。
8 0
centos 安装python2.7版本
centos 安装python2.7版本
11 0
9 个爱不释手的 JSON 工具
众所周知,JSON让开发人员易于使用,又让机器易于解析和生成。 JSON吸引了工具构建者的注意,它们开发了用于重新格式化、验证和解析JSON的众多工具,这不足为奇。这些工具既有在Web浏览器中运行的在线实用程序,又有面向代码编辑器和IDE的插件,比如Visual Studio Code和Eclipse。
5 0
+关注
范桂飓
OpenStack Developer, Opensource Lover :- )
263
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载