距离上一篇已经三个多星期了,最近比较累,下班回到家,很早就休息了,因此更新的进度有点慢。python
在 Python 中,全部以 "" 双下划线包起来的方法,都统称为"魔术方法"。好比咱们接触最多的 `init__` 。魔术方法有什么做用呢?编程
使用这些魔术方法,咱们能够构造出优美的代码,将复杂的逻辑封装成简单的方法。数据结构
那么一个类中有哪些魔术方法呢?app
咱们可使用 Python 内置的方法 dir()
来列出类中全部的魔术方法.示例以下:函数
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
pass
if __name__ == '__main__':
print(dir(User()))复制代码
输出的结果:spa
能够看到,一个类的魔术方法仍是挺多的,截图也没有截全,不过咱们只须要了解一些常见和经常使用的魔术方法就行了。3d
__new__
)和初始化(__init__
)经过上一篇的内容,咱们已经知道定义一个类时,咱们常常会经过 __init__(self)
的方法在实例化对象的时候,对属性进行设置。好比下面的例子:code
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name, age):
self.name = name;
self.age = age;
user=User('两点水',23)复制代码
实际上,建立一个类的过程是分为两步的,一步是建立类的对象,还有一步就是对类进行初始化。__new__
是用来建立类并返回这个类的实例, 而__init__
只是将传入的参数来初始化该实例.__new__
在建立一个实例的过程当中一定会被调用,但 __init__
就不必定,好比经过pickle.load 的方式反序列化一个实例时就不会调用 __init__
方法。orm
def __new__(cls)
是在 def __init__(self)
方法以前调用的,做用是返回一个实例对象。还有一点须要注意的是:__new__
方法老是须要返回该类的一个实例,而 __init__
不能返回除了 None
的任何值cdn
具体的示例:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __new__(cls, *args, **kwargs):
# 打印 __new__方法中的相关信息
print('调用了 def __new__ 方法')
print(args)
# 最后返回父类的方法
return super(User, cls).__new__(cls)
def __init__(self, name, age):
print('调用了 def __init__ 方法')
self.name = name
self.age = age
if __name__ == '__main__':
usr = User('两点水', 23)复制代码
看看输出的结果:
调用了 def __new__ 方法
('两点水', 23)
调用了 def __init__ 方法复制代码
经过打印的结果来看,咱们就能够知道一个类建立的过程是怎样的了,先是调用了 __new__
方法来建立一个对象,把参数传给 __init__
方法进行实例化。
其实在实际开发中,不多会用到 __new__
方法,除非你但愿可以控制类的建立。一般讲到 __new__
,都是牵扯到 metaclass
(元类)的。
固然当一个对象的生命周期结束的时候,析构函数 __del__
方法会被调用。可是这个方法是 Python 本身对对象进行垃圾回收的。
以前也有讲到过,Python 没有真正意义上的私有属性。而后这就致使了对 Python 类的封装性比较差。咱们有时候会但愿 Python 可以定义私有属性,而后提供公共可访问的 get 方法和 set 方法。Python 其实能够经过魔术方法来实现封装。
方法 | 说明 |
---|---|
__getattr__(self, name) |
该方法定义了你试图访问一个不存在的属性时的行为。所以,重载该方法能够实现捕获错误拼写而后进行重定向, 或者对一些废弃的属性进行警告。 |
__setattr__(self, name, value) |
定义了对属性进行赋值和修改操做时的行为。无论对象的某个属性是否存在,都容许为该属性进行赋值.有一点须要注意,实现 __setattr__ 时要避免"无限递归"的错误, |
__delattr__(self, name) |
__delattr__ 与 __setattr__ 很像,只是它定义的是你删除属性时的行为。实现 __delattr__ 是同时要避免"无限递归"的错误 |
__getattribute__(self, name) |
__getattribute__ 定义了你的属性被访问时的行为,相比较,__getattr__ 只有该属性不存在时才会起做用。所以,在支持 __getattribute__ 的 Python 版本,调用__getattr__ 前一定会调用 __getattribute__``__getattribute__ 一样要避免"无限递归"的错误。 |
经过上面的方法表能够知道,在进行属性访问控制定义的时候你可能会很容易的引发一个错误,能够看看下面的示例:
def __setattr__(self, name, value):
self.name = value
# 每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就形成了递归调用。
# 这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用本身。这样会形成程序崩溃。
def __setattr__(self, name, value):
# 给类中的属性名分配值
self.__dict__[name] = value
# 定制特有属性复制代码
上面方法的调用具体示例以下:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __getattr__(self, name):
print('调用了 __getattr__ 方法')
return super(User, self).__getattr__(name)
def __setattr__(self, name, value):
print('调用了 __setattr__ 方法')
return super(User, self).__setattr__(name, value)
def __delattr__(self, name):
print('调用了 __delattr__ 方法')
return super(User, self).__delattr__(name)
def __getattribute__(self, name):
print('调用了 __getattribute__ 方法')
return super(User, self).__getattribute__(name)
if __name__ == '__main__':
user = User()
# 设置属性值,会调用 __setattr__
user.attr1 = True
# 属性存在,只有__getattribute__调用
user.attr1
try:
# 属性不存在, 先调用__getattribute__, 后调用__getattr__
user.attr2
except AttributeError:
pass
# __delattr__调用
del user.attr1复制代码
输出的结果:
调用了 __setattr__ 方法
调用了 __getattribute__ 方法
调用了 __getattribute__ 方法
调用了 __getattr__ 方法
调用了 __delattr__ 方法复制代码
通常来讲,一个描述器是一个有“绑定行为”的对象属性 (object attribute),它的访问控制被描述器协议方法重写。这些方法是 __get__()
, __set__()
, 和 __delete__()
。有这些方法的对象叫作描述器。
默认对属性的访问控制是从对象的字典里面 (__dict__
) 中获取 (get) , 设置 (set) 和删除 (delete) 。举例来讲, a.x
的查找顺序是, a.__dict__['x']
, 而后 type(a).__dict__['x']
, 而后找 type(a)
的父类 ( 不包括元类 (metaclass) ).若是查找到的值是一个描述器, Python 就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪一个描述器方法。注意, 只有在新式类中时描述器才会起做用。在以前的篇节中已经提到新式类和旧式类的,有兴趣能够查看以前的篇节来看看,至于新式类最大的特色就是全部类都继承自 type 或者 object 的类。
在面向对象编程时,若是一个类的属性有相互依赖的关系时,使用描述器来编写代码能够很巧妙的组织逻辑。在 Django 的 ORM 中,models.Model中的 InterField 等字段, 就是经过描述器来实现功能的。
咱们先看下下面的例子:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class User(object):
def __init__(self, name='两点水', sex='男'):
self.sex = sex
self.name = name
def __get__(self, obj, objtype):
print('获取 name 值')
return self.name
def __set__(self, obj, val):
print('设置 name 值')
self.name = val
class MyClass(object):
x = User('两点水', '男')
y = 5
if __name__ == '__main__':
m = MyClass()
print(m.x)
print('\n')
m.x = '三点水'
print(m.x)
print('\n')
print(m.x)
print('\n')
print(m.y)复制代码
输出的结果以下:
获取 name 值
两点水
设置 name 值
获取 name 值
三点水
获取 name 值
三点水
5复制代码
经过这个例子,能够很好的观察到这 __get__()
和 __set__()
这些方法的调用。
再看一个经典的例子
咱们知道,距离既能够用单位"米"表示,也能够用单位"英尺"表示。
如今咱们定义一个类来表示距离,它有两个属性: 米和英尺。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class Meter(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
meter = Meter()
foot = Foot()
if __name__ == '__main__':
d = Distance()
print(d.meter, d.foot)
d.meter = 1
print(d.meter, d.foot)
d.meter = 2
print(d.meter, d.foot)复制代码
输出的结果:
0.0 0.0
1.0 3.2808
2.0 6.5616复制代码
在上面例子中,在尚未对 Distance 的实例赋值前, 咱们认为 meter 和 foot 应该是各自类的实例对象, 可是输出倒是数值。这是由于 __get__
发挥了做用.
咱们只是修改了 meter ,而且将其赋值成为 int ,但 foot 也修改了。这是 __set__
发挥了做用.
描述器对象 (Meter、Foot) 不能独立存在, 它须要被另外一个全部者类 (Distance) 所持有。描述器对象能够访问到其拥有者实例的属性,好比例子中 Foot 的 instance.meter
。
通过以前编章的介绍,咱们知道在 Python 中,常见的容器类型有: dict, tuple, list, string。其中也提到过可容器和不可变容器的概念。其中 tuple, string 是不可变容器,dict, list 是可变容器。 可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。固然具体的介绍,能够看回以前的文章,有图文介绍。
那么这里先提出一个问题,这些数据结构就够咱们开发使用吗?不够的时候,或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢?
这个时候就须要自定义容器了,那么具体咱们该怎么作呢?
功能 | 说明 |
---|---|
自定义不可变容器类型 | 须要定义 __len__ 和 __getitem__ 方法 |
自定义可变类型容器 | 在不可变容器类型的基础上增长定义 __setitem__ 和 __delitem__ |
自定义的数据类型须要迭代 | 需定义 __iter__ |
返回自定义容器的长度 | 需实现 __len__(self) |
自定义容器能够调用 self[key] ,若是 key 类型错误,抛出TypeError ,若是无法返回key对应的数值时,该方法应该抛出ValueError |
须要实现 __getitem__(self, key) |
当执行 self[key] = value 时 |
调用是 __setitem__(self, key, value) 这个方法 |
当执行 del self[key] 方法 |
其实调用的方法是 __delitem__(self, key) |
当你想你的容器能够执行 for x in container: 或者使用 iter(container) 时 |
须要实现 __iter__(self) ,该方法返回的是一个迭代器 |
来看一下使用上面魔术方法实现 Haskell 语言中的一个数据结构:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class FunctionalList:
''' 实现了内置类型list的功能,并丰富了一些其余方法: head, tail, init, last, drop, take'''
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return FunctionalList(reversed(self.values))
def append(self, value):
self.values.append(value)
def head(self):
# 获取第一个元素
return self.values[0]
def tail(self):
# 获取第一个元素以后的全部元素
return self.values[1:]
def init(self):
# 获取最后一个元素以前的全部元素
return self.values[:-1]
def last(self):
# 获取最后一个元素
return self.values[-1]
def drop(self, n):
# 获取全部元素,除了前N个
return self.values[n:]
def take(self, n):
# 获取前N个元素
return self.values[:n]复制代码
运算符相关的魔术方法实在太多了,j就大概列举下面两类:
魔术方法 | 说明 |
---|---|
__cmp__(self, other) |
若是该方法返回负数,说明 self < other ; 返回正数,说明 self > other ; 返回 0 说明 self == other 。强烈不推荐来定义 __cmp__ , 取而代之, 最好分别定义 __lt__ , __eq__ 等方法从而实现比较功能。 __cmp__ 在 Python3 中被废弃了。 |
__eq__(self, other) |
定义了比较操做符 == 的行为 |
__ne__(self, other) |
定义了比较操做符 != 的行为 |
__lt__(self, other) |
定义了比较操做符 < 的行为 |
__gt__(self, other) |
定义了比较操做符 > 的行为 |
__le__(self, other) |
定义了比较操做符 <= 的行为 |
__ge__(self, other) |
定义了比较操做符 >= 的行为 |
来看个简单的例子就能理解了:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
class Number(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
print('__eq__')
return self.value == other.value
def __ne__(self, other):
print('__ne__')
return self.value != other.value
def __lt__(self, other):
print('__lt__')
return self.value < other.value
def __gt__(self, other):
print('__gt__')
return self.value > other.value
def __le__(self, other):
print('__le__')
return self.value <= other.value
def __ge__(self, other):
print('__ge__')
return self.value >= other.value
if __name__ == '__main__':
num1 = Number(2)
num2 = Number(3)
print('num1 == num2 ? --------> {} \n'.format(num1 == num2))
print('num1 != num2 ? --------> {} \n'.format(num1 == num2))
print('num1 < num2 ? --------> {} \n'.format(num1 < num2))
print('num1 > num2 ? --------> {} \n'.format(num1 > num2))
print('num1 <= num2 ? --------> {} \n'.format(num1 <= num2))
print('num1 >= num2 ? --------> {} \n'.format(num1 >= num2))复制代码
输出的结果为:
__eq__
num1 == num2 ? --------> False
__eq__
num1 != num2 ? --------> False
__lt__
num1 < num2 ? --------> True
__gt__
num1 > num2 ? --------> False
__le__
num1 <= num2="" ?="" --------=""> True
__ge__
num1 >= num2 ? --------> False
复制代码
魔术方法 | 说明 | |
---|---|---|
__add__(self, other) |
实现了加号运算 | |
__sub__(self, other) |
实现了减号运算 | |
__mul__(self, other) |
实现了乘法运算 | |
__floordiv__(self, other) |
实现了 // 运算符 | |
___div__(self, other) |
实现了/运算符. 该方法在 Python3 中废弃. 缘由是 Python3 中,division 默认就是 true division | |
__truediv__(self, other) |
实现了 true division. 只有你声明了 from __future__ import division 该方法才会生效 |
|
__mod__(self, other) |
实现了 % 运算符, 取余运算 | |
__divmod__(self, other) |
实现了 divmod() 內建函数 | |
__pow__(self, other) |
实现了 ** 操做. N 次方操做 |
|
__lshift__(self, other) |
实现了位操做 << |
|
__rshift__(self, other) |
实现了位操做 >> |
|
__and__(self, other) |
实现了位操做 & |
|
__or__(self, other) |
实现了位操做 ` | ` |
__xor__(self, other) |
实现了位操做 ^ |
最后,若是对本文感兴趣的,能够关注下公众号: