Python(十)Python 的 Magic Method
来源:互联网 发布:kvm centos虚拟化 编辑:程序博客网 时间:2024/05/21 06:32
Python 的 Magic Method
在 Python 中,所有以 “” 双下划线包起来的方法,都统称为”魔术方法”。比如我们接触最多的 __init__
。魔术方法有什么作用呢?
使用这些魔术方法,我们可以构造出优美的代码,将复杂的逻辑封装成简单的方法。
那么一个类中有哪些魔术方法呢?
我们可以使用 Python 内置的方法 dir() 来列出类中所有的魔术方法.示例如下:
# encoding=utf-8class User(object): passif __name__ == '__main__': print(dir(User()))
输出的结果:
可以看到,一个类的魔术方法还是挺多的,截图也没有截全,不过我们只需要了解一些常见和常用的魔术方法就好了。
构造(__new__)和初始化(__init__)
通过上一篇的内容,我们已经知道定义一个类时,我们经常会通过 __init__(self) 的方法在实例化对象的时候,对属性进行设置。比如下面的例子:
# encoding=utf-8class User(object): def __init__(self, name, age): self.name = name; self.age = age;user=User('两点水',23)
实际上,创建一个类的过程是分为两步的,一步是创建类的对象,还有一步就是对类进行初始化。__new__
是用来创建类并返回这个类的实例, 而__init__
只是将传入的参数来初始化该实例。__new__
在创建一个实例的过程中必定会被调用,但 __init__
就不一定,比如通过pickle.load 的方式反序列化一个实例时就不会调用 __init__
方法。
def __new__(cls)
是在 def __init__(self)
方法之前调用的,作用是返回一个实例对象。还有一点需要注意的是:__new__
方法总是需要返回该类的一个实例,而 __init__
不能返回除了 None
的任何值
具体的示例:
# encoding=utf-8class 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 = ageif __name__ == '__main__': usr = User('两点水', 23)
看看输出的结果:
调用了 def __new__ 方法('两点水', 23)调用了 def __init__ 方法
通过打印的结果来看,我们就可以知道一个类创建的过程是怎样的了,先是调用了 __new__
方法来创建一个对象,把参数传给 __init__
方法进行实例化。
其实在实际开发中,很少会用到 __new__
方法,除非你希望能够控制类的创建。通常讲到 __new__
,都是牵扯到 metaclass(元类)的。
当然当一个对象的生命周期结束的时候,析构函数 __del__
方法会被调用。但是这个方法是 Python 自己对对象进行垃圾回收的。
属性的访问控制
之前也有讲到过,Python 没有真正意义上的私有属性。然后这就导致了对 Python 类的封装性比较差。我们有时候会希望 Python 能够定义私有属性,然后提供公共可访问的 get 方法和 set 方法。Python 其实可以通过魔术方法来实现封装。
通过上面的方法表可以知道,在进行属性访问控制定义的时候你可能会很容易的引起一个错误,可以看看下面的示例:
# encoding=utf-8def __setattr__(self, name, value): self.name = value # 每当属性被赋值的时候, ``__setattr__()`` 会被调用,这样就造成了递归调用。 # 这意味这会调用 ``self.__setattr__('name', value)`` ,每次方法会调用自己。这样会造成程序崩溃。def __setattr__(self, name, value): # 给类中的属性名分配值 self.__dict__[name] = value # 定制特有属性
上面方法的调用具体示例如下:
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 等字段, 就是通过描述器来实现功能的。
我们先看下下面的例子:
# encoding=utf-8class 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 = valclass MyClass(object): x = User('两点水', '男') y = 5if __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__()
这些方法的调用。
再看一个经典的例子
我们知道,距离既可以用单位”米”表示,也可以用单位”英尺”表示。
现在我们定义一个类来表示距离,它有两个属性: 米和英尺。
# encoding=utf-8class 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.2808class 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.01.0 3.28082.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 是可变容器。 可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。当然具体的介绍,可以看回之前的文章,有图文介绍。
那么这里先提出一个问题,这些数据结构就够我们开发使用吗?不够的时候,或者说有些特殊的需求不能单单只使用这些基本的容器解决的时候,该怎么办呢?
这个时候就需要自定义容器了,那么具体我们该怎么做呢?
来看一下使用上面魔术方法实现 Haskell 语言中的一个数据结构:
# encoding=utf-8class 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就大概列举下面两类:
比较运算符
来看个简单的例子就能理解了:
# encoding=utf-8class 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.valueif __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
算术运算符
原文链接
本文转载只为技术交流,若侵权,请邮件至shuzhuchengfu@163.com联系我下架本文!
- Python(十)Python 的 Magic Method
- 草根学Python(十)Python 的 Magic Method
- python magic method
- Python 魔术方法(Magic Method)
- Python 魔术方法(Magic Method)
- Python魔术方法-Magic Method
- 飘逸的python - 什么是魔术方法(magic method)
- 一个python实现的有魔法方法(magic method)的单链表(静态链表)
- Python 的 Magic Methods 指南
- Python 的 Magic Methods 指南
- Python 的 Magic Methods 指南
- Python 的 Magic Methods 指南
- Python的method, class method, static method
- Python下的Magic特殊方法
- Python 的 Magic Methods 指南(转)
- 开始Python -- Magic方法(1)
- 开始Python -- Magic方法(2)
- python magic methods
- 深入浅出Mybatis技术原理与实战: 读书笔记
- webSocket长连接实现demo(场景:扫码自动跳转登录,或者替换轮询)
- Maximum Width of Binary Tree问题及解法
- 必须知道的Android屏幕自适应解决方案
- viewwillappear和viewdidload的区别是什么
- Python(十)Python 的 Magic Method
- TreeSet、Comparable、Comparator、内部类、匿名类
- 腾讯云直播流程及腾讯云通讯功能整理
- 人工智能热浪中,直播行业也被撞了一下腰?
- wangEditor 基于javascript和css开发的 Web富文本编辑器, 轻量、简洁、易用、开源免费
- 关于List、Set、Map集合的简介(二)
- 成员变量自动初始化,局部变量必须手动初始化
- 通过pthread_mutex_lock和pthread_cond_wait实现生产消费模式,并且生产一次消费一次
- 第一次开发EJB报表(MES)