语言基础

语言基础-Python基础05

四、Python 的 Magic Method

  • Python 的 Magic Method
  • 构造(new)和初始化(init)
  • 属性的访问控制
  • 对象的描述器
  • 自定义容器(Container)
  • 运算符相关的魔术方法

Python 的 Magic Method

在 Python 中,所有以 "" 双下划线包起来的方法,都统称为"魔术方法"。比如我们接触最多的 init__ 。

魔术方法有什么作用呢?

使用这些魔术方法,我们可以构造出优美的代码,将复杂的逻辑封装成简单的方法。

我们可以使用 Python 内置的方法 dir() 来列出类中所有的魔术方法.示例如下:

class User(object):
pass

if name == 'main':
print(dir(User()))

输出的结果:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

可以看到,一个类的魔术方法还是挺多的,不过我们只需要了解一些常见和常用的魔术方法就好了。

构造(new)和初始化(init)

class User(object):
def init(self, name, age):
self.name = name;
self.age = age;

user=User('两点水',23)
实际上,创建一个类的过程是分为两步的,一步是创建类的对象,还有一步就是对类进行初始化。

new 是用来创建类并返回这个类的实例, 而init 只是将传入的参数来初始化该实例.new 在创建一个实例的过程中必定会被调用,但 init 就不一定,比如通过 pickle.load 的方式反序列化一个实例时就不会调用 init 方法。

其实在实际开发中,很少会用到 new 方法,除非你希望能够控制类的创建。通常讲到 new ,都是牵扯到 metaclass(元类)的。

当然当一个对象的生命周期结束的时候,析构函数 del 方法会被调用。但是这个方法是 Python 自己对对象进行垃圾回收的。

属性的访问控制

Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。

  • getattr(self, name):该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。

  • setattr(self, name, value):定义了对属性进行赋值和修改操作时的行为。不管对象的某个属性是否存在,都允许为该属性进行赋值.有一点需要注意,实现 setattr 时要避免"无限递归"的错误

  • delattr(self, name):delattrsetattr 很像,只是它定义的是你删除属性时的行为。实现 delattr 是同时要避免"无限递归"的错误

  • getattribute(self, name):getattribute 定义了你的属性被访问时的行为,相比较,getattr 只有该属性不存在时才会起作用。因此,在支持 getattribute 的 Python 版本,调用getattr 前必定会调用getattribute``getattribute 同样要避免"无限递归"的错误。

调用具体示例如下:

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 等字段, 就是通过描述器来实现功能的。

距离既可以用单位"米"表示,也可以用单位"英尺"表示。 现在我们定义一个类来表示距离,它有两个属性: 米和英尺。

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 。

自定义容器(Container)

在 Python 中,常见的容器类型有: dict, tuple, list, string。其中也提到过可容器和不可变容器的概念。其中 tuple, string 是不可变容器,dict, list 是可变容器。

可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。

这些数据结构就够我们开发使用吗?

不够的时候,或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢?

这个时候就需要自定义容器了,

  • 自定义不可变容器类型:需要定义 lengetitem 方法

  • 自定义可变类型容器:在不可变容器类型的基础上增加定义 setitemdelitem

  • 自定义的数据类型需要迭代:需定义 iter

  • 返回自定义容器的长度:需实现 len(self)

  • 自定义容器可以调用 self[key] ,如果 key 类型错误,抛出TypeError ,如果没法返回key对应的数值时,该方法应该抛出ValueError:需要实现 getitem(self, key)

  • 当执行 self[key] = value 时:调用是 setitem(self, key, value)这个方法

  • 当执行 self[key] = value 时:调用是 setitem(self, key, value)这个方法

  • 当执行 del self[key] 方法:其实调用的方法是 delitem(self, key)

  • 当你想你的容器可以执行 for x in container: 或者使用 iter(container) 时:需要实现 iter(self) ,该方法返回的是一个迭代器

实现 Haskell 语言中的一个数据结构:

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]

运算符相关的魔术方法

1、比较运算符

  • 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):定义了比较操作符 >= 的行为

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

2、算术运算符

  • 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):实现了位操作 ^

留言