目录

构造器和解构器

构造器 __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 进阶_OOP 面向对象编程_实例属性和方法的更多相关文章

  1. Python 进阶_OOP 面向对象编程_类属性和方法

    目录 目录 类属性 调用类属性 查看类属性 特殊的类属性 类方法 真构造器 __new__ 类属性 在理解类属性之前要先搞清楚 实例属性 和 函数属性 之间的区别: 1. 实例属性:指的是实例化类对象 ...

  2. Python 进阶_OOP 面向对象编程_组合与继承

    #目录 前言 组合 派生 通过继承来覆盖重载方法 最常用的重载场景实例方法的重载 从标准类中派生类方法的重载 前言 我们定义一个类是希望能够把类当成模块来使用,并把类嵌入到我们的应用代码中,与其他的数 ...

  3. Python 进阶_OOP 面向对象编程_类和继承

    目录 目录 类 最简单的类 类方法 构造器 __init__ 创建一个类 实例化一个对象 调用实例的方法和属性 创建子类 使用 super 来调用父类的构造器 实例化子类对象 调用子类的属性和方法 类 ...

  4. Python 进阶_OOP 面向对象编程_静态方法和类方法

    目录 目录 静态方法 类方法 使用函数修饰符来声明静态方法和类方法 静态方法 静态方法仅是类中的函数, 不需要绑定实例, 也就是说静态方法的定义不需要传入 self 参数. 静态方法不属于类的某一个实 ...

  5. Python 进阶_OOP 面向对象编程_self 的实例绑定

    目录 目录 self 和绑定 调用非绑定的方法 self 和绑定 在 Python 中 self 变量是特殊的, 其用于在实例方法中引用该方法所绑定的实例, 换句话说就是 Python 在实例化对象时 ...

  6. Python进阶之面向对象编程

    面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...

  7. Python进阶之面向对象编程(二)

    Python面向对象编程(二) .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB& ...

  8. Python进阶之面向对象编程概述

    Python面向对象编程(一) .note-content {font-family: "Helvetica Neue",Arial,"Hiragino Sans GB& ...

  9. Java学习day8面向对象编程2-类的属性和方法

    一.类的属性 1.语法格式 修饰符 类型 属性名 = 初值 说明:修饰符private:该属性只能由该类的方法使用.在同一类内可见.使用对象:变量.方法. 注意:不能修饰类(外部类)    修饰符pu ...

随机推荐

  1. MySQL安装教程并使用springboot2和Mybatis测试

    目录 MySQL是什么 MySQL安装 开始使用一下MySQL 用spring boot2+Mybatis试试MySQL 创建数据库和表 拉通spring boot2+mybatis MySQL是什么 ...

  2. JS的两种函数声明方式的区别

    ---恢复内容开始--- js中常见的两种函数声明方式如下: // 函数表达式 var f = function() { console.log(1); } // 直接声明 function f () ...

  3. 使用redis来存储session,不同框架对session的命名规则是不一样的

    今天做了一个测试,在同一个云服务器上,搭建了两个server,其中一个是用laravel框架写的,另外一个使用原生php开发的,为了提高访问的速度,使用云服务器中的redis来存储session数据, ...

  4. UVAlive 6756 Increasing Shortest Path

    We all love short and direct problems, it is easier to write, read and understand the problem statem ...

  5. python学习三十七天函数的作用域查找顺序LEGB

    python函数的作用域查找顺序LEGB,分别为 locals  eclosing  globals  builtins .了解作用域的范围,可以更好的操作你想要的业务,分别介绍一下. 1,local ...

  6. NGUI的窗体的推动和调节大小(drag object和drag resize object)

    一,我们先添加一个sprite,给sprite添加一个背景图片,然后attach添加一个box Collider,但是这时我们右键attach是找不到drag object的我们需要在add comp ...

  7. shell PATH示例

  8. Shell06--数组应用

    目录 Shell06---数组应用 1. 数组基本概述 2. 数组基本使用 3. 数组遍历与循环 Shell06---数组应用 1. 数组基本概述 01. 什么是数组? 数组其实也算是变量,传统的变量 ...

  9. Oracle 存储过程--01

    1.简单的例子 /* 1.求圆的面积 */ create or replace procedure proc_1 is pi constant number(9,7):=3.1415927; radi ...

  10. gelera集群详解

    [MySQL]galera集群原理简介   特性 基于行复制的完全并行同步复制实时多主架构,任意节点可读写无延迟复制,事务零丢失,可靠健壮的读写体验.自动化节点关系控制:节点故障自动摘除,节点加入自动 ...