一文带你了解Python中的双下方法

目录
  • 前言
  • 1. init方法
  • 2. 运算符的双下方法
    • 2.1 比较运算符
    • 2.2 算术运算符
    • 2.3 反向算术运算符
    • 2.4 增量赋值运算符
    • 2.4 位运算符
  • 3.字符串表示
  • 4.数值转换
  • 5.集合相关的双下方法
  • 6.迭代相关的双下方法
  • 7.类相关的双下方法
    • 7.1 实例的创建和销毁
    • 7.2 属性管理
    • 7.3 属性描述符
  • 8.总结

前言

大家在写 Python 代码的时候有没有这样的疑问。

为什么数学中的+号,在字符串运算中却变成拼接功能,如'ab' + 'cd'结果为abcd;而*号变成了重复功能,如'ab' * 2结果为abab

为什么某些对象print能输出数据,而print自定义的类对象却输出一堆看不懂的代码<__main__.MyCls object at 0x105732250>

不是因为系统做了特殊定制,而是 Python 中有一类特殊的方法,在某些特定的场合会自动调用。如,在字符串类str中定义了__add__方法后,当代码遇到字符串相加'ab' + 'cd'时,就会自动调用__add__方法完成字符串拼接。

因为这类特殊方法的方法名都是以双下划线开始和结束,所以又被称为双下方法。

Python 中的双下方法很多,今天我们对它做个详解。

Python中的双下方法

1. init方法

__init__的方法是很多人接触的第一个双下方法

class A:
    def __init__(self, a):
        self.a = a

当调用A()实例化对象的时候,__init__方法会被自动调用,完成对象的初始化。

2. 运算符的双下方法

在类中定义运算符相关的双下方法,可以直接在类对象上做加减乘除、比较等操作。

这里,定义一个尺子类Rule,它包含一个属性r_len代表尺子的长度。

class Rule:
    def __init__(self, r_len):
        self.r_len = r_len

2.1 比较运算符

如果想按照尺子的长度对不同的尺子做比较,需要在Rule类中定义比较运算符。

class Rule:
    def __init__(self, r_len):
        self.r_len = r_len

    # < 运算符
    def __lt__(self, other):
        return self.r_len < other.r_len

    # <= 运算符
    def __le__(self, other):
        return self.r_len <= other.r_len

    # > 运算符
    def __gt__(self, other):
        return self.r_len > other.r_len

    # >= 运算符
    def __ge__(self, other):
        return self.r_len >= other.r_len

这里定义了<<=>>=四个比较运算符,这样就可以用下面的代码比较Rule对象了。

rule1 = Rule(10)
rule2 = Rule(5)
print(rule1 > rule2)  # True
print(rule1 >= rule2)  # True
print(rule1 < rule2)  # False
print(rule1 <= rule2)  # False

当用>比较rule1rule2的时候,rule1对象会自动调用__gt__方法,并将rule2对象传给other参数,完成比较。

下面是比较运算符的双下方法

比较运算符双下方法

2.2 算术运算符

可以支持类对象加减乘除。

def __add__(self, other):
    return Rule(self.r_len + other.r_len)

这里定义了__add__方法,对应的是+运算符,他会把两个尺子的长度相加,并生成新的尺子。

rule1 = Rule(10)
rule2 = Rule(5)
rule3 = rule1 + rule2

下面是算术运算符的双下方法

2.3 反向算术运算符

它支持其他类型的变量与Rule类相加。以__radd__方法为例

def __radd__(self, other):
    return self.r_len + other
rule1 = Rule(10)
rule2 = 10 + rule1

程序执行10 + rule1时,会尝试调用int类的__add__int类类没有定义与Rule类对象相加的方法,所以程序会调用+号右边对象rule1__radd__方法,并把10传给other参数。

所以这种运算符又叫右加运算符。它所支持的运算符与上面的算术运算符一样,方法名前加r即可。

2.4 增量赋值运算符

增量赋值运算符是+=-=*=/=等。

def __iadd__(self, other):
    self.r_len += other
    return self
rule1 = Rule(10)
rule1 += 5

除了__divmod__方法,其他的跟算数运算符一样,方面名前都加i。

2.4 位运算符

这部分支持按二进制进行取反、移位和与或非等运算。由于Rule类不涉及位运算,所以我们换一个例子。

定义二进制字符串的类BinStr,包含bin_str属性,表示二进制字符串。

class BinStr:
    def __init__(self, bin_str):
        self.bin_str = bin_str
x = BinStr('1010')  #创建二进制字符串对象
print(x.bin_str) # 1010

BinStr定义一个取反运算符~

# ~ 运算符
def __invert__(self):
    inverted_bin_str = ''.join(['1' if i == '0' else '0' for i in self.bin_str])
    return BinStr(inverted_bin_str)

__invert__方法中,遍历bin_str字符串,将每位取反,并返回一个新的BinStr类对象。

x = BinStr('1011')

invert_x = ~x
print(invert_x.bin_str) # 0100

下面是位运算符的双下方法

这部分也支持反向位运算符和增量赋值位运算符,规则跟算数运算符一样,这里就不再赘述。

3.字符串表示

这部分涉及两个双下方法__repr____format__,在某些特殊场景,如print,会自动调用,将对象转成字符串。

还是以BinStr为例,先写__repr__方法。

def __repr__(self):
    decimal = int('0b'+self.bin_str, 2)
    return f'二进制字符串:{self.bin_str},对应的十进制数字:{decimal}'
x = BinStr('1011')
print(x)
# 输出:二进制字符串:1011,对应的十进制数字:11

当程序执行print(x)时,会自动调用__repr__方法,获取对象x对应的字符串。

再写__format__方法,它也是将对象格式化为字符串。

def __format__(self, format_spec):
    return format_spec % self.bin_str
print('{0:二进制字符串:%s}'.format(x))
# 输出:二进制字符串:1011

.format方法的前面字符串里包含0:时,就会自动调用__format__方法,并将字符串传给format_spec参数。

4.数值转换

调用int(obj)float(obj)等方法,可以将对象转成相对应数据类型的数据。

def __int__(self):
    return int('0b'+self.bin_str, 2)
x = BinStr('1011')
print(int(x))

当调用int(x)时,会自动调用__int__方法,将二进制字符串转成十进制数字。

数值转换除了上面的两个外,还有__abs____bool____complex____hash____index____str__

__str____repr__一样,在print时都会被自动调用,但__str__优先级更高。

5.集合相关的双下方法

这部分可以像集合那样,定义对象长度、获取某个位置元素、切片等方法。

__len____getitem__为例

def __len__(self):
    return len(self.bin_str)

def __getitem__(self, item):
    return self.bin_str[item]
x = BinStr('1011')

print(len(x))  # 4
print(x[0])  # 1
print(x[0:3])  # 101

len(x)会自动调用__len__返回对象的长度。

通过[]方式获取对象的元素时,会自动调用__getitem__方法,并将切片对象传给item参数,即可以获取单个元素,还可以获取切片。

集合相关的双下方法还包括__setitem____delitem____contains__

6.迭代相关的双下方法

可以在对象上使用for-in遍历。

def __iter__(self):
    self.cur_i = -1
    return self

def __next__(self):
    self.cur_i += 1
    if self.cur_i >= len(self.bin_str):
        raise StopIteration()  # 退出迭代
    return self.bin_str[self.cur_i]
x = BinStr('1011')
for i in x:
    print(i)

当在x上使用for-in循环时,会先调用__iter__方法将游标cur_i置为初始值-1,然后不断调用__next__方法遍历self.bin_str中的每一位。

这部分还有一个__reversed__方法用来反转对象。

def __reversed__(self):
    return BinStr(''.join(list(reversed(self.bin_str))))
x = BinStr('1011')
reversed_x = reversed(x)
print(reversed_x)
# 输出:二进制字符串:1101,对应的十进制数字:13

7.类相关的双下方法

做 web 开发的朋友,用类相关的双下方法会更多一些。

7.1 实例的创建和销毁

实例的创建是__new____init__方法,实例的销毁是__del__方法。

__new__的调用早于__init__,它的作用是创建对象的实例(内存开辟一段空间),而后才将该实例传给__init__方法,完成实例的初始化。

由于__new__是类静态方法,因此它可以控制对象的创建,从而实现单例模式

__del__方法在实例销毁时,被自动调用,可以用来做一些清理工作和资源释放的工作。

7.2 属性管理

类属性的访问和设置。包括__getattr____getattribute____setattr____delattr__方法。

__getattr____getattribute__的区别是,当访问类属性时,无论属性存不存在都会调用__getattribute__方法,只有当属性不存在时才会调用__getattr__方法。

7.3 属性描述符

控制属性的访问,一般用于把属性的取值控制在合理范围内。包括__get____set____delete__方法。

class XValidation:
    def __get__(self, instance, owner):
        return self.x

    def __set__(self, instance, value):
        if 0 <= value <= 100:
            self.x = value
        else:
            raise Exception('x不能小于0,不能大于100')

    def __delete__(self, instance):
        print('删除属性')

class MyCls:
    x = XValidation()

    def __init__(self, n):
        self.x = n

obj = MyCls(10)
obj.x = 101
print(obj.x) # 抛异常:Exception: x不能小于0,不能大于100

上述例子,通过类属性描述符,可以将属性x的取值控制在[0, 100]之前,防止不合法的取值。

8.总结

虽然上面介绍的不是所有的双下方法,但也算是绝大多数了。

虽然双下方法里可以编写任意代码,但大家尽量编写与方法要求一样的代码。如,在__add__方法实现的不是对象相加而是相减,虽然也能运行,但这样会造成很大困惑,不利于代码维护。

到此这篇关于一文带你了解Python中的双下方法的文章就介绍到这了,更多相关Python双下方法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python中单、双下划线的区别总结

    前言 Python 的代码风格由 PEP 8 描述.这个文档描述了 Python 编程风格的方方面面.在遵守这个文档的条件下,不同程序员编写的 Python 代码可以保持最大程度的相似风格.这样就易于阅读,易于在程序员之间交流. 我们大家在学习Python的时候,好像很多人都不理解为什么在方法(method)前面会加好几个下划线,有时甚至两边都会加,比如像__this__这种.在我看到上面的文章之前,我一直以为Python中这些下划线的作用就像Golang中方法/函数的大小写一样,或是一些其他语

  • Python中的单下划线和双下划线使用场景详解

    单下划线 单下划线用作变量 最常见的一种使用场景是作为变量占位符,使用场景明显可以减少代码中多余变量的使用.为了方便理解,_可以看作被丢弃的变量名称,这样做可以让阅读你代码的人知道,这是个不会被使用的变量,e.g.. for _, _, filenames in os.walk(targetDir): print(filenames) for _ in range(100): print('PythonPoint') 在交互解释器比如iPython中,_变量指向交互解释器中最后一次执行语句的返回

  • python常用的魔法方法(双下划线)

    目录 前言 魔法方法 __init__方法 __new__方法 __call__方法 __str___方法 __del___方法 __enter__ & __exit__方法 item系列方法 attr系列方法 单例模式 模块导入的方式 通过__new__方法 自定义元类的方式 结语 前言 本文介绍一下python中常用的魔法方法以及面向对象中非常重要的单例模式. 魔法方法 python中一切皆对象,因为python是面向对象的编程语言.python给类和对象提供了大量的内置方法,这些内置方法也

  • Python3中_(下划线)和__(双下划线)的用途和区别

    在看一些Python开源代码时,经常会看到以下划线或者双下划线开头的方法或者属性,到底它们有什么作用,又有什么样的区别呢?今天我们来总结一下(注:下文中的代码在Python3下测试通过) _ 的含义 在python的类中没有真正的私有属性或方法,没有真正的私有化. 但为了编程的需要,我们常常需要区分私有方法和共有方法以方便管理和调用.那么在Python中如何做呢? 一般Python约定加了下划线 _ 的属性和方法为私有方法或属性,以提示该属性和方法不应在外部调用,也不会被from ModuleA

  • python中单下划线(_)和双下划线(__)的特殊用法

    函数使用单下划线_开头 使用单下划线(_)开头的函数_func不能被模块外部以: from module import *形式导入. 但可以用:from module import _func形式单独导入. 类属性和类方法使用单下划线_开头 _开头为保护类型的属性和方法,仅允许类内部和子类访问,类实例无法访问此属性和方法. 类属性和类方法使用双下划线__开头 __开头为私有类型属性和方法,仅允许类内部访问,类实例和派生类均不能访问此属性和方法. 所以双划线比单划线权限更严格. 补充说明 对于__

  • 一文带你了解Python中的双下方法

    目录 前言 1. init方法 2. 运算符的双下方法 2.1 比较运算符 2.2 算术运算符 2.3 反向算术运算符 2.4 增量赋值运算符 2.4 位运算符 3.字符串表示 4.数值转换 5.集合相关的双下方法 6.迭代相关的双下方法 7.类相关的双下方法 7.1 实例的创建和销毁 7.2 属性管理 7.3 属性描述符 8.总结 前言 大家在写 Python 代码的时候有没有这样的疑问. 为什么数学中的+号,在字符串运算中却变成拼接功能,如'ab' + 'cd'结果为abcd:而*号变成了重

  • 一文带你了解C++中的字符替换方法

    目录 一.单个字符替换 1.1 std::replace 1.2 使用循环手动替换 1.3 使用正则表达式库(例如,std::regex_replace) 二.字符串替换 2.1 实用字符串流 2.2 使用字符数组 2.3 使用 STL 的算法:std::replace 2.4 使用正则表达式 三.总结 一.单个字符替换 1.1 std::replace 代码示例: #include <algorithm> // ... std::string str = "Hello, World

  • Python中使用双下划线防止类属性被覆盖问题

    在使用Python编写面向对象的代码时,我们会常常使用"继承"这种开发方式.例如下面这一段代码: class Info: def __init__(self): pass def calc_age(self): print('我是父类的方法') class PeopleInfo(Info): def __init__(self): super().__init__() def calc_age(self): print(123456) 如果你使用 PeopleInfo 初始化一个对象,

  • 一文带你了解Python中的字符串是什么

    在< 详解Python拼接字符串的七种方式 >这篇文章里,我提到过,字符串是程序员离不开的事情.后来,我看到了一个英文版本的说法: There are few guarantees in life: death, taxes, and programmers needing to deal with strings. 它竟然把程序员处理字符串跟死亡大事并列了,可见这是多么命中注定-- 回头看其它文章,我发现这种说法得到了佐证,因为我在无意中已零零碎碎地提及了字符串的很多方面,例如:字符串读写文

  • 一文带你解决Python中的所有报错

    目录 前言 Python安装 HTTPSConnectionPool(host=‘files.pythonhosted.org‘, port=443): Read timed out解决 xlrd.biffh.XLRDError: Excel xlsx file; not supported解决 Fatal error in launcher: Unable to create process using解决 报错Non-zero exit code (2)解决 [notice] A new r

  • 一文带你了解Python 四种常见基础爬虫方法介绍

    一.Urllib方法 Urllib是python内置的HTTP请求库 import urllib.request #1.定位抓取的url url='http://www.baidu.com/' #2.向目标url发送请求 response=urllib.request.urlopen(url) #3.读取数据 data=response.read() # print(data) #打印出来的数据有ASCII码 print(data.decode('utf-8')) #decode将相应编码格式的

  • 一文带你解密Python可迭代对象的排序问题

    假设有一个可迭代对象,现在想要对它内部的元素进行排序,我们一般会使用内置函数 sorted,举个例子: data = (3, 4, 1, 2, 5) print(     sorted(data) )  # [1, 2, 3, 4, 5] data = (3.14, 2, 1.75) print(     sorted(data) )  # [1.75, 2, 3.14] data = ["satori", "koishi", "marisa"]

  • 一文带你了解Python列表生成式应用的八重境界

    目录 1. 引言 2. Level1: 基础用法 3. Level2: 加入条件语句 4. Level3: 加入 enumerate() 5. Level4: 加入 zip() 6. Level5: 加入三目运算符 7. Level6: 嵌套循环 8. Level7: 嵌套列表生成式 9. Level8: 合并上述所有技巧 10. 应用栗子 11. 总结 1. 引言 在Python中有非常多且好用的技巧,其中使用最多的是列表生成式,往往可以将复杂的逻辑用简单的语言来实现,本文重点介绍列表生成式应

  • 10个示例带你掌握python中的元组

    数据结构是任何编程语言的关键部分.为了创建强大而性能良好的产品,必须非常了解数据结构. 在本文中,我们将研究Python编程语言的重要数据结构,元组. 元组是用逗号分隔并括在括号中值的集合.与列表不同,元组的元素是不可变的.不变性可以视为元组的识别特征. 我将通过示例解释元组的功能和对其的操作. 一.创建元组 元组由括号中的值组成,并用逗号分隔开 a=(3,4) print (a) print (type(a)) # (3, 4) # <class 'tuple'> 元组可以存储不同数据类型的

  • 一分钟带你掌握Python中pip的安装与使用方法

    目录 一.简单介绍 二.下载安装 三.最常用命令 1.显示版本和路径 2.获取帮助 3.升级 pip 4.安装包 5.升级包 6.卸载包 7.搜索包 8.显示安装包信息 9.列出已安装的包 10.查看指定包的详细信息 一.简单介绍 pip 是 Python 包管理工具,该工具提供了对Python 包的查找.下载.安装和卸载的功能,现在大家用到的所有包不是自带的就是通过pip安装的.Python 2.7.9 + 或 Python 3.4+ 以上版本都自带 pip 工具.给出pip官网链接:pip官

随机推荐