面向对象、类、元类、封装、异常处理

一、

1.1面向过程编程:过程二字为重点,是指解决问题的步骤。类似一条流水线,机械式的思维方式

    优点:复杂的流程流程化,进而简单化。

    缺点:可扩展性差

       面向对象编程:对象为重点,对象就是”数据“和”功能“的组成。

    优点:可扩展性强,

    缺点:编程复杂度高

  应用场景:用户需求经常变化,互联网应用,游戏,企业

1.2类:是一系列对象相似特征与技能的结合体。强调:站的角度不一样,得到的分类是不一样的。

  现实中:是先有类,再有对象。

  在程序中:是先定义类,后调用类来产生对象。

类的用途:属性----增、删、改、查、

类的定义:

#先定义类
class LuffyStudent:
    school='luffycity' #数据属性
 
 
    def learn(self): #函数属性
        print('is learning')

  类体内的代码在类定义阶段就会执行!!!,可以使用__dict__来查看。

#__init__方法用来为对象定制对象自己独有的特征
class LuffyStudent:
    school='luffycity'
    def __init__(self,name,sex,age):  #对象的独有特征
        self.Name=name
        self.Sex=sex
        self.Age=age
 
    def learn(self):  # 对象的公共特征
        print('is learning')
 
    def eat(self):
        print('is sleeping')
 
 
#后产生对象
stu1=LuffyStudent('王二丫','',18) #LuffyStudent.__init__(stu1,'王二丫','女',18)
print(stu1.__dict__)
 
>>>>>>{'Name': '王二丫', 'Sex': '', 'Age': 18}
 
 
#加上__init__方法后,实例化的步骤
# 1、先产生一个空对象stu1
# 2、LuffyStudent.__init__(stu1,'王二丫','女',18)

  类中的数据属性:是所以对象共有的

  类中的函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方式时,会把对象本身当作第一个传入,传给self

  pyrhon中一切皆对象。

  1.3继承:指的是类与类之间的关系,主要功能就是解决代码中用的问题;是一种创建新类的方式,新建的类可以继承一个或者多个父类,新建的类可称为子类和派生类,父类又可称为基类或者超类。

内置属性可以用通过__bases__来查看类继承的所有父类

属性查找:1.对象本身>>>2.对象的类>>>>3.父类

  继承多个父类时的属性查找顺序:python会解析顺序(mro)列表,其遵循的原则:1.子类会先于父类被检查;2.多个父类会根据他们在列表中的顺序被检查;3.如果下一个类存在两个合法的选择,会选择第一个父类。

  1.4派生:

  子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

  在子类派生出新的方法中重用父类的方法:

    方式一:指名道姓(不依赖继承)【父类名称.方法名称(self,参数)】

    方式二:super()  (依赖继承)  Python2:---【super(对象本身的类名,self).方法名称(具体的数据)】    python3---【super().方法名称(具体的数据)】

  1.5组合:在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day

    def tell_info(self):
        print('%s-%s-%s' %(self.year,self.mon,self.day))

二、抽象类

import abc

class Animal(metaclass=abc.ABCMeta): #只能被继承,不能被实例化
    all_type='animal'

    @abc.abstractmethod
    def run(self):
        pass

    @abc.abstractmethod
    def eat(self):
        pass

# animal=Animal()

class People(Animal):
def run(self):
print('people is running')

def eat(self):
print('people is eating')

三、多态与多态性

  多态指一类事物拥有多种形态

  多态性指在不考虑实例类型的情况下使用实例

  多态性分为静态多态性和动态多态性

  多态性的好处:

    1.增加了程序的灵活性

      以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

    2.增加了程序额可扩展性

      通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

四、封装

  封装指的是如何进行属性的隐藏

  如何进行封装:使用双下划线(__)开头的方式将属性隐藏起来(设置成私有的)

  其实所有的双下划线开头的名称都会在类定义阶段变形,且只变形一次。

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,
#这种,在外部是无法通过__x这个名字访问到。

  变形的特点:

    1.在类的外部无法直接访问属性;可间接访问_类名__属性。如:_A__foo

    2.类的内部可以直接访问;直接访问:__属性

    3.子类无法覆盖父类__开头的属性。

  封装的意义:

    封装的数据属性:明确的区分内外。把数据隐藏起来,然后对外提供操作该数据的接口,在接口内附加上对该数据的限制,已完成对该数据属性操作的严格限制。

    封装的方法:目的是隔离复杂度

    封装与扩展性:封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

  property(特性):property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。

    如何使用:

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height

    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People('jake',80,1.95)
print(p1.bmi)

    

  绑定方法与非绑定方法

    一、绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入【自动传值】):

    1. 绑定到类的方法:classmethod装饰器装饰的方法。

                  为类量身定制

                  类.boud_method(),自动将类当作第一个参数传入

                (其实对象也可调用,但仍将类当作第一个参数传入)

    2. 绑定到对象的方法在类内定义的没有被任何装饰器装饰的方法

                 为对象量身定制

                 对象.boud_method(),自动将对象当作第一个参数传入

               (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)

    二:非绑定方法:用staticmethod装饰器装饰的方法

        1. 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已

    注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说。

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

      hasattr(对象名/类,'属性')    判断有没有这个属性

      getattr(对象名/类,'属性')   拿到对象的属性

      setattr(对象名/类,'属性')    修改对象的属性

      delattr(对象名/类,'属性')    删除对象的属性

   

    内置方法介绍:链接

    item系列:

class Foo: #Dict
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item): #item='namexxx'
        # print('getitem...')
        return self.__dict__.get(item)

    def __setitem__(self, key, value):
        # print('setitem...')
        # print(key,value)
        self.__dict__[key]=value

    def __delitem__(self, key):
        # print('delitem...')
        # print(key)
        del self.__dict__[key]

obj=Foo('egon')
print(obj.__dict__)


# 查看属性:
obj.属性名
print(obj['namexxx']) #obj.name


# 设置属性:
obj.sex='male'
obj['sex']='male'

print(obj.__dict__)
print(obj.sex)


# 删除属性
del obj.name
del obj['name']

print(obj.__dict__)

    __str__内置方法:

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

    def __str__(self):
        # print('====>str')
        return '<name:%s,age:%s>' %(self.name,self.age)

obj=People('egon',18)
print(obj) #res=obj.__str__()

    __del__内置方法:

class Open:
    def __init__(self,filename):
        print('open file.......')
        self.filename=filename

    def __del__(self):
        print('回收操作系统资源:self.close()')

f=Open('settings.py')
del f #f.__del__()
print('----main------') #del f #f.__del__()

一切皆对象,对象可以怎么用?

  1.都可以被引用,x=obj

  2.都可以当做函数的参数传入

  3.都可以当做函数的返回值

  4.都可以当做容器类的元素,l = [func,time,obj,1]

  

  exec的用法:

#exec:三个参数

#参数一:包含一系列python代码的字符串

#参数二:全局作用域(字典形式),如果不指定,默认为globals()

#参数三:局部作用域(字典形式),如果不指定,默认为locals()

#可以把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}

  元类:产生类的类称为元类,默认所有用class定义的类的元类是type

# 定义类的两种方式:
# 方式一:class
class Chinese:  # Chinese=type(...)
    country = 'China'

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

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


# print(Chinese)
obj = Chinese('egon', 18)
print(obj, obj.name, obj.age)

# 方式二:type
# 定义类的三要素:类名,类的基类们,类的名称空间
class_name = 'Chinese'
class_bases = (object,) # 默认为object

class_body = """
country='China'

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

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

class_dic = {}
exec(class_body, globals(), class_dic)
print(class_dic)
Chinese = type(class_name,class_bases,class_dic)

  如何自定义元类来控制类的创建

class Mymeta(type):
    def __init__(self, class_name, class_bases, class_dic):
        if not class_name.istitle():
            raise TypeError('类名的首字母必须大写')

        if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
            raise TypeError('必须有注释,且注释不能为空')  # 主动抛出异常

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)  # 继承原来的属性


class Chinese(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)


# Chinese=Mymeta(class_name,class_bases,class_dic)

  如何自定义元类来控制类的实例化

    __call__

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

obj=Foo()
#1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
#2、调用obj的返回值就是__call__方法的返回值
res=obj(1,2,3,x=1,y=2) 

  由此可得,调用一个对象,就是在触发对象所在类的__call__方法的执行,同理得类本身也存在一个__call__方法。分析如下:

class Mymeta(type):  # 只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
    def __call__(self, *args, **kwargs):  # self=<class '__main__.Foo'>
        # 1、调用__new__产生一个空对象obj
        obj = self.__new__(self)  # 此处的self是类Foo,必须传参,代表创建一个Foo的对象obj

        # 2、调用__init__初始化空对象obj
        self.__init__(obj, *args, **kwargs)

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


class Foo(object, metaclass=Mymeta):
    school = 'beida'

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

    def say(self):
        print('%s says welcome to the BeiDa to learn Python' % self.name)


# 调用Foo就是在调用Foo类中的__call__方法
# 然后将Foo传给self,溢出的位置参数传给*,溢出的关键字参数传给**
# 调用Foo的返回值就是调用__call__的返回值
t1 = Foo('jake', 22)
print(t1)  # 123
View Code

默认的在调用Foo时会发生三件事:  

    1.产生一个空对象

    2.调用__init__方法初始化对象obj

    3.返回初始化以后的对象obj

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

  错误的类型,分成两种:

    1.语法错误,(在程序运行之前就会检测语法,需要先改正过来才可以运行程序);

    2.逻辑错误  

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

    

  异常处理:为了保证程序的容错性,在遇到错误时程序不会奔溃,我们就需要进行异常处理。

  1.如果错误发生的条件是可以预知的我们用if进行处理,在错误发生之前进行处理

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

  2.如果错误发生的条件是不可以预知的,则需要用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()
View Code

  try....except...详细用法:

#1 异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
s1 = 'hello'
try:
    int(s1)
except IndexError as e: # 未捕获到异常,程序直接报错
    print e

#2 多分支
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)

#4 多分支异常与万能异常
#4.1 如果你想要的效果是,无论出现什么异常,我们统一丢弃,或者使用同一段代码逻辑去处理他们,那么骚年,大胆的去做吧,只有一个Exception就足够了。
#4.2 如果你想要的效果是,对于不同的异常我们需要定制不同的处理逻辑,那就需要用到多分支了。

#5 也可以在多分支后来一个Exception
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)

#6 异常的其他机构
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('无论异常与否,都会执行该模块,通常是进行清理工作')

#7 主动触发异常
try:
    raise TypeError('类型错误')
except Exception as e:
    print(e)

#8 自定义异常
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)

#9 断言:assert 条件
assert 1 == 1  
assert 1 == 2

#10 总结try..except

1:把错误处理和真正的工作分开来
2:代码更易组织,更清晰,复杂的工作任务更容易实现;
3:毫无疑问,更安全了,不至于由于一些小的疏忽而使程序意外崩溃了;
View Code
原文地址:https://www.cnblogs.com/Holmes-98/p/14433464.html