本文将介绍一下类的构造函数和初始化函数,以及如何通过"魔术方法"定制一个类。

类构造和初始化

在前面的文章中,经常使用初始化函数"__init__",下面看看"__init__"和"__new__"的联系和差别。

下面先通过一段代码看看这两个方法的调用顺序:

class A(object):
def __init__(self,*args, **kwargs):
print "init %s" %self.__class__
def __new__(cls,*args, **kwargs):
print "new %s" %cls
return object.__new__(cls, *args, **kwargs) a = A()

从代码的输出可以看到,当通过类实例化一个对象的时候,"__new__"方法首先被调用,然后是"__init__"方法。

一般来说,"__init__"和"__new__"函数都会有下面的形式:

def __init__(self, *args, **kwargs):
# func_suite def __new__(cls, *args, **kwargs):
# func_suite
return obj

对于"__new__"和"__init__"可以概括为:

  • "__new__"方法在Python中是真正的构造方法(创建并返回实例),通过这个方法可以产生一个"cls"对应的实例对象,所以说"__new__"方法一定要有返回
  • 对于"__init__"方法,是一个初始化的方法,"self"代表由类产生出来的实例对象,"__init__"将对这个对象进行相应的初始化操作

前面文章中已经介绍过了"__init__"的一些行为,包括继承情况中"__init__"的表现。下面就重点看看"__new__"方法。

__new__特性

"__new__"是在新式类中新出现的方法,它有以下行为特性:

  • "__new__" 方法是在类实例化对象时第一个调用的方法,将返回实例对象
  • "__new__" 方法始终都是类的静态方法(即第一个参数为cls),即使没有被加上静态方法装饰器
  • 第一个参数cls是当前正在实例化的类,如果要得到当前类的实例,应当在当前类中的 "__new__" 方法语句中调用当前类的父类的" __new__" 方法

对于上面的第三点,如果当前类是直接继承自 object,那当前类的 "__new__" 方法返回的对象应该为:

def __new__(cls, *args, **kwargs):
# func_suite
return object.__new__(cls, *args, **kwargs)

重写__new__

如果(新式)类中没有重写"__new__"方法,Python默认是调用该类的直接父类的"__new__"方法来构造该类的实例,如果该类的父类也没有重写"__new__",那么将一直按照同样的规则追溯至object的"__new__"方法,因为object是所有新式类的基类。

而如果新式类中重写了"__new__"方法,那么可以选择任意一个其他的新式类(必须是新式类,只有新式类有"__new__",因为所有新式类都是从object派生)的"__new__"方法来创建实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。

看一段例子代码:

class Foo(object):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
# 这里的object.__new__(cls, *args, **kwargs) 等价于
# super(Foo, cls).__new__(cls, *args, **kwargs)
# object.__new__(Foo, *args, **kwargs)
# Bar.__new__(cls, *args, **kwargs)
# Student.__new__(cls, *args, **kwargs),即使Student跟Foo没有关系,也是允许的,因为Student是从object派生的新式类 # 在任何新式类,不能调用自身的“__new__”来创建实例,因为这会造成死循环
# 所以要避免return Foo.__new__(cls, *args, **kwargs)或return cls.__new__(cls, *args, **kwargs)
print "Call __new__ for %s" %obj.__class__
return obj class Bar(Foo):
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
print "Call __new__ for %s" %obj.__class__
return obj class Student(object):
# Student没有“__new__”方法,那么会自动调用其父类的“__new__”方法来创建实例,即会自动调用 object.__new__(cls)
pass class Car(object):
def __new__(cls, *args, **kwargs):
# 可以选择用Bar来创建实例
obj = object.__new__(Bar, *args, **kwargs)
print "Call __new__ for %s" %obj.__class__
return obj foo = Foo()
bar = Bar()
car = Car()

代码的输出为:

__init__的调用

"__new__"决定是否要使用该类的"__init__"方法,因为"__new__" 可以调用其他类的构造方法或者直接返回别的类创建的对象来作为本类的实例。

通常来说,新式类开始实例化时,"__new__"方法会返回cls(cls指代当前类)的实例,然后调用该类的"__init__"方法作为初始化方法,该方法接收这个实例(即self)作为自己的第一个参数,然后依次传入"__new__"方法中接收的位置参数和命名参数。

但是,如果"__new__"没有返回cls(即当前类)的实例,那么当前类的"__init__"方法是不会被调用的。看下面的例子:

class A(object):
def __init__(self, *args, **kwargs):
print "Call __init__ from %s" %self.__class__ def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
print "Call __new__ for %s" %obj.__class__
return obj class B(object):
def __init__(self, *args, **kwargs):
print "Call __init__ from %s" %self.__class__ def __new__(cls, *args, **kwargs):
obj = object.__new__(A, *args, **kwargs)
print "Call __new__ for %s" %obj.__class__
return obj b = B()
print type(b)

代码中,在B的"__new__"方法中,通过"obj = object.__new__(A, *args, **kwargs)"创建了一个A的实例,在这种情况下,B的"__init__"函数就不会被调用到。

派生不可变类型

关于"__new__"方法还有一个重要的用途就是用来派生不可变类型。

例如,Python中float是不可变类型,如果想要从float中派生一个子类,就要实现"__new__"方法:

class Round2Float(float):
def __new__(cls, num):
num = round(num, 2)
#return super(Round2Float, cls).__new__(cls, num)
return float.__new__(Round2Float, num) f = Round2Float(4.14159)
print f

代码中从float派生出了一个Round2Float类,该类的实例就是保留小数点后两位的浮点数。

定制一个类

在Python中,我们可以通过"魔术方法"使自定义的class变得强大、易用。

例如,前面的文章中介绍过Python迭代器,当我们想定义一个可迭代的类对象的时候,就可以去实现"__iter__(self)"这个魔术方法;

又例如,前面文章介绍的上下文管理器,当需要建立一个上下文管理器类对象的时候,就可以去实现"__enter__(self)"和"__exit__(self)"方法。

所以,建议参考 "魔术方法"的文档,通过魔术方法来定制自定义的类。

调用魔术方法

一些魔术方法直接和内建函数相对应的,在这种情况下,调用他们的方法很简单,下面给出了一些对应表。

魔术方法

调用方式

解释

__new__(cls [,...])

instance = MyClass(arg1, arg2)

__new__ 在创建实例的时候被调用

__init__(self [,...])

instance = MyClass(arg1, arg2)

__init__ 在创建实例的时候被调用

__cmp__(self, other)

self == other, self > other, 等。

在比较的时候调用

__pos__(self)

+self

一元加运算符

__neg__(self)

-self

一元减运算符

__invert__(self)

~self

取反运算符

__index__(self)

x[self]

对象被作为索引使用的时候

__nonzero__(self)

bool(self)

对象的布尔值

__getattr__(self, name)

self.name # name 不存在

访问一个不存在的属性时

__setattr__(self, name, val)

self.name = val

对一个属性赋值时

__delattr__(self, name)

del self.name

删除一个属性时

__getattribute(self, name)

self.name

访问任何属性时

__getitem__(self, key)

self[key]

使用索引访问元素时

__setitem__(self, key, val)

self[key] = val

对某个索引值赋值时

__delitem__(self, key)

del self[key]

删除某个索引值时

__iter__(self)

for x in self

迭代时

__contains__(self, value)

value in self, value not in self

使用 in 操作测试关系时

__concat__(self, value)

self + other

连接两个对象时

__call__(self [,...])

self(args)

"调用"对象时

__enter__(self)

with self as x:

with 语句环境管理

__exit__(self, exc, val, trace)

with self as x:

with 语句环境管理

__getstate__(self)

pickle.dump(pkl_file, self)

序列化

__setstate__(self)

data = pickle.load(pkl_file)

序列化

总结

文中介绍了类的构造和初始化方法:"__new__"和"__init__"。

"__new__"方法是新式类特有的方法,通常情况下,__new__方法会创建返回cls(cls指代当前类)的实例,然后调用该类的"__init__"方法作为初始化方法,该方法接收这个实例(即self)作为自己的第一个参数,然后依次传入"__new__"方法中接收的位置参数和命名参数;但是,如果"__new__"没有返回cls(即当前类)的实例,那么当前类的"__init__"方法是不会被调用的。

通过"魔术方法",可以对自定义类进行定制、扩展,使得自定义类变得强大、易用。

Python中的类(下)的更多相关文章

  1. Python中的类、对象、继承

    类 Python中,类的命名使用帕斯卡命名方式,即首字母大写. Python中定义类的方式如下: class 类名([父类名[,父类名[,...]]]): pass 省略父类名表示该类直接继承自obj ...

  2. python中那些双下划线开头得函数和变量--转载

    Python中下划线---完全解读     Python 用下划线作为变量前缀和后缀指定特殊变量 _xxx 不能用'from module import *'导入 __xxx__ 系统定义名字 __x ...

  3. python python中那些双下划线开头的那些函数都是干啥用用的

    1.写在前面 今天遇到了__slots__,,所以我就想了解下python中那些双下划线开头的那些函数都是干啥用用的,翻到了下面这篇博客,看着很全面,我只了解其中的一部分,还不敢乱下定义. 其实如果足 ...

  4. 4、Python中的类详解(0601)

    <大话数据结构>的作者程杰在博客园也有博客,网址是:http://cj723.cnblogs.com/ 面向对象编程(OOP) 1.程序 = 指令 + 数据 代码可以选择以指令为核心或以数 ...

  5. python中那些双下划线开头得函数和变量

    Python中下划线---完全解读     Python 用下划线作为变量前缀和后缀指定特殊变量 _xxx 不能用’from module import *’导入 __xxx__ 系统定义名字 __x ...

  6. Python中的类(上)

    在Python中,可以通过class关键字定义自己的类,然后通过自定义的类对象类创建实例对象. 例如,下面创建了一个Student的类,并且实现了这个类的初始化函数"__init__&quo ...

  7. python中新式类和经典类

    python中的类分为新式类和经典类,具体有什么区别呢?简单的说, 1.新式类都从object继承,经典类不需要. Python 2.x中默认都是经典类,只有显式继承了object才是新式类 Pyth ...

  8. python中的类(二)

    python中的类(二) 六.类的成员 字段:普通字段,静态字段 eg: class Province(): country=’中国’ #静态字段,保存在类中,执行时可以通过类或对象访问 def __ ...

  9. Python中的类(一)

    Python中的类(一) 一. 应用场景 如果多个函数中有一些相同的参数时,转换成面向对象. 二. 如何创建类 类是用来描述具有相同的属性和方法的对象的集合.它定义了该集合中每个对象所共有的属性和方法 ...

  10. 关于Python中的类普通继承与super函数继承

    关于Python中的类普通继承与super函数继承 1.super只能用于新式类 2.多重继承super可以保公共父类仅被执行一次 一.首先看下普通继承的写法 二.再看看super继承的写法 参考链接 ...

随机推荐

  1. webpack1--安装与初始化

    webpack可用于打包所有的脚本,图片,样式表及其他资源. 今天来讲一下如何安装webpack 及 创建package.json.webpack依赖于npm和 node,所以本文是假设你已安装好了n ...

  2. GCC编译错误小结

    gcc编译时对’xxxx’未定义的引用问题可能错误 错误一: 没有实现xxxx 错误二: c++引用c语言so库,但是so库头文件没有extern "C" 错误三: 检查各个共享库 ...

  3. (原)hisi3531立体声pcm实现播放方式

    版权声明:本文为博主原创文章,未经博主允许不得转载(http://www.cnblogs.com/lihaiping/p/5251854.html) 最近在使用hisi3531做一个项目,需要实现本地 ...

  4. Eclipse中创建Maven项目失败

    Eclipse中创建Maven项目报错:Unable to create project from archetype org.apache.maven.archetypes:maven-archet ...

  5. Win10无法使用内置管理员用户打开edge解决方案

    https://jingyan.baidu.com/article/4f7d5712d23f1b1a2119274b.html

  6. C# Byte[]、Image、Bitmap 之间的相互转换

    //byte[] 转图片 public static Bitmap BytesToBitmap(byte[] Bytes) { MemoryStream stream = null; try { st ...

  7. Windows下安装Oracle12C(二)

    一.安装Oracle 参考 Windows下安装Oracle12C(一) 二.新建用户 1. 安装完成后,启动SQLPlus,然后输入内置的用户名和密码 用户名:sys   密码:as sysdba ...

  8. 怎样解决Java Web项目更改项目名后报错

    作为企业级开发最流行的工具,用Myeclipse开发java web程序无疑是最合适的,有时候,我们需要web工程的项目名,单方面的改动工程的项目名是会报错的,那么该如何改web工程项目名呢? 简 单 ...

  9. UFLDL教程练习(exercise)答案(2)

    主成分分析与白化,这部分很简单,当然,其实是用Matlab比较简单,要是自己写SVD分解算法,足够研究好几个月的了.下面是我自己实现的练习答案,不保证完全正确,不过结果和网站上面给出的基本一致. 1. ...

  10. linux下命令行打开文件管理器

    nautilus,这个太有用了,应为可以在secureCRT中使用,因为可以添加sudo来调用