第三模块:02面向对象的编程(二)

时间:2021-09-16 22:28:44

4.绑定方法与非绑定方法

在类内部定义的函数分为俩大类,绑定方法以及非绑定方法

4.1绑定方法:绑定给谁,就由谁来调用,谁用就会把调用者当做第一个参数传入

绑定给对象的方法:最基本的方法,对于类所定义的一般方法基本都是绑定给对象的方法,没有被任何装饰器修饰的方法

class dog:
    def __init__(self,name,color,age):
        self.color = color
        self.age = age
        self.name = name
    def running(self):
        print("%s在快乐的田野上欢快的奔跑着!"%self.name)

dog1= dog('白狗','',3)
dog2 = dog('黑狗','',8)
print(dog1.running())
print(dog2.running())
#结果为
白狗在快乐的田野上欢快的奔跑着!

黑狗在快乐的田野上欢快的奔跑着!

绑定给类的方法:在类内的定义的装饰器classmethod修饰的方法

class A:
    x=1
    @classmethod
    def test(cls):
        print(cls,cls.x)

class B(A):
    x=2
B.test()

'''
输出结果:
<class '__main__.B'> 2
'''
#可以看出的就是B调用所以在test方法中久调用B

4.2非绑定方法:在类内的定义的装饰器staticmethod修饰的方法

没有自动传值一说,只是一个普通工具,对象和类都可以使用,不与类或者方法绑定

class Foo:
    @staticmethod #装饰器
    def spam(x,y,z):
        print(x,y,z)

5.内置方法

5.1反射

反射:通过字符串映射到对象的属性

hasattor(obj,'name') #判断obj中是否有obj.name的方法

getattor(obj,'name',none)#拿到一个属性的值

setattor(obj,'sex','male')#修改sex的属性为男性

delatter(obj,'age')#删掉类的属性

具体使用

class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
    def rent_house(self):
        print('%s 黑中介租房子啦,傻逼才租呢' %self.name)

b1=BlackMedium('万成置地','回龙观天露园')

#检测是否含有某属性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))

#获取属性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()

# getattr(b1,'aaaaaaaa') #报错
print(getattr(b1,'aaaaaaaa','不存在啊'))

#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'sb')
print(b1.__dict__)
print(b1.show_name(b1))

#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
delattr(b1,'show_name111')#不存在,则报错

print(b1.__dict__)

反射当前模块成员

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys


def s1():
    print 's1'


def s2():
    print 's2'


this_module = sys.modules[__name__]

hasattr(this_module, 's1')
getattr(this_module, 's2')

反射之反射的好处

好处一:实现可插拔机制

有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

egon还没有实现全部功能

class FtpClient:
    'ftp客户端,但是还么有实现具体的功能'
    def __init__(self,addr):
        print('正在连接服务器[%s]' %addr)
        self.addr=addr

不影响lili的代码编写

#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):
    func_get=getattr(f1,'get')
    func_get()
else:
    print('---->不存在此方法')
    print('处理其他的逻辑')

好处二:动态导入模块(基于反射当前模块成员) !

其他的没看完,再说吧

6.元类

元类在整个面向过程的学习中也属于一个重点知识,虽说对于一般情况,我们或许会直接调用class来创建一个类,直接来使用,但是对于一些需要高深的情况也是必须的,他对于一些规则有着强力的控制,所以说对于元类也是属于一个选修的课程

6.1储备知识

储备知识为exec,说实在我也只会用,对于他的原理还是一知半解

对于exec他需要三个参数

  • 参数1:字符串形式的命令
  • 参数2:全局作用域(字典形式)如果不指认,默认用globals()
  • 参数3:局部作用域(字典形式)如果不指认,默认用local()
#可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
g={
'x':1,
'y':2
}
l={}

exec('''
global x,z
x=100
z=200

m=300
''',g,l)

print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}

然后就没了。。。。。

6.2什么是元类

在说明之前,需要知道的是,既然python一切皆对象,那么对象怎么用呢?

  1. 都可以被引用
  2. 都可以当做函数的参数传入
  3. 都可以当做函数的返回值
  4. 都可以当做容器类元素(备注:容器类元素就是指列表啊,函数啊,元组啊这类)

看代码

class Foo:
      pass

f1=Foo() #f1是通过Foo类实例化的对象

既然一切都为对象,那么Foo也为对象,那么Foo是怎么生成的呢?这个时候就引出元类的概念

元类是类的类,是类的模板

产生类的类称之为元类,默认使用class定义的类,他们的元类都是‘type’

元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

6.3定义类的俩种方法

6.3.1用class

这个是最基本的方法,直接调用python内置创建类的方法class就可以直接创建类,也是我们最常见的创建的方法。

class Chinese(object):
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)
#这样一个类就这样创建好了

6.3.2用type

用type其实就是在用class一步一步的创建一个元类

定义类的三要素:类名,类的基类,类的名称空间(或者叫类体)

#类名
class_name='Chinese'
#类的父类
class_bases=(object,)
#类体
class_body="""
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print('%s is talking' %self.name)
"""

步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

步骤二:调用元类type(也可以自定义)来产生类Chinense

Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo


print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''

我们看到,type 接收三个参数:

  • 第 1 个参数是字符串 ‘Foo’,表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

补充:若Foo类有继承,即class Foo(Bar):.... 则等同于type('Foo',(Bar,),{})

6.4自定义元类的应用

关于这部分还是注明一下,代码部分从egon老师处拷贝

6.4.1自定义元类,控制类的创建

#步骤一:如果说People=type(类名,类的父类们,类的名称空间),那么我们定义元类如下,来控制类的创建
class Mymeta(type):  # 继承默认元类的一堆属性
    def __init__(self, class_name, class_bases, class_dic):
        if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():
            raise TypeError('必须为类指定文档注释')

        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)


class People(object, metaclass=Mymeta):
    country = 'China'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print('%s is talking' % self.name)

这样我们就对创建类有了要求,创建类必须有注释同时类名首字母必须是大写

6.4.2自定义元类,控制类的实例化

#知识储备:
    #产生的新对象 = object.__new__(继承object类的子类)
#步骤二:如果我们想控制类实例化的行为,那么需要先储备知识__call__方法的使用
class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age

    def __call__(self, *args, **kwargs):
        print(self,args,kwargs)


# 调用类People,并不会出发__call__
obj=People('egon',18)

# 调用对象obj(1,2,3,a=1,b=2,c=3),才会出发对象的绑定方法obj.__call__(1,2,3,a=1,b=2,c=3)
obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

#总结:如果说类People是元类type的实例,那么在元类type内肯定也有一个__call__,会在调用People('egon',18)时触发执行,然后返回一个初始化好了的对象obj

#步骤三:自定义元类,控制类的调用(即实例化)的过程
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、实例化People,产生空对象obj
        obj=object.__new__(self)


        #2、调用People下的函数__init__,初始化obj
        self.__init__(obj,*args,**kwargs)


        #3、返回初始化好了的obj
        return obj

class People(object,metaclass=Mymeta):
    country='China'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def talk(self):
        print('%s is talking' %self.name)

obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}

#步骤四:
class Mymeta(type): #继承默认元类的一堆属性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('类名首字母必须大写')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、调用self,即People下的函数__new__,在该函数内完成:1、产生空对象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)

        #2、一定记得返回obj,因为实例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country='China'

    def __init__(self,name,age):
        self.name=name
        self.age=age

    def talk(self):
        print('%s is talking' %self.name)

    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj

obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}

#步骤五:基于元类实现单例模式,比如数据库对象,实例化时参数都一样,就没必要重复产生对象,浪费内存
class Mysql:
    __instance=None
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port

    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance


obj1=Mysql()
obj2=Mysql()
print(obj1 is obj2) #False

obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True

#应用:定制元类实现单例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定义类Mysql时就触发
        self.__instance=None
        super().__init__(name,bases,dic)

    def __call__(self, *args, **kwargs): #Mysql(...)时触发

        if not self.__instance:
            self.__instance=object.__new__(self) #产生对象
            self.__init__(self.__instance,*args,**kwargs) #初始化对象
            #上述两步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port


obj1=Mysql()
obj2=Mysql()

print(obj1 is obj2)

 

7.异常处理

说明一下,对于异常处理目前只理解他的原理,对于他的使用还是很好的理解,或许还没到那个地步吧。

7.1异常第定义

异常:异常是错误发生的信号,一旦出错,并且程序没有处理这个错误,就会抛出异常,这个时候程序就会运行中止

第三模块:02面向对象的编程(二)

 

7.2异常的分类

异常的分类主要分语法错误以及逻辑错误,下面详细说一下

语法错误

对于语法错误,就是字面的意思,语法错误,for循环没加:或者是小括号没加右面那个,绝大多数就是失误了,程序在编译之前就会先检查语法错误的,看一下一些错误示例吧

#语法错误示范一
if
#语法错误示范二
def test:
    pass
#语法错误示范三
class Foo
    pass
#语法错误示范四
print(haha)

这种绝大多数是打字打快了,缺少的,重新写一下就好了

逻辑错误

逻辑错误是写代码中绝大多数会碰到的,包括我自己在学习初期,碰到的绝大多数都是逻辑错误,这个就需要自己慢慢处理了

看一下一些基本的逻辑错误

#TypeError:int类型不可迭代
for i in 3:
    pass
#ValueError
num=input(">>: ") #输入hello
int(num)

#NameError
aaa

#IndexError
l=['egon','aa']
l[3]

#KeyError
dic={'name':'egon'}
dic['age']

#AttributeError
class Foo:pass
Foo.x

#ZeroDivisionError:无法完成计算
res1=1/0
res2=1+'str'

然后我们说一下常用的逻辑的错误

AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的

其他逻辑错误,就没有那么常用了

ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError

7.3异常的处理

为了保证程序的容错性与健壮性,我们需要程序的异常做处理,对异常处理主要是以下俩种

对于错误发生的条件是可以预知的,此时应该用if来预防,这种情况我们遇到的最多,对于条件之外的其他都会 用if啊,elif啊,else啊来控制,看代码

AGE=10
while True:
    age=input('>>: ').strip()
    if age.isdigit(): #只有在age为字符串形式的整数时,下列代码才不会出错,该条件是可预知的
        age=int(age)
        if age == AGE:
            print('you got it')
            break

对于错误发生条件不可预见的,应该用异常处理机制,try...except...,还是看代码吧

#基本语法为
try:
    被检测的代码块
except 异常类型:
    try中一旦检测到异常,就执行这个位置的逻辑
#举例
try:
    f=open('a.txt')
    g=(line.strip() for line in f)
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
except StopIteration:
    f.close()
#打开一个文本文档,文档只有四行,按照代码,这样就会报错,但是错误的时候程序就会退出,文本文档就没有正常关闭,这样就需要在程序退出后还是正常关闭文本文档的。

对于try ... except...的详细用法下一小节在说明

7.4try ... except...的详细用法

1.异常类只能用来处理指定的异常情况,如果非指定异常则无法处理

s1 = 'hello'
try:
    int(s1)
except IndexError as e: # 未捕获到异常,程序直接报错
    print e

2.多分支,说白了多分支就和if的多条件一毛一样

多分支:被监测的代码块抛出的异常有多种可能性,对每一种异常都定制专门的处理逻辑

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)

3.外能异常处理

exception,被监测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型只用一种处理逻辑就可以了

s1 = 'hello'
try:
    int(s1)
except Exception as e:
    print(e)

不过也可以在多分支后面再加上这个万能处理

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
except Exception as e:
    print(e)

4.其他异常机构

s1 = 'hello'
try:
    int(s1)
except IndexError as e:
    print(e)
except KeyError as e:
    print(e)
except ValueError as e:
    print(e)
#except Exception as e:
#    print(e)
else:
    print('try内代码块没有异常则执行我')
finally:
    print('无论异常与否,都会执行该模块,通常是进行清理工作')

5.主动触发异常

对于程序,别人错误输入的时候我们希望主动抛出错误,用于提醒,这就是主动抛出的作用了

try:
    raise TypeError('类型错误')
except Exception as e:
    print(e)

6.自定义异常类型

class EgonException(BaseException):
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

try:
    raise EgonException('类型错误')
except EgonException as e:
    print(e)

所谓的自定义就是按照异常处理这个类自定义自己的异常处理,作为首要的就是继承系统自带的异常错误的类,有一个输出值以及返回值

 7.断言assert

所谓断言,可以这么理解,我完成这一阶段的事情了,你必须给我特定的东西,没有符合,那么对不起,你没有达标,就要抛出异常了

断言需要明确的结果

info = {}
info['name']= 'alex'
info['age']= 18

assert('name' in info)and ('age'in info)#必须有name和age这俩个key
if info ['name']== 'alex'and info['age']==18:
    print('welcome')

 

最后说一下异常的好处吧

  • 1:把错误处理和真正的工作分开来
  • 2:代码更易组织,更清晰,复杂的工作任务更容易实现;
  • 3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;