首页 > 技术文章 > Python基础 ( 十 ) —— 面向对象补充1(其他的内置__XXX__方法)

Matrixssy 2019-10-09 10:58 原文

isinstance(obj, cls) : 判断对象obj是否为一个类cls实例化出来的对象,能反映继承关系,即B继承A, B实例化出来的对象b, isinstance(b, A) 为 True

type() : 判断实例化出来的对象obj所属的类

issubclass(cls1, cls2) : 判断类cls1是否为类cls2的子类

raise XXX : 用来发出异常信号给屏幕, 比如 raise AttributeError('异常')   

 

 

 __getattribute__ : __getattr__的大哥,不管通过实例对象是否调用到某个方法或属性,都会先触发__getattribute__

其实内置的__getattribute__大体就做了这么两件事:

1、如果实例对象能调用到某个方法或属性,则使得实例获取到这个方法或属性

2、如果调用不到则raise AttributeError ,然后这个操作会呼唤小弟__getattr__执行

(注意这个AttributeError被小弟接住了,不会程序中断;但是默认的__getattr__小弟自己的逻辑还会raise一个AttributeError)

如下我们自己定义一个__getattribute__,模拟这个过程

class A:
    def __init__(self, x):
        self.x = x
    def __getattr__(self, item):
        print('执行getattr')
    def __getattribute__(self, item):
        print('执行getattribute')
        raise AttributeError('交给我小弟getattr去做')

a = A(1)
a.xx

 #__getitem__、__setitem__、__delitem__

类似前面的__getattr__ ......  ,不同的是这只当通过字典的方式(不是操作__dict__字典那种方式)操作才会触发

class A:
    def __setitem__(self, key, value):
        print('__setitem__被执行')
        self.__dict__[key] = value
    def __getitem__(self, item):
        print('__getitem__被执行')
        return self.__dict__[item]
    def __delitem__(self, key):
        print('__delitem__被执行')
        self.__dict__.pop(key)
a = A()
print(a.__dict__)
a['name'] = 'alex'                      #触发__setitem__
a['age'] = 18                           #触发__setitem__
print(a.__dict__)
del a['name']                           #触发__delitem__
print(a.__dict__)
print(a['age'])                         #触发__getitem__

 #__str__

__str__本身为内置的一个显示实例化对象的方法,也可自己定制.有以下两个特点:

当使用print输出对象的时候,会自动触发这个对象下的__str__(self)方法,并打印从这个方法中return的值

__str__方法需要返回一个字符串,当做这个对象的描写

class A:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __str__(self):
        return '自定的实例显示方法%s %s' %(self.name, self.age)
a = A('alex', 18)
print(a)                        # print实际会触发 str(a) --> 也就是a.__str__,打印出a.__str__的返回值
#通过以下例子就可以看出来
class B(list):
    def __str__(self):
        return '我自定的显示'
print(B('abcdef'))
#如果再怀疑print()是否真的触发了__str__ 可以去终端试试(终端定义类的或者函数的时候会进入 ... 为开头的模式,定义完成后连续两下Enter就可退出)
#事实证明,自己定义的__str__在print()后才触发, 并且str(a) 其实就是在调用a.__str__

 

__repr__

类似__str__, 只是__str__是在print时候触发, 而__repr__在解释器里显示的时候触发的

并且如果这个类没定义__str__,则print()就用__repr__的返回值,有的话就使用__str__的返回值,以下为终端演示:

 

 __format__

当使用format(a)时候, 其实就是在使用a.__format__,例如:

class Date:
    def __init__(self,year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day
date = Date(2019, 10, 9)
print("{0.mon}-{0.day}-{0.year}".format(date))                 #调用实例对象属性的format格式化

但是我们为何不自定一个format()的规则呢?如下:

class Date:
    format_dic = {
        'm-d-y': '{0.mon}-{0.day}-{0.year}',
        'y:m:d': '{0.year}:{0.mon}:{0.day}'
    }
    def __init__(self,year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day
    def __format__(self, format_spec):
        if not format_spec or (format_spec not in self.format_dic):
            print('使用默认格式')
            fm = '{0.year} {0.mon} {0.day}'
            return fm.format(self)
     print('使用指定格式') fm
= self.format_dic[format_spec] return fm.format(self) date = Date(2019, 10, 9) print(format(date)) print(format(date, 'm-d-y'))

__slots__

之前的小补充:使用 . 的方式访问属性本质上就是在方位对象的__dict__字典,类的字典是共享的,而每个对象又是独立的(这也就是为什么实例化的对象的__dici__字典里只有__init__里定义的属性)

 

 为什么要使用__slots__?: 字典会占用大量内存,而每个类实例化出来的对象都有着独立的属性字典,如果实例化的对象太多,占用内存就太大;所以可以使用__slots__替代属性字典__dict__来节省内存。下面为使用的例子:

class Foo:
    #创建单个属性
    # __slots__ = 'name'            #相当于属性字典里的 {'name':None}
    #若创建多个属性
    __slots__ = ['name', 'age']     #相当于属性字典里的 {'name':None, 'age':None}
f1 = Foo()
f1.name = 'alex'
# print(f1.__dict__)                #会报错,用了__slots__生成的实例对象,已经不再有__dict__属性
#f1.age = 18                        #会报错,已经无法增加新的属性,因为本质上是在调用__setattr__,而这需要操作__dict__属性字典
print(f1.__slots__, Foo.__slots__)  #这时候已经没有实例的属性字典了,他俩一样
print(f1.name)
print(f1.__slots__)

但是注意:慎用,因为通过__slots__定义对象属性,相当于把对象的属性给写死了,没法再添加;同时所有有关操作__dict__的方法都失效了

__doc__

查看类的描述信息,但注意:这个描述信息没法被子类继承

class Foo:
    " 这是描述信息 "
    pass
class Foo1(Foo):
    pass
f = Foo()
f1 = Foo1
print(f.__doc__)
print(f1.__doc__)
View Code

__module__: 查看当前操作的对象在哪个模块(调用的外部模块或者当前模块__main__)

__class__: 查看当前操作的对象的类是什么

__del__: 析构函数,当对象在内存中被释放时,自动触发执行

注:此方法一般无需定义,因为Python是一门高级语言,程序猿使用时无需关心内存的分配和释放,都是交给Python解释器来执行;

所以析构函数的调用是在解释器进行垃圾回收时自动触发执行的

class Foo:
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print('对象被释放')
f1 = Foo('alex')
del f1                          #此时对象f1被删除后,内存中被释放
print('------------')
#注意当程序执行完毕后,后自动释放内存,此时会触发__del__
class Foo1:
    def __init__(self, name):
        self.name = name
    def __del__(self):
        print('对象被释放')
f2 = Foo1('alex')
del f1.name
print('------------')
View Code

 __call__: 对象后面加括号时,触发执行

注:对象()的形式本质上是在调用对象的__call__:__call__会返回一个对象

class Foo:
    pass
f = Foo()
#print(f.__call__)               #对象f没有__call__方法
#f()                             #所以无法使用对象()的形式
class Foo1:
    def __call__(self, *args, **kwargs):
        print('使用了对象(),触发了__call__')
f1 = Foo1()
f1()                             #触发__call__
View Code

__next__、__iter__

复习一下迭代器协议: 是指对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,否则引起一个stop Iteration异常,以终止迭代,并且不可逆。

如何利用__next__、__iter__做一个自己的迭代器?

class Foo:
    def __init__(self, n):
        self.n = n
    def __iter__(self):
        return self
    def __next__(self):
        if self.n >= 5:
            raise StopIteration('迭代完毕')
        else:
            self.n += 1
            return self.n

f = Foo(0)
#迭代完毕后继续迭代会raise StopIteration('迭代完毕') 错误
# f.__next__()
# f.__next__()
# f.__next__()
# f.__next__()
# f.__next__()
# f.__next__()
#用 for 则会自动停止,处理好你这个错误: 用了 try: except StopIteration : ;详情看迭代器那章
for i in f:                         #相当于iter(f) == f.__iter__(), 然后  f.__iter__().__next__() = i
    print(i)

 创建一个斐波那契迭代器

class Fibonacci:
    def __init__(self):
        self.n1 = 1
        self.n2 = 1
        self.count = True                               #为了返回第一个数设置的开关
    def __iter__(self): 
        return self
    def __next__(self):
        if self.n1 > 100:
            raise StopIteration('迭代终止')
        elif self.n2 == 1 and self.count:
            self.count = False
            return 1
        self.n1, self.n2 = self.n2, self.n1 + self.n2
        return self.n1
f = Fibonacci()
for i in f:
    print(i)
View Code

#上下文管理协议:__enter__、__exit__

当我们使用with open() as f : 的时候,其实就是open类实例化出一个对象,然后把这个对象赋值给变量f

那么是不是其他类也能通过这种方式实例化对象呢?答案是肯定的

我们只要在这个类中定义__enter__与__exit__方法就可以实现这种方式,具体如下

class Foo:

    def __enter__(self):
        print('enter触发')
        return self                 #如果不返回对象,这个对象就不会赋值给下面的f

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit触发')

with Foo() as f:                    #在这里触发__enter__, 并把Foo()实例化出来的对象传给__enter__;也就是f=Foo().__enter__();而不是认为做了f=Foo()--》特别注意!!
    print(f)                        #在下面的代码块执行完毕后触发__exit__
print('这时已经触发完__exit__')

 

__exit__触发意味着with这个结构结束了

这里问题又来了,__exit__中的exc_type, exc_val, exc_tb三个参数又是什么呢?

如果with 下面的代码块正确执行,那么他们都的值都是None,一旦代码块中间报错时,会先触发__exit__,然后抛出异常

 

exc_type:发生的异常类型

 exc_val:发生异常的值

 exc_tb:异常的追踪信息

小补充:任何异常都分为三部分,而上面三个参数就是分别获取这三个部分

class Foo:

    def __enter__(self):
        print('enter触发')
        return self                 

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit触发')
        print(exc_type)
        print(exc_val)
        print(exc_tb)

with Foo() as f:
    print(不存在的变量)              #这里触发__exit__
    print(f)
print('这时已经触发完__exit__')

若__exit__ return 一个True,那么这个异常会被吃了,代码继续正常执行;但在with这个结构中,触发了__exit__之后就会退出这个结构的代码块

class Foo:

    def __enter__(self):
        print('enter触发')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit触发')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True

with Foo() as f:
    print(不存在的变量)                           #这里触发__exit__
    print(f)
    print("你看,异常被吃了,这里没有继续执行")
print('这时已经触发完__exit__')

总结: with open obj as f:

      代码块

1. with obj ----> 触发obj.__enter__() --> f = obj.__enter__()

2.执行代码块

一:没有异常的情况下,整个代码块执行完后触发__exit__,它的三个参数都为None

二:有异常情况下,从异常出现的位置直接触发__exit__() ;

a:如果__exit__的返回值为True,代表吞掉异常,with语句退出

b:如果__exit__的返回值为不为True,raise异常

c:__exit__的运行代表with语句的结束

三:以上的例子都没做释放内存的操作,需要在__exit__实现,后面再讲

优点:

 

 

#描述符: __get__()__set__()__delete__()注意别和__del__混了,__del__是析构方法    重要!!!

一个实现了 描述符协议 的类就是一个描述符。

描述符协议:实现了 __get__()__set__()__delete__() 其中至少一个方法的类,就是一个描述符。

  • __get__: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。

  • __set__:将在属性分配操作中调用。不会返回任何内容。

  • __delete__:控制删除操作。不会返回内容。

描述符分两种:

1、数据描述符:至少实现了__get__()和__set__()

2、非数据描述符:没有实现__set__()

注意事项:

  • 描述符本身应该定义为新式类,被代理的类也应该是新式类

  • 必须把描述符定义成这个类的类属性,不能定义到构造函数__init__中

  • 严格遵循该优先级:由高到低

    1、类属性

         2、数据描述符

    3、实例属性

    4、非数据描述符

    5、找不到属性触发__getattr__()

class Foo:
    def __set__(self, instance, value):
        print('set')
    def __get__(self, instance, owner):
        print('get')
    def __delete__(self, instance):
        print('delete')

class Bar:
    x = Foo()
    def __init__(self):
        self.x = 1
b = Bar()
Bar.x                 #类调用时,也会触发__get__
print(b.x)            #数据描述符 > 实例属性, 于是得到的不是__init__里定义的  1,而是__get__的返回值
b.x = 1               #触发__set__, 因为实例属性 < 数据描述符
print(b.x)            #b.x的值还是那个代理的,而不是改成1
print(Bar.x)
Bar.x = 1             #不会触发__set__,类属性优先级最高
print(Bar.x)          #看到Bar.x的值被覆盖
#不信还可这样试试
#del Bar.x
#b.x       -->  报错,x被删了
类属性> 数据描述符> 实例属性
实例属性> 非数据描述符

使用方法:

class Desc:
    def __init__(self, name):                     #为了记录被代理的变量的名字, 如这里的 'x'
        self.name = name

    def __get__(self, instance, owner):           #这里的instance 就是实例t ;owner 为拥有这个实例的类TestDesc
        print("get!!!!")
        print("self=", self)
        print("instance=", instance)
        print("owner=", owner)
        print('*'*50, "\n")
        #return '返回值:{}'.format(instance.__dict__[self.name])

    def __set__(self, instance, value):            #value 为你设置的值
        print("set!!!!")
        print("self=", self)
        print("instance=", instance)
        print("value=", value)
        print('*'*50, "\n")
        #下面使用可以看出,实例t的字典里并没有设置的值,所以这里可以手动把值添加到字典中
        # instance.__dict__[self.name] = value

    def __delete__(self, instance):
        print("delete!!!!")
        print("self=", self)
        print("instance=", instance)
        print('*' * 50, "\n")


class TestDesc:
    x = Desc('x')                        #通俗的说就是在TestDesc这个类中,用Desc去描述x属性;相当于x代理给了Desc
    y = 1
    def __init__(self, n):
        self.x = n                      #不会存于实例的字典中,但是会存于类的字典中

t = TestDesc('__set__ 里面的value就是这个')  #触发__set__:
print(t.__dict__)                            #实例的字典里没x
print(t.x)                                   #触发__get__:当实例化对象属性(等于描述符的那个属性)被调用时
print(TestDesc.x)                            #通过类调用也会触发__get__,只是instance值为None
t.x = '这个值传入__set__中'                  #触发__set__:当实例化对象设置描述符属性时候触发,如上面的x=Desc()
del t.x                                      #触发__delete__
print(TestDesc.__dict__)                     #类的字典里有x

特别注意:通过类的方式调用被代理的属性,也会触发__get__,只是此时instance的值为None!!!

为什么要使用描述符?   参考:https://www.cnblogs.com/wongbingming/p/10781688.html

 

 这里附上最后的代码:

class Score:
    """
    这是关于限制分数格式的描述符
    """
    def __init__(self, default=0):
        self._score = default

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError('Score must be integer')
        if not 0 <= value <= 100:
            raise ValueError('Valid value must be in [0, 100]')

        self._score =value

    def __get__(self, instance, owner):
        return self._score

    def __delete__(self, instance):
        del self._score

class Student:
    math = Score()
    chinese = Score()
    english = Score()

    def __init__(self, name, math, chinese, english):
        self.name = name
        self.math = math
        self.chinese = chinese
        self.english = english

    def __repr__(self):
        return "<Student: {}, math: {}, chinese: {}, english: {}>".format(
            self.name, self.math, self.chinese, self.english
        )

stu1 = Student('alex', 76, 90, 95)
print(stu1, '\n', stu1.math, stu1.chinese, stu1.english)

#如果输入成绩不满足分数描述符里的限制
# stu2 = Student('alex', 110, 90, 70)
# stu3 = Student('alex', '90', 90, 70)
View Code

但是以上不仅把输入的类型写死了,而且也没有把值添加到实例的字典中,下面这样会更好

class Score:
    """
    这是关于限制分数格式的描述符
    """
    def __init__(self, name, type):
        self._name = name
        self._type = type

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError('%s Score must be %s' %(self._name, self._type))
        if isinstance(value, int):
            if not 0 <= value <= 100:
                raise ValueError('Valid value must be in [0, 100]')
        instance.__dict__[self._name] = value

    def __get__(self, instance, owner):
        return instance.__dict__[self._name]

    def __delete__(self, instance):
        instance.__dict__.pop(self._name)

class Student:
    name = Score('name', str)
    math = Score('math', int)
    chinese = Score('chinese', int)
    english = Score('english', int)

    def __init__(self, name, math, chinese, english):
        self.name = name
        self.math = math
        self.chinese = chinese
        self.english = english

    def __repr__(self):
        return "<Student: {}, math: {}, chinese: {}, english: {}>".format(
            self.name, self.math, self.chinese, self.english
        )

stu1 = Student('alex', 76, 90, 95)
print(stu1)
#如果输入不满足分数描述符里的限制
# stu2 = Student(666, 100, 100, 100)
# stu3 = Student('alex', 110, 90, 70)
# stu4 = Student('alex', '90', 90, 70)
View Code

 

补充1: 软件开发规范

--项目名称
    -- bin  一个项目的启动文件装在这个文件夹下
        -- strat.py
    -- conf config配置,配置文件
        -- settings.py 可能会发生改变的配置信息
    -- core 核心代码
        -- 相关文件 实际的python代码所在的文件
    例如: -- user.py -- auth.py -- main.py -- lib 第三方库 -- db database 数据库 一堆数据文件,不一定是py文件 -- log 日志
此外,还可以有readme文件:
补充2:
pycharm 创建项目时,会自动把项目的主文件夹添加到环境变量中
方便你调用库,但是用open读取文件的时候无效(因为是在当前工作路径下读取)
小补充:
1、形如__XX__为内置方法,即系统自己给你定义的方法,并且如果你也定义了名字一样的方法则用你定义的。
2、getattr()...别看很__getattr__()...他们很像,但是功能完全不同,也没联系
3、python是弱类型语言:定义变量时无需定义它的类型


推荐阅读