python 魔法方法常用_Python技术进阶——魔法方法(一)

想必只要是做Python开发的同学,都会或多或少见到以双下划线开头的方法,这些就是我们经常说的“魔法”方法。它可以对你的类添加特殊的功能,使用恰当会给我们的开发带来很大的便利。

这篇文章主要是总结了在我们开发中,经常遇到的那些“魔法”方法,如何使用以及它们的使用场景。

概览

目前我们常见的魔法方法大致可分为以下几类:

构造与初始化

类的表示

访问控制

比较操作

容器类操作

可调用对象

Pickling序列化

我们这次主要介绍这几类常用魔法方法。

构造与初始化

init

构造方法是我们使用频率最高的魔法方法了,几乎在我们定义类的时候,都会去定义构造方法,它的主要作用就是在初始化一个对象时,定义这个对象的初始值。

# coding: utf8

class Person(object):

def __init__(self, name, age):

self.name = name

self.age = age

p1 = Person('张三', 25)

p2 = Person('李四', 30)

new

这个方法我们一般很少定义,不过我们在一些开源框架中偶尔会遇到定义这个方法的类。实际上,这才是“真正的构造方法”,它会在对象实例化时第一个被调用,然后再调用init,它们的区别主要如下:

new的第一个参数是cls,而init的第一个参数是self

new返回值是一个实例,而init没有任何返回值,只做初始化操作

new由于是返回一个实例对象,所以它可以给所有实例进行统一的初始化操作

由于new优先于init调用,且返回一个实例,所以我们可以利用这种特性,每次返回同一个实例来实现一个单例类:

class Singleton(object):

"""单例"""

_instance = None

def __new__(cls, *args, **kwargs):

if not cls._instance:

cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)

return cls._instance

class MySingleton(Singleton):

pass

a = MySingleton()

b = MySingleton()

assert a is b # True

另外一种使用场景是当你需要继承内置类时,例如int、str、tuple,只能通过new来达到初始化数据的效果:

class g(float):

"""千克转克"""

def __new__(cls, kg):

return float.__new__(cls, kg * 2)

# 50千克转为克

a = g(50)

print a # 100

print a + 100 # 200, 由于继承了float,所以可以直接运算,非常方便!

除此之外,new比较多的应用场景是配合元类使用,具体会在以后的文章中讲解到。

del

这个方法代表析构方法,也就是在对象被垃圾回收时被调用。但是请注意,执行del x不一定会执行此方法。

由于Python是通过引用计数来进行垃圾回收的,也就是说,如果这个实例还是有被引用到,即使执行del销毁这个对象,但其引用计数还是大于0,所以不会触发执行del。

来看一个例子:

class Person(object):

def __del__(self):

print '__del__'

如果我们直接执行:

a = Person()

print 'exit'

# Output:

# exit

# __del__

此时我们没有对实例进行任何操作时,del在程序退出后被调用。

a = Person()

del a # 手动销毁

print 'exit'

# Output:

# __del__

# exit

由于此实例没有被其他对象所引用,当我们手动销毁这个实例时,del被调用后程序正常退出。

a = Person()

b = a # b引用a

del a # 手动销毁,不触发__del__

print 'exit'

# Output:

# exit

# __del__

此时实例有被其他对象引用,尽管我们手动销毁这个实例,但依然不会触发del方法,而是在程序正常退出后被调用执行。

为了保险起见,当我们在对文件、socket进行操作时,要想安全地关闭和销毁这些对象,最好是在try异常块后的finally中进行关闭和释放操作!

类的表示

str/repr

这两个魔法方法一般会放到一起进行讲解,它们的主要差别为:

str强调可读性,而repr强调准确性/标准性

str的目标人群是用户,而repr的目标人群是机器,它的结果是可以被执行的

%s调用str方法,而%r调用repr方法

来看几个例子,了解内置类实现这2个方法的效果:

a = 'hello'

str(a)

'hello'

'%s' % a # 调用__str__

'hello'

repr(a) # 对象a的标准表示,也就是a是如何创建的

"'hello'"

'%r' % a # 调用__repr__

"'hello'"

import datetime

b = datetime.datetime.now()

str(b)

'2017-02-22 12:28:40.923379'

print b # 等同于print str(b)

2017-02-22 12:28:40.923379

repr(b) # 展示对象b的标准创建方式(如何创建的)

'datetime.datetime(2017, 2, 22, 12, 28, 40, 923379)'

b # 等同于print repr(b)

datetime.datetime(2017, 2, 22, 12, 28, 40, 923379)

c = eval(repr(b)) # repr(b)目标针对于机器,所以可执行

c

datetime.datetime(2017, 2, 22, 12, 28, 40, 923379)

从上面的例子可以看出这两个方法的主要区别,在实际中我们定义类时,一般这样定义即可:

class Person(object):

def __init__(self, name, age):

self.name = name

self.age = age

def __str__(self):

# 格式化,友好对用户展示

return 'name: %s, age: %s' % (self.name, self.age)

def __repr__(self):

# 标准化展示

return "Person('%s', %s)" % (self.name, self.age)

person = Person('zhangsan', 20)

print str(person) # name: zhangsan, age: 20

print '%s' % person # name: zhangsan, age: 20

print repr(person) # Person('zhangsan', 20)

print '%r' % person # Person('zhangsan', 20)

这里值得注意的是,如果只定义了str或repr其中一个,那会是什么结果?

如果只定义了_str,那么repr(person)输出

如果只定义了repr,那么str(person)与repr(person)结果是相同的

也就是说,repr在表示类时,是一级的,如果只定义它,那么str = repr。

而str展示类时是次级的,用户可自定义类的展示格式,如果没有定义repr,那么repr(person)将会展示缺省的定义。

unicode

如果一个类定义了unicode方法,那么在调用unicode(obj)时,此方法将被调用,但是其返回值类型是unicode。

class Person(object):

def __unicode__(self):

# 这里不是u'hello'

return 'hello'

person = Person()

print unicode(person) # helllo

print type(unicode(person)) #

尽管unicode的返回值不是unicode类型,但在输出时候会自动转换成unicode类型。

此方法在开发中一般很少使用,通常我们只需要定义str即可。

hash/eq

hash方法返回一个整数,用来表示该对象的唯一标识,配合eq方法判断两个对象是否相等(==):

class Person(object):

def __init__(self, uid):

self.uid = uid

def __repr__(self):

return 'Person(%s)' % self.uid

def __hash__(self):

return self.uid

def __eq__(self, other):

return self.uid == other.uid

p1 = Person(1)

p2 = Person(1)

p1 == p2 # True

p3 = Person(2)

print set([p1, p2, p3]) # 根据唯一标识去重输出 set([Person(1), Person(2)])

如果我们需要判断两个对象是否相等,只要我们重写hash和eq方法就可以完成此功能。此外使用set存放这些对象时,会根据这两个方法进行去重操作。

nonzero

当调用bool(obj)时,会调用nonzero方法,返回True/False。

class Person(object):

def __init__(self, uid):

self.uid = uid

def __nonzero__(self):

return self.uid > 10

p1 = Person(1)

p2 = Person(15)

print bool(p1) # False

print bol(p2) # True

在Python3中,nonzero被重命名bool。

访问控制

访问控制相关的魔法方法,主要涉及以下几个:

setattr:通过.设置属性或setattr(key, value)

getattr:访问不存在的属性

delattr:删除某个属性

getattribute:访问任意属性或方法

来看一个完整的例子:

class Person(object):

def __setattr__(self, key, value):

"""属性赋值"""

if key not in ('name', 'age'):

return

if key == 'age' and value < 0:

raise ValueError()

super(Person, self).__setattr__(key, value)

def __getattr__(self, key):

"""访问某个不存在的属性"""

return 'unknown'

def __delattr__(self, key):

"""删除某个属性"""

if key == 'name':

raise AttributeError()

super(Person, self).__delattr__(key)

def __getattribute__(self, key):

"""所有属性/方法调用都经过这里"""

if key == 'money':

return 100

if key == 'hello':

return self.say

return super(Person, self).__getattribute__(key)

def say(self):

return 'hello'

p1 = Person()

p1.name = 'zhangsan' # 调用__setattr__

p1.age = 20 # 调用__setattr__

print p1.name # zhangsan

print p1.age # 20

setattr(p1, 'name', 'lisi') # 调用__setattr__

setattr(p1, 'age', 30) # 调用__setattr__

print p1.name # lisi

print p1.age # 30

p1.gender = 'male' # __setattr__中忽略对gender赋值

print p1.gender # gender不存在,调用__getattr__返回:unknown

print p1.money # money不存在,在__getattribute__中返回100

print p1.say() # hello

print p1.hello()# hello,调用__getattribute__,间接调用say方法

del p1.name # __delattr__中引发AttributeError

p2 = Person()

p2.age = -1 # __setattr__中引发ValueError

setattr

通过此方法,对象可在在对属性进行赋值时进行控制,所有的属性赋值都会经过它。

一般常用于对某些属性赋值的检查校验逻辑,例如age不能小于0,否则认为是非法数据等等。

getattr

很多同学以为此方法是和setattr完全对立的,其实不然!

这个方法只有在访问某个不存在的属性时才会被调用,看上面的例子,由于gender属性在赋值时,忽略了此字段的赋值操作,所以此属性是没有被成功赋值给对象的。当访问这个属性时,getattr被调用,返回unknown。

del

删除对象的某个属性时,此方法被调用。一般常用于某个属性必须存在,否则无法进行后续的逻辑操作,会重写此方法,对删除属性逻辑进行检查和校验。

getattribute

这个方法我们很少用到,它与getattr很容易混淆。它与前者的区别在于:

getattr访问某个不存在的属性被调用,getattribute访问任意属性被调用

getattr只针对属性访问,getattribute不仅针对所有属性访问,还包括方法调用

越是强大的魔法方法,责任越大,如果你不能正确使用它,最好还是不用为好,否则在出现问题时很难排查。

如果此文章能给您带来小小的工作效率提升,不妨小额赞助我一下,以鼓励我写出更好的文章!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值