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

类构造和初始化

在前面的文章中,经常使用初始化函数"__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. js android页面被挂起问题解决

    问题: 页面上设了定时器,但浏览器后台运行被挂起时,页面定时器暂停 解决: 向服务器发送同步请求,服务器延时1秒返回.页面收到返回时再次发送请求 服务器相当于起博器,维持页面将停的心跳

  2. [Intellij] 软件设置和常用快捷键

    理解Project 和 Module 一个 Project 是由一个或多个 Module 组成,模块之间尽量是处在同一个项目业务的的情况下,彼此之间互相依赖关联.如: Project : spring ...

  3. Python之使用PIL实现cv2

    有时候使用cv2需要安装opencv,但是opencv安装比较麻烦,因为需要编译过程. 所以,我们可以使用PIL对cv2的一些常用接口进行复现. 这里我们实现了cv2的imread(), imwrit ...

  4. [转]浅谈Android五大布局(一)——LinearLayout、FrameLayout和AbsoulteLayout

    Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...

  5. 【总结】牛客职播第十期:程盟有你,way来可期

    一.介绍 携程旅行2018校园春招流程介绍&面试答疑 讲师:宋涛,李响 二.答疑 1,技术都是先通过笔试进行优胜劣汰.论真英雄还是靠技术! 2,英语四级必须过! 3,国外大公司招人,对他们的文 ...

  6. Java如何暂停线程一段时间?

    在Java编程中,如何暂停线程一段时间? 以下示例显示如何通过创建sleepThread()方法来暂停线程一段时间. package com.yiibai; public class Suspendi ...

  7. Java如何获取正在运行的线程的名称?

    在Java编程中,如何获取正在运行的线程的名称? 以下示例显示如何获取正在运行的线程的名称. package com.yiibai; public class GettingThreadName ex ...

  8. Java获取正在执行的函数名

    利用StackTrace堆栈轨迹获取某个时间的调用堆栈状态. package com.dsp.demo; public class TechDemo { public static void main ...

  9. 测试webservice的时候,如果出现这个错误:"The test form is only available for requests from the local machine"

    测试webservice的时候,如果出现这个错误:"The test form is only available for requests from the local machine&q ...

  10. perl 查看某个模块的安装路径

    perldoc 命令可以查看某个模块的安装路径 命令: perldoc -l Bio::Seq 返回值如下: /usr/local/share/perl5/Bio/Seq.pm