Python学习笔记之魔法方法

魔法方法
1.总是被双下划线包围
2.是面向对象的Python的一切
3.总是能在适当时候被自动调用
构造函数
init(self,param1,param2…)
当创建一个对象的时候,init函数就会被自动调用,类似于C++的构造函数。

>>> class ball:
	def __init__(self,name):
		self.name = name
	def kick(self):
		print('我叫%s,该死的,谁踢我...'% self.name)
>>> b = ball('土豆')
>>> b.kick()
我叫土豆,该死的,谁踢我...
>>> c = ball()
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    c = ball()
TypeError: __init__() missing 1 required positional argument: 'name'

构造函数不能做返回。

>>> class A:
	def __init__(self):
		return "A of A-cup"
>>> a = A()
Traceback (most recent call last):
  File "<pyshell#186>", line 1, in <module>
    a = A()
TypeError: __init__() should return None, not 'str'

事实上,new(cls[,…])才是定义一个对象时最先被调用的,一般都是不去做重写,用Python自己的方案调用。当类继承一个不可变类型又需要修改时就需要使用它。

>>> class CapStr(str):
		def __new__(cls,string):
			string = string.upper()
			return str.__new__(cls,string)	
>>> a = CapStr("I love FishC.com!")
>>> a
'I LOVE FISHC.COM!'

当创建a时首先会调用CapStr里的new函数,这时会执行重写的new函数,然后再返回基类str的new函数,以此来解决当类继承一个不可变类型而又要修改它时的问题。

析构函数
del(self)
当垃圾回收机制调用时,才会调用析构函数。

>>> class C:
	def __init__(self):
		print("我是__init__方法,我被调用了...")
	def __del__(self):
		print("我是__del__方法,我被调用了...")
>>> c1 = C()
我是__init__方法,我被调用了...
>>> c2 = c1
>>> c3 = c2
>>> del c1
>>> del c2
>>> del c3
我是__del__方法,我被调用了...```

**算术运算**
Python无处不对象。
```python
>>> type(len)
<class 'builtin_function_or_method'>
>>> type(dir)
<class 'builtin_function_or_method'>
>>> type(int)
<class 'type'>
>>> type(list)
<class 'type'>
>>> class C:
	pass
>>> type(C)
<class 'type'>
>>> a = int('123')
>>> a
123
>>> b = int('456')
>>> a + b
579

诸如len、dir、int、list这些工厂函数其本质上还是类对象,他们会返回一个实例对象,如a和b就是类对象int返回的实例对象,而对象之间又可以进行算术运算,其实两个对象之间的运算属于魔法方法,其本质上就是调用不同的魔法方法实现对象之间的运算。

而我们可以通过重新,来满足更多的需求。

>>> class New_int(int):
		def __add__(self, other):
			return int.__sub__(self, other)
		def __sub__(self,other):
			return int.__add__(self,other)
>>> a = New_int(3)
>>> b = New_int(5)
>>> a + b
-2
>>> a - b
8

上式则是通过创建一个继承于int的类对象New_int,然后对其的__add__和__sub__进行改写。
但是改写时一定要注意,很容易会出现无限递归的情况。

>>> class New_int(int):
		def __add__(self, other):
			return self + other
		def __sub__(self,other):
			return self - other
>>> a = New_int(3)
>>> b = New_int(5)
>>> a + b
Traceback (most recent call last):
  File "<pyshell#232>", line 1, in <module>
    a + b
  File "<pyshell#229>", line 3, in __add__
    return self + other
  File "<pyshell#229>", line 3, in __add__
    return self + other
  File "<pyshell#229>", line 3, in __add__
    return self + other
  [Previous line repeated 1022 more times]
RecursionError: maximum recursion depth exceeded

由于self + other这一行里的+,程序无限循环调用__add__函数。

算术运算参考表
在这里插入图片描述
其中divmod是返回两个数进行地板除法后,剩下的余数。

更多魔法方法请参考

其中重写反运算方法时,要注意它的顺序问题。

>>> class Nint(int):
	def __radd__(self,other):
		return int.__sub__(self,other)
>>> a = Nint(5)
>>> b = Nint(3)
>>> a + b
8
>>> 1 + b
2

上面的a + b由于a里由__add__函数,所以会优先执行它,而1 + b,因为1不是实例对象,没有__add__函数,所以会调用b中的__radd__函数进行反运算,由于__radd__函数中的self是b,other是1,所以1 + b即3 - 1运算出来的结果是2.

属性访问
常用的一些属性访问BIF。
在这里插入图片描述
我们通过重写这些魔法方法来讲解他们的用法。

>>> class C:
		def __getattribute__(self, name):
			print("getattribute")
			return super().__getattribute__(name)
		def __getattr__(self, name):
			print("getattr")
		def __setattr__(self, name, value):
			print("setattr")
			super().__setattr__(name, value)
		def __delattr__(self, name):
			print("delattr")
			super().__delattr__(name)	
>>> c = C()
>>> c.x
getattribute
getattr
>>> c.x = 1
setattr
>>> c.x
getattribute
1
>>> del c.x
delattr

当c.x这个属性不存在时,系统会先调用getattribute方法再调用getattr方法,当c.x被赋值为1后,再次访问,系统只调用getattribute方法,所以当访问某个属性时,若属性不存在则系统会再调用一次getattr方法。

改写属性访问BIF时要注意循环陷阱。

class Rectangle:
    def __init__(self, width=0, hight=0):
        self.width = width
        self.hight = hight
    def __setattr__(self, name, value):
        if name == 'square':
            self.width = value
            self.hight = value
        else:
            self.name = value
    def getArea(self):
        return self.width * self.hight

输出结果:系统陷入无限递归,达到最大递归深度后,系统报错。

>>> r = Rectangle(4,5)
Traceback (most recent call last):
  File "<pyshell#76>", line 1, in <module>
    r = Rectangle(4,5)
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 3, in __init__
    self.width = width
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 10, in __setattr__
    self.name = value
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 10, in __setattr__
    self.name = value
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 10, in __setattr__
    self.name = value
  [Previous line repeated 1019 more times]
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 6, in __setattr__
    if name == 'square':
RecursionError: maximum recursion depth exceeded in comparison
>>> 

当执行self.width = width时,会调用__setattr__,之后程序走else部分,即执行self.name = value,此时会再次调用__setattr__,然后系统就陷入了无限递归。

解决这种问题我们可以用super函数调用父类的__setattr__或用__dict__字典方式赋值.

class Rectangle:
    def __init__(self, width=0, hight=0):
        self.width = width
        self.hight = hight
    def __setattr__(self, name, value):
        if name == 'square':
            self.width = value
            self.hight = value
        else:
            self.__dict__[name] = value  #super().__setattr__(name, value)
    def getArea(self):
        return self.width * self.hight

输出结果

>>> r = Rectangle(4,5)
>>> r.width
4
>>> r.hight
5
>>> r.getArea()
20
>>> r.square = 10
>>> r.width
10
>>> r.hight
10
>>> r.getArea()
100

在重写属性访问BIF时一定要注意递归陷阱!!!

描述符
描述符就是将某种特殊类型的类的实例指派给另一个类的属性。
特殊类型的要求就是含有__get__、set、__delete__这三个方法。
在这里插入图片描述
现在我们通过例子来看下描述符。

>>> class MyDecriptor:
		def __get__(self, instance, owner):
			print("getting...",self, instance, owner)
		def __set__(self, instance, owner):
			print("setting...",self, instance, owner)
		def __delete__(self, instance):
			print("deleting...",self, instance)	
>>> class Test:
		x = MyDecriptor()

>>> test = Test()
>>> test.x
getting... <__main__.MyDecriptor object at 0x0000028E61C1C940> <__main__.Test object at 0x0000028E63CACA30> <class '__main__.Test'>
>>> test
<__main__.Test object at 0x0000028E63CACA30>
>>> Test
<class '__main__.Test'>
>>> test.x = "X-man"
setting... <__main__.MyDecriptor object at 0x0000028E61C1C940> <__main__.Test object at 0x0000028E63CACA30> X-man
>>> del test.x
deleting... <__main__.MyDecriptor object at 0x0000028E61C1C940> <__main__.Test object at 0x0000028E63CACA30>

由其中可以知道,我们定义了一个描述符类,并将这个描述符类指派给了Test类的x属性,其中描述符类里的self指属性test.x,instance指实例对象test,owner指类对象Test,这就是一个描述符的基本用法,从这可以知道property方法其本质上也是定义一个描述符类,因此我们可以定义属于我们自己的property。

class MyProperty:
	def __init__(self	, fget=None, fset=None, fdel=None):
		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(in

> 这里是引用

stance)
class C:
	def __init__(self):
		self._x = None
	def getX(self):
		return self._x
	def setX(self, value):
		self._x = value
	def delX(self):
		del self._x
	x = MyProperty(getX, setX, delX)

>>> c = C()
>>> c.x = 'X-man'
>>> c.x
'X-man'
>>> c._x
'X-man'
>>> del c.x
>>> c.x
Traceback (most recent call last):
  File "<pyshell#176>", line 1, in <module>
    c.x
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 7, in __get__
    return self.fget(instance)
  File "F:\小甲鱼学习记录\小甲鱼Python课后作业\test.py", line 17, in getX
    return self._x
AttributeError: 'C' object has no attribute '_x'

上面的MyProperty含有__get__、__set__和__delete__这三个函数,因此它是特殊类型,我们可以将它指派给C类的x属性。然后我们又定义了一个C类,并且将定义后的getX、setX和delX传入我们自定义的描述符MyProperty,再指派给x。

下面是一个练习
在这里插入图片描述

class Celsuis:
    def __init__(self, value=26.0):
        self.value = value
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)
class  Fahrenheit:
    def __get__(self, instance, owner):
        return instance.cel*1.8+32
    def __set__(self, instance, value):
        instance.cel = (float(value) - 32)/1.8        
class Temperature:
    cel = Celsuis()
    fah = Fahrenheit()

输出结果

>>> temp = Temperature()
>>> temp.cel
26.0
>>> temp.cel = 30
>>>> temp.cel
30.0

先创建一个实例对象temp,然后执行temp.cel,这里的cel是实例对象temp的属性同时它也是一个类对象Celsuis的实例对象,之后调用Celsuis中的__get__方法,结果显示为26.0。然后输入temp.cel = 30,系统调用Celsuis中的__set__方法,再执行temp.cel。

>>> temp.fah
86.0
>>> temp.fah = 100
>>> temp.cel
37.77777777777778

之后输入temp.fah,同理tenp.cel,系统调用Fahrenheit中的__get__方法返回instance.cel*1.8+32 其中instance指实例对象temp,此时系统会再调用Celsuis中的__get__方法,输出结果86.0。输入temp.fah,系统调用Fahrenheit中的__set__方法执行instance.cel = (fioat(value) - 32)/1.8,此时系统会再调用Celsuis中的__set__方法。

描述符的定义需要定义在类的层次上,若定义在实例层次上,系统将无法自动调用__get__、__set__和__delete__方法。

>>> class MyDes:
        def __init__(self, value = None):
                self.val = value
        def __get__(self, instance, owner):
                return self.val ** 2
>>> class Test:
        def __init__(self):
                self.x = MyDes(3)

>>> test = Test()
>>> test.x

输出结果

>>> test.x
<__main__.MyDes object at 0x1058e6f60>

定制序列
我们可以基于序列的协议形式,通过改写里面的魔法方法来定制属于我们自己的容器。
在这里插入图片描述

class CountList:
    def __init__(self, *args):
        self.values = [x for x in args]
        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 = CountList(1,3,5,7,9)
>>> c2 = CountList(2,4,6,8,10) 
>>> c1[1]
3
>>> c2[1]
4
>>> c1.count
{0: 0, 1: 1, 2: 0, 3: 0, 4: 0}
>>> c1[1]
3
>>> c1.count
{0: 0, 1: 2, 2: 0, 3: 0, 4: 0}   

上面我们定义了一个不可改变的列表,并且能够统计访问的次数。

迭代器

for i in "FishC":
	print(i)
	
F
i
s
h
C

里面的字符串是容器,也是迭代器,而for语句是迭代器的触发
而Python提供了两个迭代的BIF:iter()和next()

>>> string = "FishC"
>>> it = iter(string)
>>> next(it)
'F'
>>> next(it)
'i'
>>> next(it)
's'
>>> next(it)
'h'
>>> next(it)
'C'
>>> next(it)
Traceback (most recent call last):
  File "<pyshell#97>", line 1, in <module>
    next(it)
StopIteration

iter()的作用是生成一个迭代器,而next()则是一个触发条件。
他们相对应的魔法方法是__iter__()和__next__()
我们通过改写来实现斐波那契数列。

class Fibs:
	def __init__(self, n=20):
		self.a = 0
		self.b = 0
		self.n = n
	def __iter__(self):
		return self
	def __next__(self):
		self.a, self.b = self.b, self.a+self.b
		if self.a>self.n:
			raise StopIteration
		return self.a
>>> fibs = Fibs()
>>> for each in fibs:
	print(each)	
1
1
2
3
5
8
13

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值