zoukankan      html  css  js  c++  java
  • python魔法函数

     

    1 什么是魔法函数

     

    先来定义一个类:

    In [1]:
    class Company(object):
        def __init__(self, employee_list):
            self.employee_list = employee_list
    
    In [4]:
    company = Company(['张三', '李四', '王五'])
    print(company)
    
     
    <__main__.Company object at 0x7f7c4046ebd0>
    
     

    此时,直接对Company实例化的对象进行print输出时,打印出来的信息是类名称和地址信息。但如果我们想看的不是这些,而是想输出employee_list,怎么做呢?

    In [7]:
    class Company(object):
        def __init__(self, employee_list):
            self.employee_list = employee_list
        
        def __str__(self):
            return str(self.employee_list)
    
    In [8]:
    company = Company(['张三', '李四', '王五'])
    print(company)
    
     
    ['张三', '李四', '王五']
    
     

    在这个例子中,我们添加了一个__str__()函数,然后再打印输出Company类实例时,输出的就是employee_list,但是,我们并没有显式地调用__str__()函数,这是因为,在对一个实例使用print()函数时,Python内部机制自动会调用__str__()函数。

    类似__str__()这种函数在类内部还有很多,这一类函数,我们统称为魔法函数。现在,我们明确一下魔法函数的范畴:

    魔法函数是指类内部以双下划线开头,并且以双下划线结尾的函数,在特定时刻,Python会自动调用这些函数。魔法函数不是通过继承等机制获得的,而是类一旦定义,Python内部机制自动会给类赋予这些特殊的函数,且用户是不能创建魔法函数的,即使函数名以双下划线开头和双下划线结尾。通过魔法函数可以实现许多个性化、便捷的操作。

    2 Python中的魔法函数

    2.1 字符串表示:__str____repr__

    • __str__

    • __repr__

    在很多时候,人们都容易将__str____repr__两个方法记混,甚至认为这两的功能是一样的,但事实上还是有一些差异的。

    __str__在上文中已经说过,是用于将实例对象进行print输出时使用。如下所示:

    In [17]:
    class Company(object):
        def __init__(self, name=None):
            self.name = name
            
        def __str__(self):
            return '*****公司名称为:%s*****' % self.name
    
    In [18]:
    c = Company(name='腾讯')
    print(c)
    
     
    *****公司名称为:腾讯*****
    
     

    对实例化对象是用print()函数输出时,Python内部机制会想调用str()方法,在str()方法内部继续调用__str__方法实现输出:

    In [23]:
    str(c)
    
    Out[23]:
    '*****公司名称为:腾讯*****'
     

    但是如果我们不是用print()函数而直接输出c,那么,输出结果依然是原来默认的:

    In [19]:
    c
    
    Out[19]:
    <__main__.Company at 0x7f7c4049d050>
     

    这是因为直接输出类实例化对象时,调用的是__repr__方法:

    In [20]:
    class Company(object):
        def __init__(self, name=None):
            self.name = name
            
        def __str__(self):
            return '*****公司名称为:%s*****' % self.name
    
        def __repr__(self):
            return '#####公司名称为:%s#####' % self.name
    
    In [22]:
    c = Company(name='腾讯')
    c
    
    Out[22]:
    #####公司名称为:腾讯#####
     

    综上所述,__str____repr__的区别在于,__str__方法在对实例化对象是用print()函数输出时调用,其实时Python内部机制调用str()方法,然后str()方法内部继续调用__str__方法获取输出字符串。而__repr__是在开发模式下直接输出实例化对象时被调用。

    2.2 集合、序列相关:__len____getitem____setitem____delitem____contains__

    • __len__

    Python内置函数中有一个len()函数,这个函数适用于获取序列类型数据的长度,在对一个实例使用len()方法时,真实输出的其实是__len__的返回值。所以,只要一个类内部实现了__len__方法,就可以对其实例使用__len__方法。

    In [24]:
    class Company(object):
        def __init__(self, name=None, employee_lst=None):
            self.name = name
            self.employee_lst = employee_lst
            
        def __len__(self):
            return len(self.employee_lst)
    
    In [26]:
    c = Company(name='腾讯', employee_lst=['张三', '李四', '王五'])
    len(c)
    
    Out[26]:
    3
     
    • __getitem____setitem____delitem__
     

    我们知道,在Python的dict类型数据中,可以通过方括号的方式来赋值、取值和删除值,例如通过t_dict['attr1'] = 1的方式进行赋值,通过t_dict['attr1']可以取得值,通过del t_dict['attr1']可以删除一个值。那么在自定义的一个类里面,通过__getitem____setitem____delitem__这三个,我们也可以让我们自定义类的实例化对象拥有这样的操作。

    In [48]:
    class Company(object):
        def __init__(self):
            self.company_info = {}
            
        def __setitem__(self,key,value):  # 令类实例化对象可以通过c[key] = value的方式赋值
            self.company_info[key] = value
            
        def __getitem__(self,key):          # 令类实例化对象可以通过c[key]的方式取值
                return self.company_info[key]
            
        def __delitem__(self, key):          # 令类实例化对象可以通过del c[key]的方式删除值
            del self.company_info[key]
    
    In [51]:
    c = Company()
    c['name'] = '腾讯'
    c['type'] = 'IT'
    print(c['name'])
    del c['name']
    print(c.company_info)
    
     
    腾讯
    {'type': 'IT'}
    
     

    有些时候,配合Python的反射机制类使用这三个魔法函数会有更加魔幻的效果,可以直接对实例属性进行操作:

    In [59]:
    class Company(object):
            
        def __setitem__(self,key,value):
            setattr(self, key, value)
            
        def __getitem__(self,key):
            return getattr(self, key)
        
        def __delitem__(self, key):
            delattr(self, key)
    
    In [60]:
    c = Company()
    c['name'] = '腾讯'
    c['type'] = 'IT'
    
    In [61]:
    c['type']
    
    Out[61]:
    'IT'
    In [62]:
    del c['type']
    
    In [63]:
    c['type']
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-63-56601054285d> in <module>
    ----> 1c['type']
    
    <ipython-input-59-b82d5d10cbb4> in __getitem__(self, key)
          5 
          6     def __getitem__(self,key):
    ----> 7return getattr(self, key)
          8 
          9     def __delitem__(self, key):
    
    AttributeError: 'Company' object has no attribute 'type'
     
    • __contains__
     

    对于Python中dict类型的数据结构,可以使用in关键字判断序列内部是否包含某个key,在我们自定义的类中,如果定义了__contains__方法,那么也能使用in关键字判断是否包含某个属性。

    In [67]:
    class Company(object):
        def __init__(self):
            self.company_info = {}
            
        def __contains__(self, key):
            return key in self.company_info
    
    In [69]:
    c = Company()
    c.company_info['name'] = '腾讯'
    print('name' in c)
    print('type' in c)
    
     
    True
    False
    
     

    结合反射机制使用:

    In [70]:
    class Company(object):
        def __setitem__(self,key,value):
            setattr(self, key, value)
            
        def __contains__(self, key):
            return hasattr(self, key)
    
    In [75]:
    c = Company()
    c['name'] = '腾讯'
    print('name' in c)
    print('type' in c)
    
     
    True
    False
    
     

    2.3 迭代相关:__iter____next__

    • __iter____next__

    我之前写过一篇博客《为什么for循环可以遍历list:Python中迭代器与生成器》,很详细得介绍了Python中关于迭代器与生成器的原理。关于迭代器和生成器,其核心就在于__iter____next__两个方法。

    iter是Iterable的简写,表示“可迭代的”,所以,任何内部定义了__iter__的对象,我们都可以称之为可迭代对象,在Python中,有一个类专门与之对应:Iterable,我们可以通过判断对象是否是Iterable类的实例来判断是否是可迭代对象。进一步的,如果一个类内部定义了__iter__方法的同时,也定了__next__方法,那么,它的实例化对象就是迭代器,也有一个类与迭代器对应,那就是Iterator。

    In [99]:
    from collections.abc import Iterable
    from collections.abc import Iterator
    
    In [81]:
    isinstance(123, Iterable)  # 整型不是可迭代对象
    
    Out[81]:
    False
    In [101]:
    isinstance('abc', Iterator)  # 字符串不是迭代器
    
    Out[101]:
    False
    In [102]:
    isinstance('abc', Iterable)  # 字符串是可迭代对象
    
    Out[102]:
    True
    In [103]:
    class Company():
        def __iter__(self):  # 自定义一个类,只要实现了__iter__方法,就是可迭代对象
            pass
    print('Company()是可迭代对象吗:',isinstance(Company(),Iterable))
    print('Company()是迭代器吗:',isinstance(Company(),Iterator))
    
     
    Company()是可迭代对象吗: True
    Company()是迭代器吗: False
    
    In [104]:
    class Company():
        def __iter__(self):  
            pass
        def __next__(self):  # 自定义一个类,同时实现了__iter__方法和__next__方法,就是迭代器
            pass
    print('Company()是可迭代对象吗:',isinstance(Company(),Iterable))
    print('Company()是迭代器吗:',isinstance(Company(),Iterator))
    
     
    Company()是可迭代对象吗: True
    Company()是迭代器吗: True
    
     

    知道怎么区分可迭代对象和迭代器之后,就可以解释__iter____next__的作用了。那就是定义了这两个方法,就可以对实例化对象进行遍历。以for循环为例,通过for循环对一个可迭代对象进行迭代时,for循环内部机制会自动通过调用iter()方法执行可迭代对象内部定义的__iter__方法来获取一个迭代器,然后一次又一次得迭代过程中通过调用next()方法执行迭代器内部定义的__next__方法获取下一个元素,当没有下一个元素时,for循环自动捕获并处理StopIteration异常。

    In [94]:
    class B():
        def __init__(self, lst):
            self.lst = lst
            self.index = 0
        def __iter__(self):
            print('B.__iter__()方法被调用')
            return self
        def __next__(self):
            try:
                print('B.__next__()方法被调用')
                value = self.lst[self.index]
                self.index += 1
                return value
            except IndexError:
                raise StopIteration()
    
    In [98]:
    b = B([1, 2, 3])
    for i in b:
        print(i)
    
     
    B.__iter__()方法被调用
    B.__next__()方法被调用
    1
    B.__next__()方法被调用
    2
    B.__next__()方法被调用
    3
    B.__next__()方法被调用
    
     

    2.4 可调用:__call__

    • __call__

    假如有一个对象A,如果A是一个类,我们使用A()进行调用,那么就是创建一个A类的实例化对象,如果A是一个函数,我们使用A()就是调用函数A。那么,如果A是一个某个类的实例化对象时,A()是进行什么操作呢?答案就是调用该类的__call__方法,我们可以理解为,__call__就是“()”运算符。

    In [88]:
    class Company(object):
        def __init__(self):
            pass
        def __call__(self, name):
            self.name = name
            print('__call__方法被调用,name:%s' % self.name)
    
    In [89]:
    c = Company()
    c('腾讯')
    
     
    __call__方法被调用,name:腾讯
    
     

    现在,我们证实了__call__就是“()”运算法,那么,是不是类、函数这些可使用“()”运算符的对象内部都定义有__call__函数呢?答案是肯定的。

    In [90]:
    class Company(object):
        def __init__(self):
            pass
    def A():
        pass
    
    In [91]:
    print('类Company是否有__call_方法:', hasattr(Company, '__call__'))
    print('函数A是否有__call_方法:', hasattr(A, '__call__'))
    
     
    类Company是否有__call_方法: True
    函数A是否有__call_方法: True
    
     

    借助这一特性,我们可以弥补hasattr()函数的不足。我们知道,通过hasattr()函数可以判断一个类内部是否有某个属性,但是没法判断到底是变量还是方法,但进一步借助方法内部肯定定义有__call__这个特性,就可以进一步判断。

    In [92]:
    class Company(object):
        def __init__(self):
            self.name = None
        def func(self):
            pass
    
    In [93]:
    c = Company()
    print('c中是否存在属性name:', hasattr(c, 'name'))
    print('c中是否存在属性func:', hasattr(c, 'func'))
    print('name是函数吗:', hasattr(c.name, '__call__'))
    print('func是函数吗:', hasattr(c.func, '__call__'))
    
     
    c中是否存在属性name: True
    c中是否存在属性func: True
    name是函数吗: False
    func是函数吗: True
    
     

    2.5 with上下文管理器:__enter____exit__

    只要你熟悉Python开发,那么对with上下文管理就一定不会陌生,例如操作文本时,我们通常习惯with open来对打开文件,获得句柄。使用with来打开文件的好处就是在打开文件后进行操作的过程中,无论是否出现异常,Python都会对关闭句柄,也就是一定会进行收尾工作,避免占用内存资源。

    这种上下文管理机制是怎么实现的呢?这就涉及到我们现在要说的两个两个魔法函数__enter____exit__

    __enter__:with语句开始执行时候调用

    __exit__:with语句结束时候调用,注意,无论with语句中的代码是否正常结束,都会执行__exit__方法

    除了读写文件之外,我们使用Python来操作数据库时,也需要做收尾处理,也就是关闭数据库连接,那么,这个时候我们也可以用with来进行。

    In [3]:
    import pymysql
    
    
    class Dao(object):
        def __init__(self, cursor_type=None):
            self.conn = pymysql.connect( # 创建数据库连接
                host='192.168.31.201', # 要连接的数据库所在主机ip
                database='test',
                user='root', # 数据库登录用户名
                password='admin123456', # 登录用户密码
                charset='utf8' # 编码,注意不能写成utf-8
            )
                   
            self.cursor = None
            if cursor_type:
                self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
            else:
                self.cursor = self.conn.cursor()
    
        def __enter__(self):
            return self.cursor  # 返回类实例本身
    
        def __exit__(self, exc_type, exc_value, exc_trace):
            self.conn.commit()  # 提交事务
            self.cursor.close()  # 关闭游标
            self.conn.close()  # 关闭数据库连接
    
    In [6]:
    with Dao() as cursor:
        cursor.execute("select * from employee;")
        e = cursor.fetchall()
        print(e)
    
     
    ((1, '张三'), (2, '李四'))
    
     

    2.6 属性相关:__getattr____setattr____getattribute__

    • __getattr____setattr__

    __getattr__函数的作用: 在一个类实例中查找一个属性时,通过__dict__失败, 那么会调用到类的__getattr__函数,如果没有定义这个函数,那么抛出AttributeError异常。也就是说__getattr__是属性查找的最后一步。

    In [13]:
    class Company(object):
        def __init__(self, name):
            self.company_name = name
        
        def fun(self):
            print('fun方法被调用……')
            
        def __getattr__(self, name):
            print('__getattr__方法被调用')
            raise AttributeError('哥们,你查找的属性"%s"不存在' % name)
    
    In [14]:
    c = Company('腾讯')
    
     

    如果提前找到了某个属性,那么将不会继续调用__getattr__

    In [15]:
    print(c.company_name)
    print(c.fun)
    
     
    腾讯
    <bound method Company.fun of <__main__.Company object at 0x7fa0a8077100>>
    
     

    当属性不存在是,将会调用__getattr__,所以,我们可以通过__getattr__函数来定义当找不到属性时候的提醒方式,甚至是返回一个其他的默认值。

    In [16]:
    c.abc
    
     
    __getattr__方法被调用
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-16-a2bb1cff9d71> in <module>
    ----> 1c.abc
    
    <ipython-input-13-810c2a9c4f3c> in __getattr__(self, name)
          8     def __getattr__(self, name):
          9         print('__getattr__方法被调用')
    ---> 10raise AttributeError('哥们,你查找的属性"%s"不存在' % name)
    
    AttributeError: 哥们,你查找的属性"abc"不存在
     

    通过__getattr__方法,我们可以对Python的字典进行改造,另外开始通过dict_name.key的方式来访问。

    In [21]:
    class Dict(dict):
        def __init__(self, *args, **kwargs):
            super(Dict, self).__init__(*args, **kwargs)
            
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
    
    In [22]:
    d = Dict({'name': '张三', 'age': '李四'})
    d.name
    
    Out[22]:
    '张三'
     

    __getattr__是用来获取属性,那么__setattr__就是用来给属性赋值,当我们使用实例.key=value的方式进行赋值的时候就一定会调用__setattr__方法。

    In [27]:
    class Company(object):
        def __init__(self, name):
            self.company_name = name
    
        def __setattr__(self, name, value):
            print("__setattr__方法被调用")
    #         self.name = value   # 第一种写法
    #         object.__setattr__(self, name, value)   # 第二种写法
            self.__dict__[name] = value         # 第三种写法
    
    In [29]:
    c = Company('腾讯')
    c.company_name = '阿里'
    print(c.company_name)
    
     
    __setattr__方法被调用
    __setattr__方法被调用
    阿里
    
     

    为什么__setattr__被调用了两次呢?因为在__init__中也使用了一次实例.key=value的方式赋值。

    所以,在定义__setattr__的时候一定要注意,一定不能使用上述代码中被注释掉的第一种写法,因为使用self.name = value进行赋值时,本身又会再次调用__setattr__方法,这就造成了无线递归,造成bug。所以使用第二和第三种写法才是正确的。

     

    继续用__setattr__方法改造字典:

    In [30]:
    class Dict(dict):
        def __init__(self, *args, **kwargs):
            super(Dict, self).__init__(*args, **kwargs)
            
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
                
        def __setattr__(self, key, name):
            self[key] = name
    
    In [31]:
    d = Dict()
    d.name = '张三'
    print(d.name)
    
     
    张三
    
     
    • __getattribute__
     

    __getattribute__与上面的__getattr__很相似,区别在于__getattr__是在类中未找到属性时调用,而__getattribute__是不管类中有无查找的属性存在,都优先调用。不过在使用__getattribute__方法市,必须注意陷入无限递归,当在__getattribute__代码块中,再次执行属性的获取操作时,会再次触发__getattribute__方法的调用,代码将会陷入无限递归,直到Python递归深度限制,所以,在__getattribute__中获取属性时,需要通过父类的__getattribute__方法获取对应的属性。

    In [32]:
    class Company(object):
        def __init__(self, name):
            self.company_name = name
        
        def __getattribute__(self, name):
            print('__getattribute__方法被调用')
            return object.__getattribute__(self, name)
    #         raise AttributeError('哥们,你查找的属性"%s"不存在' % name)
    
    In [33]:
    c = Company('腾讯')
    c.company_name
    
     
    __getattribute__方法被调用
    
    Out[33]:
    '腾讯'
    In [34]:
    c.abc
    
     
    __getattribute__方法被调用
    
     
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-34-a2bb1cff9d71> in <module>
    ----> 1c.abc
    
    <ipython-input-32-e6bee225b017> in __getattribute__(self, name)
          5     def __getattribute__(self, name):
          6         print('__getattribute__方法被调用')
    ----> 7return object.__getattribute__(self, name)
          8 #         raise AttributeError('哥们,你查找的属性"%s"不存在' % name)
    
    AttributeError: 'Company' object has no attribute 'abc'
     
    • __dict__dir()__dir__
     

    上文中提到过__dict____dict__是对象的一个属性,并不是函数,它的作用是返回对象的所有属性名为key,属性值为value的一个字典,注意,这里所说的所有属性是指数据对象本身的属性,例如类的__dict__只包含类本身的属性和函数,而类实例也只包含类实例的属性。这一点与dir()函数不同,dir()将会返回一个列表,列表中包含对象所有有关的属性名。也就是说,__dict__dir()的子集。而dir()实际上调用的是__dir__方法。

    In [37]:
    class Company(object):
        def __init__(self, name):
            self.company_name = name
        
        def fun(self):
            print('fun方法被调用……')
    
    In [38]:
    c = Company('腾讯')
    
    In [40]:
    c.__dict__
    
    Out[40]:
    {'company_name': '腾讯'}
    In [41]:
    Company.__dict__
    
    Out[41]:
    mappingproxy({'__module__': '__main__',
                  '__init__': <function __main__.Company.__init__(self, name)>,
                  'fun': <function __main__.Company.fun(self)>,
                  '__dict__': <attribute '__dict__' of 'Company' objects>,
                  '__weakref__': <attribute '__weakref__' of 'Company' objects>,
                  '__doc__': None})
    In [44]:
    dir(c)
    
    Out[44]:
    ['__class__',
     '__delattr__',
     '__dict__',
     '__dir__',
     '__doc__',
     '__eq__',
     '__format__',
     '__ge__',
     '__getattribute__',
     '__gt__',
     '__hash__',
     '__init__',
     '__init_subclass__',
     '__le__',
     '__lt__',
     '__module__',
     '__ne__',
     '__new__',
     '__reduce__',
     '__reduce_ex__',
     '__repr__',
     '__setattr__',
     '__sizeof__',
     '__str__',
     '__subclasshook__',
     '__weakref__',
     'company_name',
     'fun']
    In [45]:
    c.__dir__()
    
    Out[45]:
    ['company_name',
     '__module__',
     '__init__',
     'fun',
     '__dict__',
     '__weakref__',
     '__doc__',
     '__repr__',
     '__hash__',
     '__str__',
     '__getattribute__',
     '__setattr__',
     '__delattr__',
     '__lt__',
     '__le__',
     '__eq__',
     '__ne__',
     '__gt__',
     '__ge__',
     '__new__',
     '__reduce_ex__',
     '__reduce__',
     '__subclasshook__',
     '__init_subclass__',
     '__format__',
     '__sizeof__',
     '__dir__',
     '__class__']
  • 相关阅读:
    Ubuntu 下安装 PHP Solr 扩展的安装与使用
    转载:Ubuntu14-04安装redis和php5-redis扩展
    Datagridview全选,更新数据源代码
    sftp不识别的问题ssh命令找不到
    linux:如何修改用户的密码
    win7.wifi热点
    Rico Board.1.环境配置
    linux学习记录.6.vscode调试c makefile
    linux学习记录.5.git & github
    linux学习记录.3.virtualbox 共享文件夹
  • 原文地址:https://www.cnblogs.com/chenhuabin/p/13752770.html
Copyright © 2011-2022 走看看