前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python进阶教程笔记(三)类的特殊方法

Python进阶教程笔记(三)类的特殊方法

作者头像
Lemon黄
发布2020-10-30 11:42:36
8870
发布2020-10-30 11:42:36
举报
文章被收录于专栏:Lemon黄Lemon黄

一、什么是特殊方法

  • 双下划线开头
  • 双下划线结尾
  • 每个Python对象都拥有特殊方法
  • 常见特殊方法
代码语言:javascript
复制
__str__(), __add__(), __sub__(), __mul__(), __truediv__(), __len__(),
__new__(), __init__(), __del__(), __repr__(), __bytes__(), __format__(),
__lt__(), __le__(), __eq__(), __ne__(), __gt__(), __ge__(), __hash__(),
__bool__(), __dir__(), __set__(), __call__(), __slots__(), ...

二、类的__str__ 和 __repr__方法

对于Python的内建对象,比如int、dict、list等,通过str()方法,可以把这些对象转换为字符串对象输出。

代码语言:javascript
复制
num = 12
str(num) # ==> '12'
d = {1: 1, 2: 2}
str(d) # ==> '{1: 1, 2: 2}'
l = [1,2,3,4,5]
str(l) # ==> '[1, 2, 3, 4, 5]'

对于自定义对象,通过str()方法,同样可以得到对象所对应的字符串结果,只不过结果会有些难理解。

代码语言:javascript
复制
class Person:
    pass

bob = Person()
str(bob) # ==> '<__main__.Person object at 0x7fc77b859c50>'

<__main__.Person object at 0x7fc77b859c50>这个结果其实是Animal的实例cat在内存中的地址,这是相当难以理解的,不过引发思考的是,通过str()打印的数据,是怎么来的呢?

这其实是对象的内建方法__str__返回的。 通过dir()方法,我们可以把对象的所有方法打印出来。

代码语言:javascript
复制
>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

可以看到,int、dict、list等的内建对象都实现了自己的__str__()方法,可以把相应的字符串返回,如果我们的类也想把容易理解的字符串输出的话,那么我们也需要实现类的__str__()方法。

代码语言:javascript
复制
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return 'name: {}, gender: {}'.format(self.name, self.gender)

bob = Person('Bob', 'Male')
str(bob) # ==> 'name: Bob, gender: Male'

但是,对于直接在终端输入变量bob,得到的依然是这样结果。

代码语言:javascript
复制
>>> bob
<__main__.Person object at 0x7fc77b859cc0>

而对于int、list等的对象,直接输入变量也可得到可读的结果。

代码语言:javascript
复制
>>> num = 12
>>> str(num)
'12'
>>> d = {1: 1, 2: 2}
>>> d
{1: 1, 2: 2}

__str__()函数似乎没有在自定义类Person中生效,这是为什么呢?

这是因为 Python 定义了__str()____repr__()两种方法,__str()__用于显示给用户,而__repr__()用于显示给开发人员,当使用str()时,实际调用的是__str__()方法,而直接输入变量,调用的是__repr__()方法。

代码语言:javascript
复制
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __str__(self):
        return 'name: {}, gender: {}'.format(self.name, self.gender)
    def __repr__(self):
        return 'name: {}, gender: {}'.format(self.name, self.gender)

bob = Person('Bob', 'Male')
str(bob) # ==> 'name: Bob, gender: Male'
>>> bob
'name: Bob, gender: Male'

三、类的__len__方法

对于列表List或者元组Tuple,通过内建方法len(),可以得出列表或者元组中元素的个数。如果一个类表现得像一个list,想使用len()函数来获取元素个数时,则需要实现len()方法。

比如我们实现一个班级Class的类,初始化把班级的同学名字列表传进去,希望len()函数可以返回班级同学的数量时,可以这样实现。

代码语言:javascript
复制
class Class:
    def __init__(self, students):
        self.students = students
    def __len__(self):
        return len(self.students)

students = ['Alice', 'Bob', 'Candy']
class_ = Class(students)
len(class_) # ==> 3

通过自定义__len__()方法,可以让len()函数返回相关的结果,如果没有定义__len__()方法的类使用len()函数获取长度时,将会引起异常。

代码语言:javascript
复制
class Class:
    def __init__(self, students):
        self.students = students

class_ = Class(students)
len(class_)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'Class' has no len()

四、类的数学运算

事实上,Python很多的操作都是通过内建函数来实现的,比如最熟悉的加减乘除,都是通过内建函数来实现的,分别是__add__、__sub__、__mul__、__truediv__。因此,只要我们的自定义类实现了相关的内建函数,我们的类对象,也可以做到加减乘除。

对于有理数,我们可以使用Rational类来表示:

代码语言:javascript
复制
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q

其中,p、q 都是整数,表示有理数 p/q。 如果要让Rational进行加法运算,需要正确实现__add__

代码语言:javascript
复制
class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __add__(self, r):
        return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
    def __str__(self):
        return '{}/{}'.format(self.p, self.q)

定义好后,就可以尝试一下有理数的加法了:

代码语言:javascript
复制
>>> r1 = Rational(1, 2)
>>> r2 = Rational(2, 3)
>>> print(r1 + r2)
7/6

需要注意__add__()函数,它有一个参数,表示的是运算的第二个操作数,比如:r1 + r2,那么在add()方法中的参数,r指的就是r2,这个参数是运算符重载的时候传递的。

另外,细心的同学可能注意到了,相比加减乘的特殊方法,除法的特殊方法名字较长__truediv__,并且含有true这样的描述,这其实和Python除法是有关系的。

Python的除法可以分为地板除(你没看错,就是地板)和普通除法,地板除的特殊方法是__floordiv__,普通除法是__truediv__

地板除法和普通除法不一样,地板除法的结果只会向下取整数。

代码语言:javascript
复制
>>> num = 5
>>> num.__truediv__(3)
1.6666666666666667
>>> num.__floordiv__(3)
1 # 向下取整
>>> num = 7
>>> num.__floordiv__(3)
2

在运算中,普通除法使用/表示,而地板除使用//表示。

代码语言:javascript
复制
>>> 5 / 3
1.6666666666666667
>>> 5 // 3
1

五、类的__slots__方法

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。比如:

代码语言:javascript
复制
class Student(object):
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

此时,Student类有三个属性,name、gender、score,由于是动态语言,在运行时,可以随意添加属性。

代码语言:javascript
复制
student = Student('Bob', 'Male', 99)
student.age = 12 # ==> 动态添加年龄age属性

如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__slots__来实现。

代码语言:javascript
复制
class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

使用__slots__ = ('name', 'gender', 'score')限定Student类的属性,这个时候在外部再次添加动态属性age,将会报错。

代码语言:javascript
复制
student = Student('Bob', 'Male', 99)
>>> student.age = 12 # ==> 动态添加年龄age属性
Traceback (most recent call last):
AttributeError: 'Student' object has no attribute 'age'

__slots__的目的是限制当前类所能拥有的属性,避免因为外部属性的操作导致类属性越来越难以管理。

六、类的__call__方法

在Python中,函数其实是一个对象,我们可以将一个函数赋值给一个变量,而不改变函数的功能。

代码语言:javascript
复制
>>> f = abs
>>> f
<built-in function abs>
>>> abs
<built-in function abs>
>>> f.__name__
'abs'
>>> f(-123)
123

把内建函数abs()赋值给变量f之后,可以看到f就和abs一样,都是。 由于 f 可以被调用,所以,f 被称为可调用对象,而事实上,所有的函数都是可调用对象。

如果把一个类实例也变成一个可调用对象,可以实现一个特殊的方法__call__()

例如,我们把Person类变成一个可调用对象:

代码语言:javascript
复制
class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print('My name is {}...'.format(self.name))
        print('My friend is {}...'.format(friend))

接着我们初始化一个Person对象,并对这个对象通过函数的方式调用:

代码语言:javascript
复制
>>> p = Person('Bob', 'Male')
>>> p('Alice') # ==> 用函数的方式调用Person类的实例p
My name is Bob...
My friend is Alice...
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Lemon黄 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、什么是特殊方法
  • 二、类的__str__ 和 __repr__方法
  • 三、类的__len__方法
  • 四、类的数学运算
  • 五、类的__slots__方法
  • 六、类的__call__方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档