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

目录

构造器和解构器

构造器 __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'}

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

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

上一篇:Java I/O学习(附实例和详解)


下一篇:C++程序设计:原理与实践(进阶篇)15.6 实例:一个简单的文本编辑器