Python3 学习笔记6-魔法方法

目录

一、构造和析构

1. __init__()方法:构造器,当一个实例对象被定义时调用

2. __new__()方法

3. __del__()方法:析构器,当删除一个实例对象时调用

(魔法方法汇总可点击链接)

https://www.cnblogs.com/Jimmy1988/p/6801795.html

二、算术运算

1. 工厂函数:即类对象,调用时会创建一个相应的实例对象

2. 常见算术运算

3. 反运算

4. 一元操作符

三、小练习:计时器

四、属性访问

五、描述符

六、定制容器

七、迭代器 和 生成器

八、其他

一、构造和析构

1. __init__()方法:构造器,当一个实例对象被定义时调用

(1)对象初始化的方法,只有需要初始化的时候才重写该方法

(2)括号内默认参数 self 为 Python 自动添加,其为 __new__() 返回的实例

(3)返回值为 None,即不需要返回值,不能是其他

>>> class Rectangular:
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     def getPeri(self):
...         return (self.x + self.y) * 2
...     def getArea(self):
...         return self.x * self.y

>>> rect = Rectangular(3, 4)
>>> rect.getPerl()
14
>>> rect.getArea()
12

2. __new__()方法

(1)对象实例化时嗲用的首个方法

(2)括号内至少有一个参数 cls,代表实例化的类

(3)需要返回值,通常是 类实例化的对象,也可以返回其他对象

(4)当类需要继承一个不可变的类型时,需要重新该方法

>>> class CapStr(str):
...     def __new__(cls, string):
...         string = string.upper()
...         return str.__new__(cls, string)

>>> a = CapStr("I can not quite understand the method")
>>> a
'I CAN NOT QUITE UNDERSTAND THE METHOD'

3. __del__()方法:析构器,当删除一个实例对象时调用

(1)不同于上述两个构造方法构造当前类的实例对象,__del__() 方法用来销毁实例化对象

(2)当一个对象的引用计数为 0 的时候会自动调用 __del__() 方法释放这个对象占用的内存

>>> import sys

>>> class C:
...     def __init__(self):
...         print("调用了 __init__() 方法")
...     def __del__(self):
...         print("调用了 __del__() 方法")
>>> c1 = C()
'调用了 __init__() 方法'
>>> sys.getrefcount(c1)
2    # 为什么显示引用了2次
>>> c2 = c1
>>> c3 = c2
>>> del c1
>>> del c2
>>> del c3
'调用了 __del__() 方法'

(魔法方法汇总可点击链接)

https://www.cnblogs.com/Jimmy1988/p/6801795.html

二、算术运算

1. 工厂函数:即类对象,调用时会创建一个相应的实例对象

# int()、 float()、 str()、 list()、 tuple() 等 BIF 在 Python2.2 后转换为 工厂函数
# 类型同“类”一样。

>>> type(len)
<class 'builtin_function_or_method'>

>>> type(int)
<class 'type'>

>>> class C:
...     pass
>>> type(C)
<class 'type'>

>>> a = int('123')
>>> a
123
>>> b = int('456')
>>> a + b
579

2. 常见算术运算

举例:

# 常规写法
>>> class New_int(int):
...     def __add__(self, other):
...         return int.__add__(self, other)
...     def __sub__(self, other):
...         return int.__sub__(self, other)

>>> a = New_int(2)
>>> b = New_int(3)
>>> a + b
5
>>> a - b
-1

# 自定义时需注意返回值
>>> class Test_int(int):
...     def __add__(self, other):
            # 该写法会导致递归,
            # 即 返回对象 + 另一个对象 将重新调用 __add__(self, other) 方法
...         return self + other    
...     def __sub__(self, other):
...         return self - other
>>> a = Test_int(2)
>>> b = Test_int(3)
>>> a + b
Traceback (most recent call last):
  File "<pyshell#64>", line 1, in <module>
    a + b
  File "<pyshell#61>", line 3, in __add__
    return self + other
  File "<pyshell#61>", line 3, in __add__
    return self + other
  File "<pyshell#61>", line 3, in __add__
    return self + other
  [Previous line repeated 1022 more times]
RecursionError: maximum recursion depth exceeded

# 可改写成此种形式
>>> class Test_int(int):
...     def __add__(self, other):
...         return int(self) + int(other)
...     def __sub__(self, other):
...         return int(self) - int(other)
>>> a = Test_int(2)
>>> b = Test_int(3)
>>> a + b
5

3. 反运算

例如:

>>> class Nint(int):
...     def __radd__(self, other):
...         # 注重操作数顺序的运算符(如:减法、除法 和 位移 等),重写反运算魔法方法时要注意顺序
...         return int.__sub__(other, self)    # 注:other 和 self 不能写反
>>> a = Nint(2)
>>> b = Nint(3)
# 这里加数是 a,被加数是 b,因此是 a 主动
# 对于 a+b,b 的 __radd__(self,other) 的 self 是 b 的对象,other 是 a 的对象
>>> a + b
5
# 反运算:如果 a 对象的 __add__() 方法没有实现或者不支持相应的操作
# 那么 Python 就会调用 b 的 __radd__() 方法
>>> 1 + b
-2

4. 一元操作符

(1)__neg__():一元取负算术运算符

(2)__pos__():一元取正算术运算符

(3)__abs__():返回数字绝对值

(4)__invert__():整数按位取反,定义为 ~x == -(x+1)

>>> a = 123
>>> a.__invert__()
-124

三、小练习:计时器

要求:(1)创建一个代表计时器的类

          (2)定义两种方法,分别表示计时开始和结束

          (3)输入实例化对象 t1 或 print(t1) 都能正确显示结果

          (4)当未运行计时器或终止计时器时,会有人性化提醒

          (5)两个计时器的实例化对象可相加

          (6)计时器只考虑到秒

>>> import time as tm

>>> class MyTimer:
...     # 重写 __init__() 方法,保证未采用 MyTimer 内部的方法时,程序有提醒
...     def __init__(self):
...         # 指定计时器的单位
...         self.unit = ['年', '月', '天', '时', '分', '秒']
...         # 指定计时器的单位进制
...         self.borrow = [0, 12, 31, 24, 60, 60]
...         # 设定未使用内部方法时的提醒信息
...         self.prompt = "MyTimer is not running."
...         # 初始化计时器的持续运行时间
...         self.lasted = []
...         # 若采用 self.start = 0 ,则属性和方法同名,此时属性会覆盖方法
...         self.begin = 0
...         self.end = 0
...      
...     # 若添加 __repr__ = __str__ 代码,则只输入实例化对象时,不再会显示对象的内存地址
...     # 也可直接采用 def __repr__(self): return self.prompt 的方法
...     def __str__(self):
...         return self.prompt
...     __repr__ = __str__
... 
...     # 设计两个计时器对象可以相加
...     def __add__(self, other):
...         result = []
...         prompt = "Running time: "
...         for i in range(6):
...             result.append(self.lasted[i] + other.lasted[i])
...             if result[i]:
...                 prompt += (str(result[i]) + self.unit[i])
...         return prompt
...  
...     # 计时器开始
...     def start(self):
...         # 采用 time 模块的 localtime() 函数,返回一个时间元组,只保留前6个元素
...         self.begin = tm.localtime()
...         # 若调用 start 方法后未调用 stop 方法,则输出当前 self.prompt 的内容
...         self.prompt = "Please call method 'stop'."
...         print("MyTimer running...")
... 
...     # 计时器终止
...     def stop(self):
...         # 若先调用 stop 方法,未调用 start 方法,则输出提醒信息
...         if not self.begin:
...             print("Please call method 'start' first.")
...         else:
...             self.end = tm.localtime()
...             self._calc()
...             print("End of MyTimer...")
... 
...     # 内部计算运行时间
...     def _calc(self):
...         self.lasted = []
...         self.prompt = "Running time: "
...         for i in range(6):
...             self.lasted.append(self.end[i] - self.begin[i])
...             if self.lasted[i]:
...                 self.prompt += (str(self.lasted[i]) + self.unit[i])
... 
...         print(self.prompt)
...         
...         # 为下一轮计时初始化变量
...         self.begin = 0
...         self.end = 0

四、属性访问

        属性在哪个对象上定义,便会出现在哪个对象的 __dict__ 中。

>>> class Test1:
...     a = 1
>>> class Test2(Test1):
...     b = 2
...     def __init__(self, num):
...         self.num = num
...     def test(self):
...         return self.num + 1

>>> t2 - Test2(2)
>>> t2.__dict__
{'num': 2}
>>> Test2.__dict__
mappingproxy({'__module__': '__main__', 'b': 2, '__init__': <function Test2.__init__ at 0x000002094F0F4700>, 'test': <function Test2.test at 0x000002094F1054C0>, '__doc__': None})
>>> t2.test()
3
>>> Test1.__dict__
mappingproxy({'__module__': '__main__', 'a': 1, '__dict__': <attribute '__dict__' of 'Test1' objects>, '__weakref__': <attribute '__weakref__' of 'Test1' objects>, '__doc__': None})

        Python 中常用点操作符来访问对象的属性,也可通过魔法方法来对属性进行管理。

  __getattribute__() 方法是实例对象查找属性或方法的入口。实例对象访问属性或方法时都需要调用该方法,之后才会根据一定的规则在各个 __dict__() 中查找相应的属性值或方法对象,若没有找到则会调用 __getattr__() 方法

        例如:

>>> class C:
...     def __getattribute__(self, name):
...         print('===getattribute===')
...         return super().__getattribute__(name)
...     def __setattr__(self, name, value):
...         print('===setattr===')
...         super().__setattr__(name, value)
...     def __delattr__(self, name):
...         print('===delattr===')
...         super().__delattr__(name)
...     def __getattr__(self, name):
...         print('===getattr===')
...     
>>> c = C()    
# 输出的结果不正确,为什么?

小甲鱼运行结果:

        小练习:测BMI指数

>>> class Carl_BMI:
...     # 体重单位:kg; 身高单位:m
...     def __init__(self, weight = 0, height = 0):
...         self.weight = weight
...         self.height = height
...         
...     def __setattr__(self, name, value):
...         # 只有为叫 “Giant” 的属性赋值才会采用 if 下的语句
...         if name == 'Giant':
...             self.weight = value
...             self.height = value
...         else:
...             # __dict__ 方法 和 super() 方法可二选一
...             # self.__dict__[name] = value
...             super().__setattr__(name, value)
...         
...     def __getBMI__(self):
...         return self.weight / ( self.height ** 2 )

>>> c = Carl_BMI(65, 1.77)
>>> c.__getBMI__()
20.747550193111813
>>> c.Giant = 10
>>> c.__getBMI__()
0.1
>>> c.Lily = 20
>>> c.__getBMI__()
0.1
>>> c = Carl_BMI()
>>> c.Lily = 20
>>> c.__getBMI__()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "F:/PyCharm_Files/Carl_BMI.py", line 16, in __getBMI__
    return self.weight / ( self.height ** 2 )
ZeroDivisionError: division by zero
>>> c.Giant = 20
>>> c.__getBMI__()
0.05

(详情参考: Python中的属性访问与描述符 - 阿谋 - 博客园

五、描述符

        描述符(descriptor):将某种特殊类型的类(该类至少定义 __get__() 、__set__() 和 __delete__() 三个方法之一)的实例指派给另一个类的属性

        描述符相关魔法方法:

        例如:

# 定义描述符类 TestDescriptor
# 其实现了 __get__()、__set__() 和 __del__() 方法
>>> class TestDescriptor:
...     # self 表示:描述符类自身的实例;
...     # instance 表示:描述符拥有者所在的类的实例,此处为 Test 类的实例;
...     # owner 表示该描述符拥有者所在的类本身,此处为 Test 类
...     def __get__(self, instance, owner):
...         print("getting...", self, instance, owner)
...     # 对 x 属性赋值则自动调用 __set__() 方法,self 和 instance 同 __get__() 方法,value 为所赋的值
...     def __set__(self, instance, value):
...         print("setting...", self, instance, value)
...     def __delete__(self, instance):
...         print("deleting...", self, instance)
>>> class Test:
...     # 将描述符类 TestDescriptor 的类实例指派给 Test 的类属性
...     x = TestDescriptor()

>>> test = Test()
>>> test.x
getting... <__main__.TestDescriptor object at 0x0000025CE815FF10> <__main__.Test object at 0x0000025CE82B0C10> <class '__main__.Test'>
>>> test.x = "Hahaha"
setting... <__main__.TestDescriptor object at 0x0000025CE815FF10> <__main__.Test object at 0x0000025CE82B0C10> Hahaha
>>> del test.x
deleting... <__main__.TestDescriptor object at 0x0000025CE83396D0> <__main__.Test object at 0x0000025CE8339940>

        自定义描述符类

>>> class MyProperty:
...     def __init__(self, fget, fset, fdel):
...         self.fget = fget
...         self.fset = fset
...         self.fdel = fdel
...     def __get__(self, instance, owner):
...         return self.fget(instance)
...     def __set__(self, instance, value):
...         self.fset(instance, value)
...     def __delete__(self, instance):
...         self.fdel(instance)

>>> class C:
...     def __init__(self):
...         self._x = None
...     def get_x(self):
...         return self._x
...     def set_x(self, value):
...         self._x = value
...     def del_x(self):
...         del self._x
... 
...     x = MyProperty(get_x, set_x, del_x)

>>> c = C()
>>> c.x = 'a'
>>> c._x
'a'

        小练习:一个温度类 + 两个分别描述摄氏度和华氏度属性的描述类,两属性自动转换

>>> class Celsius:
...     # 此处的 __init__() 方法不重写也可正常运行,可以删除?
...     def __init__(self, value = 0):
...         self.value = float(value)
...     def __get__(self, instance, owner):
...         return self.value
...     def __set__(self, instance, value):
...         self.value = float(value)
...     def __delete__(self, instance):
...         del self.value

>>> class Fahrenheit:
...     def __get__(self, instance, owner):
...         # instance.cel 为摄氏度的值,返回华氏度的值
...         return instance.cel * 1.8 +32
...     def __set__(self, instance, value):
...         # 通过设置华氏度的值,返回摄氏度的值
...         instance.cel = (float(value) - 32) /1.8

>>> class Temperature:
...     cel = Celsius()
...     fah = Fahrenheit()

六、定制容器

        定制容器不可变:只定义 __len__() 和 __getitem__() 方法

        定制容器可变:还需定义 __setitem__() 和 __delitem__() 方法

        鸭子类型:

鸭子类型(duck typing),《零基础入门学习Python》,Python交流,鱼C论坛 - Powered by Discuz!

        定制容器相关的魔法方法:

        小练习:(1)自定义一个只读列表;(2)记录每个元素的被访问次数

>>> class MyList:
...     def __init__(self, *args):
...         self.values = [x for x in args]
...         # Python 字典 fromkeys() 函数用于创建一个新字典,以序列 seq 中元素做字典的键,value 为字典所有键对应的初始值
...         # dict.fromkeys(seq[, value])
...         self.count = [].fromkeys(range(len(self.values)), 0)
...         # 由于字典不能有两个相同的键,列表中可能会出现相同的元素,所以此处采用列表的下标作为字典的键
...     def __len__(self):
...         return len(self.values)
...     def __getitem__(self, key):
...         self.count[key] += 1
...         return self.values[key]

>>> c1 = MyList(1, 2, 4, 4, 5)
>>> c2 = MyList(5, 10, 15, 20, 25)
>>> c1[1]
2
>>> c2[2]
15
>>> c1.count
{0: 0, 1: 1, 2: 0, 3: 0, 4: 0}
>>> c2.count
{0: 0, 1: 0, 2: 1, 3: 0, 4: 0}

七、迭代器 和 生成器

        1. 迭代器:迭代类似于循环,每次重复都相当于一次迭代,而每次迭代的结果都用作下一次迭代的初始值;迭代器序列通常为序列和字典等。

        BIF:(1)iter():调用该方法则得到某一容器对象的迭代器

                 (2)next():调用该方法则可得到下一个返回值

        例如:

>>> str = "qwe123"
>>> it = iter(str)
>>> next(it)
'q'
>>> next(it)
'w'
>>> next(it)
'e'
>>> next(it)
'1'
>>> next(it)
'2'
>>> next(it)
'3'

        魔法方法:(1)__iter__():若设置容器为迭代器,则必须实现该方法,即返回迭代器本身

                          (2)__next__():决定迭代的规则

>>> class MyFib:
...     def __init__(self, n = 20):
...         self.a = 0
...         self.b = 1
...         self.n = n
...     def __iter__(self):
...         return self
...     def __next__(self):
...         self.a, self.b = self.b, self.a + self.b
...         return self.a

>>> fibs = MyFib()
>>> for each in fibs:
...     print(each)
1
1
2
3
5
8
13

>>> fibs = MyFib(10)
>>> for each in fibs:
...     print(each)
1
2
3
4
5
8

        2.生成器:生成器可暂时挂起函数,并保留函数的局部变量等数据,再次调用时可从上一次暂停的位置继续执行。

>>> def TestGen():
...     print("starting...")
...     while 1:
...         num = yield 111
...         print("num:",num)
>>> tg = TestGen()
>>> next(tg)
starting...
111
>>> print("*" * 20)
********************
>>> next(tg)
num: None
111

>>> def TestG():
...     print("starting...")
...     yield 111
...     yield 222
>>> tg1 = TestG()
>>> next(tg1)
starting...
111
>>> next(tg1)
222
>>> next(tg1)
Traceback (most recent call last):
  File "<pyshell#47>", line 1, in <module>
    next(tg1)
StopIteration

yield 关键字的生动理解:

python中yield的用法详解——最简单,最清晰的解释_冯爽朗的博客-CSDN博客_yield

        小练习:斐波那契数列

>>> def fibs():
...     a = 0
...     b = 1
...     while True:
...         a, b = b , a + b
...         yield a
>>> for each in fibs():
...     if each > 20:
...         break
...     print(each)
1
1
2
3
5
8
13

        3. 生成器表达式

        (1)列表推导式:

>>> [i * 2 for i in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

        (2)字典推导式:

>>> {i : i % 2 == 0 for i in range(10)}
{0: True, 1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False}

        (3)集合推导式:

>>> {i for i in [0, 1, 1, 3, 5, 5, 7, 9, 9]}
{0, 1, 3, 5, 7, 9}

        (4)生成器表达式:

# 用小括号括起来不是元组推导式,而是生成器表达式
>>> (i for i in range(10))
<generator object <genexpr> at 0x000002BAF50F3740>
>>> a = (i for i in range(10))
>>> next(a)
0
>>> next(a)
1
>>> for each in a:
...     print(each)
2
3
4
5
6
7
8
9

八、其他

1. Python 中几种下划线的含义可参考下方链接

https://blog.csdn.net/tcx1992/article/details/80105645

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python学习笔记》是由皮大庆编写的一本关于Python语言学习的教材。在这本书中,作者详细介绍了Python语言的基础知识、语法规则以及常用的编程技巧。 首先,作者简要介绍了Python语言的特点和优势。他提到,Python是一种易于学习和使用的编程语言,受到了广大程序员的喜爱。Python具有简洁、清晰的语法结构,使得代码可读性极高,同时也提供了丰富的库和模块,能够快速实现各种功能。 接着,作者详细讲解了Python的基本语法。他从变量、数据类型、运算符等基础知识开始,逐步介绍了条件语句、循环控制、函数、模块等高级概念。同时,作者通过大量的示例代码和实践案例,帮助读者加深对Python编程的理解和应用。 在书中,作者还特别强调了编写规范和良好的编程习惯。他从命名规范、注释风格、代码缩进等方面指导读者如何写出清晰、可读性强的Python代码。作者认为,良好的编程习惯对于提高代码质量和提高工作效率非常重要。 此外,作者还介绍了Python的常用库和模块。他提到了一些常用的库,如Numpy、Pandas、Matplotlib等。这些库在数据处理、科学计算、可视化等领域有广泛的应用,帮助读者更好地解决实际问题。 总的来说,《Python学习笔记》是一本非常实用和全面的Python学习教材。通过学习这本书,读者可以系统地学习和掌握Python编程的基础知识和高级应用技巧,为以后的编程学习和工作打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值