Python--魔法方法学习

1、什么叫魔法方法?

  • 魔法方法:Python解释器自动给出默认的,是可以给你的类增加魔力的特殊方法。如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用(不重载则会使用默认的)。你可以根据需求,重写这些方法去定义自己想要的行为,而这一切都是自动发生的。
  • 魔法方法经常是两个下划线包围来命名的(比如__init____del__
  • 函数与方法的区别:
  • 魔法方法是针对class而言的,脱离了”类“谈magic_method是没有意义的
  • 网上的一些魔法方法详解:http://blog.csdn.net/koko66/article/details/42709279,可以学习一下

2、__init__(self[,...])(构造器,注意前后都是双下划线)

  • 只要实例化一个类,该方法在创建对象时会被自动调用

    #实例化创建一个对象,系统会自动调用了__init__方法
    >>>wangwu = Person('王五','男','16')
    >>>wangwu.sheep()
    王五:不嗨了,早睡早起身体棒!
    

    可在类里初始化好name,sex,age三个值,此时实例化Person时可不用传参,直接用默认的值。

    class Person1():#定义一个Person1类
        def __init__(self):
            self.name='旺仔牛奶' #默认name
    >>>a=Person1()
    >>>a.name
    '旺仔牛奶'
    

    若无初始化具体的值,实例化Person时又没有传入参数,则会报错。

    >>>lisi = Person()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: __init__() missing 3 required positional arguments: 'name', 'sex', and 'age'
    
  • 小结:

    1、根据“需求”(需要初始化时,如上面的Person类,需要初始化name,sex,age)才去重写__init__方法

    2、有了__init__方法,在创建实例的时候,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去

    3、该方法的返回值为None,不能试图让其返回其他值!

    class A:
          def __init__(self):
          return 'A'
    >>>a = A()#会报错
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: __init__() should return None, not 'str'
    

3、__new__(cls[, ...])

  • 实例化调用的第一个方法,它只取下cls参数(类参数),并把其他参数传给 __init__,这个方法不常去重写,但也有适用场景:继承一个不可变的类型,如元组、字符串,但又需去修改值时,可重写该方法。

    #全部转为大写字母
    class CapStr(str):           #继承str类
        def __new__(cls,string):    #重写__new__方法,在初始化时就将传入字符串全部转为大写
            string = string.upper()
            return str.__new__(cls,string) #返回一个类对象str的__new__方法,参数是修改为大写的string
    >>>a = CapStr("abcd")
    ABCD
    

4、__del__(self[, ...])

  • 当对象将要被销毁时,此方法被自动调用,类似于析构器!注:是当已经没有任何变量引用该对象时,python的垃圾回收机制会将其自动销毁,此时才会去调用__del__方法!

    del x ==x.__del__() 是错误的!
    
    class C:
        def __init__(self):
            print('我是__inti__方法,我被调用了')
           def __del__(self):
                print('我是__del__方法,我被调用了')
    
    >>>a = C()    #__inti__方法在对象创建时就会被自动调用
    我是__inti__方法,我被调用了
    >>>b=a
    >>>c=b
    >>>del c     #此时不会调用__del__方法
    >>>del b
    >>>del a      #所有对a的引用都被del后,__del__才会被调用
    我是__del__方法,我被调用了
    
  • 注意:当解释器退出的时候,如果对象还存活着,这里不能确保__del__一定会被执行!所以__del__ 不能替代一些良好的编程习惯(比如连接用完了将其关掉)!

5、算术运算魔法方法

  • 当对象进行算术操作时,即触发对应的算术魔法方法

    1、__add__(self, other), 定义加法行为 : +

    2、__sub__(self, other) ,定义减法行为 :-

    3、__mul__(self, other) ,定义乘法行为 :*

    4、__truediv__(self, other) ,定义真除法行为 :/

    (算数运算,反运算,增量赋值运算,一元操作符等其他的方法见上面给出的魔法方法详解链接)

    class New_int1(int):#继承int类
          pass
    >>> a = New_int1(3)
    >>> b = New_int1(4)
    >>> a+b #加号行为,继承int类,则会去调用int中的__add__(self, other)魔法方法
    7
    >>> a-b
    -1
    ------------------------------------------------------------------------------------
    class New_int2(int): #继承int类
          def __add__(self,other): #重写加法行为,令New_int类中的加法运算来做int中的减法运算
              return int.__sub__(self,other) 
        def __sub__(self,other):
              return int.__add__(self,other)
    
    >>> a = New_int2(3)
    >>> b = New_int3(4)
    >>> a+b
    -1
    >>> a-b
    7
    

6、属性访问控制

  • __getattr__(self, name):定义当用户试图访问一个不存在的属性时的行为

    class Test(object):
      def __init__(self,city):
          self.city = city
      def __getattr__(self,name):
          print('don\'t have the attribute:%s'%name)
    >>> a = Test('广州')
    >>> a.city
    '广州'
    >>> a.name
    don't have the attribute:name
    #这里并没有name属性,在找不到属性的情况下,正常的继承object的对象都会抛出AtrributeError的错误。但是这里通过__getattr__魔法方法改变了找不到属性时候的类的行为。输出了查找的属性的参数。
    
  • __getattribute__(self, name): 定义当该类的属性被访问时的行为(拦截所有的属性,包括存在的属性)。注意:避免"无限递归"的错误(返回时最好用调用基类的方法)

    class Test(object):
      def __init__(self,city):
          self.city = city
      def __getattribute__(self,name):
          print('__getattribute__:%s'%name)
          return name
    >>> a = Test('广州')
    >>> a.city #__getattribute__会拦截所有的属性,包括已存在的。
    __getattribute__:city
    '广州'
    >>> a.name
    __getattribute__:name
    'name'
    
  • __setattr__(self, name,value):定义当一个属性被设置时的行为。不管对象的某个属性是否存在,它都允许你为该属性进行赋值。注意:避免"无限递归"的错误(返回时最好用调用基类的方法)

    class Test(object):
      def __init__(self,city):
          self.city = city
      def __setattr__(self,name,value):
          return super().__setattr__(name,value) #调用基类的__setattr__方法(推荐)
    >>> a = Test('广州')
    >>> a.name ='小茗童鞋'
    >>> a.name
    '小茗童鞋'
    --------------------------------------------------------------------------------------------
    #若在 __setattr__不调用基类方法,可以采用给对象的特殊属性__dict__赋值的方法(__dict__会以字典形式显示当前对象的所有属性和对应的值)
    >>> a.__dict__
    {'city': '广州', 'name': '小茗童鞋'}
    class Test(object):
      def __init__(self,city):
          self.city = city
      def __setattr__(self,name,value):
          self.__dict__[name] = value
    >>> b = Test('深圳')
    >>> b.sex = '男'
    >>> b.sex
    '男'
    >>> b.__dict__
    {'city': '深圳', 'sex': '男'}
    --------------------------------------------------------------------------------------------
    #无限递归例子:
    class Test(object):
      def __init__(self,city):
          self.city = city
      def __setattr__(self,name,value):
          self.name = value
    #执行初始化方法self.city = city的时候,就会调用__setattr__方法,而这里的__setattr__方法里面的self.name = value又会调用自身,所以导致无限递归。故使用该魔法方法的时候要特别注意!
    >>> a = Test('广州')
    Traceback (most recent call last):
    File "<pyshell#36>", line 1, in <module>
        a = Test('广州')
    File "<pyshell#35>", line 3, in __init__
        self.city = city
    File "<pyshell#35>", line 5, in __setattr__
        self.name = value
    RecursionError: maximum recursion depth exceeded while calling a Python object
    
  • __delattr__(self, name):定义当一个属性被删除时的行为。注意:避免"无限递归"的错误(返回时最好用调用基类的方法)

     class Test(object):
      def __init__(self,city):
          self.city = city
      def __delattr__(self,name):
          print('__delattr__:%s'%name)
          return super().__delattr__(name) #调用基类的__delattr__方法(推荐)
    >>> a = Test('广州')
    >>> del a.city
    __delattr__:city
    >>> a.city
    Traceback (most recent call last):
      File "<pyshell#73>", line 1, in <module>
        a.city
    AttributeError: 'Test' object has no attribute 'city' 
    
  • property(fget=None, fset=None, fdel=None, doc=None):是一个类,主要功能是为了方便类内部函数的调用(使得在外部不用调用类方法,可以直接以属性方式操作)

    class C:
      def __init__(self,size=10):
          self.size = size
      def getSize(self):
          return self.size
      def setSize(self,value):
          self.size = value
      def delSize(self):
          del self.size
      x = property(getSize,setSize,delSize) #通过property函数,使得x与size直接挂钩    
    >>> c = C()
    >>> c.x = 1 
    >>> c.x
    1
    >>> c.size
    1
    >>> del c.x
    >>> c.size
    Traceback (most recent call last):
      File "<pyshell#16>", line 1, in <module>
        c.size
    AttributeError: 'C' object has no attribute 'size'
    

7、描述符(property的原理)

描述符就是将某种特殊类型的类(类里含有下述魔法方法之一)的实例指派给另一个类的属性。

1、__get__(self, instance, owner):用于访问属性,它返回属性的值

2、__set__(self, instance, value):将在属性分配操作中调用,不返回任何内容

3、__delete__(self, instance):控制删除操作,不返回任何内容

class MyDecriptor:#称为描述符类
    def __get__(self,instance,owner):
        print("geting...",self,instance,owner)
    def __set__(self,instance,value):
        print("seting...",self,instance,value)
    def __delete__(self,instance):
        print("deleting...",self,instance)      
class Test:
    x = MyDecriptor() #此处说明MyDecriptor就是x的描述符,类似property使用
>>> test = Test()
>>> test.x #调用__get__方法
geting... <__main__.MyDecriptor object at 0x0000000003570FD0> <__main__.Test object at 0x0000000003570F98> <class '__main__.Test'>
#此处self:描述符类本身的一个实例 <__main__.MyDecriptor object at 0x0000000003570FD0>
#instance:拥有者类的实例 <__main__.Test object at 0x0000000003570F98>
#owner:拥有者类本身 <class '__main__.Test'>
>>> test.x ='哈哈哈' #调用__set__方法
seting... <__main__.MyDecriptor object at 0x0000000003570FD0> <__main__.Test object at 0x0000000003570F98> 哈哈哈
>>> del test.x #调用__delete__方法
deleting... <__main__.MyDecriptor object at 0x0000000003570FD0> <__main__.Test object at 0x0000000003570F98>

------------------------------------------------------------------------------------------------
#定义一个自己的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(instance) 
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 = '哈哈' #直接以访问属性形式调用描述类MyProperty
>>> c.x
'哈哈'
>>> c._x
'哈哈'
>>> del c.x
>>> c._x
Traceback (most recent call last):
  File "<pyshell#125>", line 1, in <module>
    c._x
AttributeError: 'C' object has no attribute '_x'

8、容器类型-魔法方法

1、容器是对数据的封装

2、python的容器类型分为可变类型(如list、dict)和不可变类型(如string、tuple)。可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。

魔法方法 定义 备注
__len__(self) 求容器的大小(注意与capacity的区别) 可变和不可变容器均具备 __len____getitem__
__getitem__(self, key) 获取容器中指定元素的行为
__setitem__(self, key, value) 设置容器中指定元素的行为 只有可变容器拥有 __setitem____delitem__
__delitem__(self, key) 删除容器中指定元素的行为
__iter__(self) 定义迭代器中元素的行为
__reversed__(self) 当调用reversed()函数时 仅当序列可以是有序的时候实现它,例如对于列表或者元组。
__contains__(self, item) 成员运算符in/ not in的行为
class 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]
>>> a = FunctionalList([1,2,7,10,3])
>>> a.head()
1
>>> a.take(2)
[1, 2]
>>> a.init()
[1, 2, 7, 10]
>>> a.tail()
[2, 7, 10, 3]
>>> b = iter(a) #调用迭代器行为
>>> for i in b:
        print(i)    
1
2
7
10
3
>>> from collections import Iterator #可以使用isinstance()判断一个对象是否是Iterator对象
>>> isinstance(b, Iterator)
True

9、迭代器--魔法方法

1、可迭代对象Iterable:可以直接作用于for循环的对象统称为可迭代对象,使用isinstance()判断一个对象是否是Iterable对象。

>>> from collections import Iterable
>>> isinstance({}, Iterable)
True
>>> isinstance('hhh', Iterable)
True
>>> isinstance(10, Iterable)
False

2、迭代器Iterator:可以被next()函数调用并不断返回下一个值的对象称为迭代器,使用isinstance()判断一个对象是否是Iterator对象。

  • 内置函数:iter(),next()
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
#生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
#把list、dict、str等Iterable变成Iterator可以使用iter()函数
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
  • 魔法方法: __iter__(self), __next__(self)
>>> string = 'abcd'
>>> it = iter(string)
#每次调用next(it),就计算出it的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
>>> next(it) 
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
'd'
>>> next(it)
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    next(it)
StopIteration
--------------------------------------------------------------------------------------------
#定义一个斐波那契迭代器
class Fibs:
    def __init__(self,n=10):
        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
        if self.a > self.n:#控制迭代数量
            raise StopIteration
        else:
            return self.a       
>>> fibs = Fibs()
>>> for each in fibs:
    print(each) 
1
1
2
3
5
8

10、__call__(self,name)

任何类,只需要定义一个__call__()方法,就可以直接对实例(对象)进行调用。

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def __call__(self, friend):
        print('My name is %s...' % self.name)
        print('My friend is %s...' % friend)        
>>> p = Person('Bob', 'male')
>>> p('Tim') #直接对实例进行调用,即是把这个类型的对象当作函数来使用
My name is Bob...
My friend is Tim...
#对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象。

如何判断一个变量是对象还是函数?(即判断一个对象是否能被调用,能被调用的对象就是一个Callable对象)

#用callabel()函数判断
>>> callable(Person('Bob', 'male'))
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False

11、__str____repr__

__str__()返回用户看到的字符串,__repr__()返回程序开发者看到的字符串,即__repr__()是为调试服务的。

 class Animal(object):
    def __init__(self,name):
        self.name = name
>>> print(Animal('Cat')) #打印出来不好看
<__main__.Animal object at 0x0000000003558978>
#改进
class Animal(object):
    def __init__(self,name):
        self.name = name
    def __str__(self): #定义一个__str__()方法,返回一个好看的字符串
        return 'Animal object (name: %s)' % self.name   
>>> print(Animal('Cat'))
Animal object (name: Cat)
#但若不用print,打印出来的仍是不好看,因为直接显示变量调用的不是__str__(),而是__repr__()
>>> a = Animal('Cat')
>>> a
<__main__.Animal object at 0x0000000003558C18>
#解决:再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的
class Animal(object):
    def __init__(self,name):
        self.name = name
    def __str__(self): 
        return 'Animal object (name: %s)' % self.name
    __repr__ = __str__ 
>>> print(Animal('Cat'))
Animal object (name: Cat)
>>> a = Animal('Cat')
>>> a
Animal object (name: Cat)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容