• python 面向对象进阶


    CONTENTS

    • 面向对象进阶语法内容:
      •  经典类与新式类
      •  静态方法:@staticmethod
      •  类方法: @classmethod
      •  属性方法:@property
      • 类的特殊成员方法
      • 反射
    • 异常处理
    • 动态模块导入

    面向对象进阶语法

    经典类与新式类的爱恨情仇

    经典类与新式类形式上的区别:

    1 # 经典类
    2 class Dog:
    3     pass
    4 
    5 # 新式类
    6 class Cat(object):
    7     pass

    从形式上看,新式类与经典类只是在创建时多了一个object的声明。但实际上,经典类与新式类有很多方面的不同:

    1.  类的继承策略(体现在多继承)

    • 横向继承(广度优先)
    • 纵向继承(深度优先) 

    假设有A, B, C, D四个类,B, C继承A, D继承B和C,如下图所示。横向继承,也就是广度优先的继承顺序D-B-C-A;纵向继承,也就是深度优先的继承顺序D-B-A。

    在python2.x中,经典类利用的是深度优先的继承策略;新式类利用的是广度优先的继承策略。

    而在python3.x中,经典类和新式类均利用的是广度优先的继承策略。

    在python3.6中执行下述代码,可以看出多继承关于构造函数,方法的继承顺序,也可以调换B,C的继承顺序。简而言之,在多继承的过程中,只是按继承顺序继承第一个有相应的构造函数或者方法,搜索的顺序遵循广度优先的继承策略。

     1 class A(object):
     2     def __init__(self):
     3         self.n = "a"
     4         print("A class")
     5     def fun(self):
     6         print("in the fun A")
     7 
     8 class B(A):
     9     # def __init__(self):
    10     #     self.n = "b"
    11     # def fun(self):
    12     #     print("in the fun B")
    13     pass
    14 
    15 class C(A):
    16     # def __init__(self):
    17     #     self.n = "c"
    18     # def fun(self):
    19     #     print("in the fun C")
    20     pass
    21 class D(B,C):
    22     pass
    23 d = D()
    24 print(d.n)
    25 d.fun()

     2.  新式类增添了一些新的类的特殊成员方法(详见类的特殊成员方法)。

    静态方法

     通过@staticmethod 装饰器可以把类中的一个方法变为静态方法。静态方法和原来的方法有什么不同呢?普通的方法通过类的实例化便可以直接访问,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量,也就是说,静态方法实际上已经和类没有什么关系,只是名义上还需要用类的实例来调用。对于下述代码,eat方法中的self并不能直接自动传入d的实例化,若还想实现self.name传参,需要手动将实例化d传入,即d.eat(d)

     1 class Dog(object):
     2     # 类方法使用
     3     # name = "huazai"
     4     def __init__(self, name):
     5         self.name = name
     6         self.__food = None
     7     # 此时可以调用,因为是类的方法,可以通过实例调用
     8     # def eat(self, food):
     9     #     print("%s is eating %s" %(self.name, food))
    10 
    11     # 加静态方法后
    12     @staticmethod    # 静态方法
    13     def eat(self):
    14         print("%s is eating %s"% (self.name,'dd'))
    15 d = Dog("erha")
    16 d.eat(d)

    类方法

     通过@classmethod 装饰器可以把类中的一个方法变为类方法。类方法的不同在于,其只能访问类变量,不能访问实例变量

     执行下述两段代码,第一段代码中直接调用加了类方法的eat,会出现AttributeError,显示Dog中没有name这个属性,也就是说,此时的类方法self已经不能再访问实例变量self.name

     1 class Dog(object):
     2     # 类方法使用
     3     # name = "labuladuo"
     4     def __init__(self, name):
     5         self.name = name
     6         self.__food = None
     7     # 加类方法后
     8     @classmethod
     9     def eat(self):
    10         print("%s is eating %s"%(self.name, 'dd'))
    11 
    12 d = Dog("erha")
    13 d.eat()

    第二段代码:在第三行声明类变量name后,便可以执行。

     1 class Dog(object):
     2     # 类方法使用
     3     name = "labuladuo"    #类方法只能调用类变量,就是此处的name
     4     def __init__(self, name):
     5         self.name = name
     6         self.__food = None
     7     # 加类方法后
     8     @classmethod
     9     def eat(self):
    10         print("%s is eating %s"%(self.name, 'dd'))
    11 
    12 d = Dog("erha")
    13 d.eat()

     属性方法

    通过@property 装饰器可以把类中的一个方法变为属性方法,也就是把方法变成属性。变成属性后,在调用时不用加括号。  下面是一个简单的例子,注意在调用属性方法eat时,如第18行所示。              

     1 class Dog(object):
     2     # 类方法使用
     3     name = "labuladuo"
     4     def __init__(self, name):
     5         self.name = name
     6         self.__food = None
     7 
     8     # 加属性方法1
     9     @property
    10     def eat(self):
    11         print("%s is eating %s"%(self.name, 'dd'))
    12 
    13 d = Dog("erha")
    14 # 属性方法
    15 # 按第一行代码执行,会出现 nonetype object is not callable的错误
    16 # 按第二行代码运行就可以了, 说明该装饰器property是将方法变成一个静态属性
    17 # d.eat()
    18 d.eat

    但是,如果按照调用属性的方式调用一个方法,会存在一个问题:即没有办法给这个属性方法传入或删除参数

    解决的办法是:利用两个装饰器@eat.setter,@eat.deleter对同名参数进行装饰,如下述代码:

     1 class Dog(object):
     2     # 类方法使用
     3     name = "labuladuo"
     4     def __init__(self, name):
     5         self.name = name
     6         self.__food = None    # 私有属性 __表示私有
     7     # 加属性方法2   想传参数
     8     @property
     9     def eat(self):
    10         print("%s is eating %s"%(self.name,self.__food))
    11     @eat.setter
    12     def eat(self,food):
    13         self.__food = food    # 通过属性来赋值,完善功能
    14         print("set to food" ,food)
    15 
    16     @eat.deleter
    17     def eat(self):
    18         del self.__food
    19         print("删除完成")
    20 
    21 d = Dog("erha")
    22 d.eat = "baozi"  # 在有@eat.setter装饰器修饰时,此时由于已经变为属性,可以直接赋值 否则会出 can't set attribute 的错误
    23 d.eat
    24
    25 del d.eat # 在没有加@eat.deleter时,是不能直接删的,虽然它是属性 会报can't delete attribute 的错误

     类的特殊成员方法

     1. __doc__  输出关于类的描述信息

    1 class Dog(object):
    2     '''关于狗的一个类'''
    3     def __init__(self, name, age):
    4         self.name = name
    5         self.age = age
    6     def bark(self):
    7         print("一只名叫%s的狗正在barking!"%self.name)
    8 print(Dog.__doc__)

    在写类时,一般会在类下写上关于这个类的描述信息,增加代码的可读性。执行上述代码,会打印出“关于狗的一个类”的字样,即类的描述信息

    2. __module__ 和 __class__

    其中,__module__表示 当前操作对象在哪一个模块中

         __class__表示 当前操作对象的类名

    1 class C:
    2     def __init__(self):
    3         self.name = 'Iris'
    1 from lib.aa import C
    2 obj = C()
    3 print(obj.__module__)  # 输出 lib.aa,即:输出模块  C是从哪个模块导出的
    4 print(obj.__class__)

    3. __init__ 构造方法, 通过类创建对象时触发

    4. __del__ 析构方法, 对象在内存中被释放时触发, 此方法一般无需定义

    5. __call__ 类内声明,对象加括号触发执行,即 对象名() 或 类名()() 来触发执行 

     1 class Dog(object):
     2     def __init__(self, name, age):
     3         self.name = name
     4         self.age = age
     5     def bark(self):
     6         print("a dog whose name is %s is barking!"%self.name)
     7     def __call__(self, *args, **kwargs):
     8         print("in the call func",*args, **kwargs)
     9     def __str__(self):
    10         return "<obj:%s, attribute:%s>" %(self.name, self.age)
    11 d = Dog("pick","10")
    12 d("1234")# 调用__call__
    13 
    14 print(Dog.__dict__) #返回类里的所有属性,不包含实例
    15 print(d.__dict__)   # 只有实例属性
    16 
    17 print(d) # __str__ 的效果,在打印对象时,会返回return的结果

    6. __dict__ 查看类或对象中的所有成员(实例代码见5)

    • 类.__dict__  返回类里的所有属性,不包含实例
    • 对象.__dict__ 只返回实例属性,也就是在__init__ 中声明的变量

    7. __str__ 类内声明,打印对象时,返回的不再是内存地址,而是输出该方法的返回值(实例代码见5)

       执行代码,print(d)的结果为__str__函数中return的结果,如下图所示。

    8. __getitem__、 __setitem__和 __delitem__

    用于索引操作,如字典。换句话说,就是写一个类,类内声明可以实现让用户通过字典的形式调用这个类,包含获取,设置和删除的功能。

     1 # 写一个类,然后让用户通过字典的形式调用(获取,设置查询)
     2 class Foo(object):
     3     def __init__(self):
     4         self.data = {}
     5     def __getitem__(self, key):
     6         print('__getitem__', key)
     7         return self.data.get(key)
     8     def __setitem__(self, key, value):
     9         print('__setitem__', key, value)
    10         self.data[key] = value
    11     def __delitem__(self, key):
    12         print('__delitem__', key)
    13 
    14 obj = Foo()
    15 
    16 result = obj['k1']  # 自动触发执行 __getitem__
    17 obj['k2'] = 'Iris'  # 自动触发执行 __setitem__
    18 print(obj["k2"])   # 以字典的形式查询
    19 del obj['k1']   # 自动触发执行__delitem__

    9. __new__ __metaclass__

    在8中的代码,在进行如下操作:

    1 print(type(obj))
    2 print(type(Foo))

    返回的结果:对象obj的类型是Foo类,而类Foo的类型是type,类还有类 !。

    实际上,对象obj是通过类Foo创建的,但在Python中一切皆对象,Foo类本身也是对象,类的起源是 type,也就是类的类,type的实例化。

    那么,类就有两种创建方式,还有一种通过type创建的方式:

    第一种:普通方式(常用)

    1 class Foo(object):
    2   
    3     def func(self):
    4         print 'hello Iris'

    第二种:特殊方式(不常用)

    1 def func(self):
    2     print ”hello Iris“
    3   
    4 Foo = type('Foo',(object,), {'func': func})
    5 #type第一个参数:类名
    6 #type第二个参数:当前类的基类
    7 #type第三个参数:类的成员

    说好的介绍__new__和__metaclass__ ,怎么又扯到类的创建了?__new__是用来创建实例的,并且先于__init__,看下面这段代码。

     1 class Foo(object):
     2     def __init__(self, name, age):
     3         self.name = name
     4         self.age = age
     5         print("__init__执行")
     6     def __new__(cls, *args, **kwargs):
     7         print("__new__执行")
     8         return object.__new__(cls)   # 如果把这句注释掉,__init__便不会执行, 实例化也就不成功
     9 obj = Foo("Iris","11")
    10 obj.age

    metaclass,直译为元类,简单的解释就是:

    当定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

    但是如果想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

    连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

    所以,metaclass允许你创建类或者修改类。换句话说,可以把类看成是metaclass创建出来的“实例”。” 基本上不会有用到的情况。

    反射

    通过字符串映射或修改程序运行时的状态、属性、方法。

    反射四大方法:

    • hasattr     判断对象里是否有对应的方法
    • getattr   根据字符串获取对象里对应方法的地址
    • setattr      如果没有该字符串属性或方法,则添加
    • delattr      删除某个属性或方法
     1 class Foo(object):
     2  
     3     def __init__(self, name):
     4         self.name = 'Iris'
     5  
     6     def func(self):
     7         print(" hello ")
     8  obj = Foo()
     9  
    10 # #### 检查是否含有成员 ####
    11 hasattr(obj, 'name')
    12 hasattr(obj, 'func')
    13  
    14 # #### 获取成员 ####
    15 getattr(obj, 'name')
    16 getattr(obj, 'func')
    17  
    18 # #### 设置成员 ####
    19 setattr(obj, 'age', 18)
    20 setattr(obj, 'show', lambda num: num + 1)
    21  
    22 # #### 删除成员 ####
    23 delattr(obj, 'name')
    24 delattr(obj, 'func')
    View Code

    异常处理

     在编程过程中,为了增加项目的友好型,程序中出现的bug信息一般不会直接显示给用户。这些bug需要通过代码进行捕捉,也就是异常处理。

     1 data = {"name": "alex", "age": 28}
     2 try:
     3     data["name1"]
     4 except KeyError as e:
     5     print(e)
     6 except Exception:
     7     print("在所有错误里")
     8 else:
     9     print("一切正常")
    10 finally:
    11     print("你管我,我就执行")

    第4行代码会捕捉一些已知可能会发生的错误,e中包含着错误信息;第6行代码表示抓取所有错误(但是有些错误是捕捉不到的);第10行代码finally不管有没有捕捉到异常都会执行。  

     有时也会使用断言assert指令,用于一些预先判断的场合。如果断言正确,程序正常运行;如果断言错误,程序报错。

    如果a的值异常会对后面程序的运行产生很大的影响,即后面的代码不能出错,则可以运行前对a的值进行一次断言,当报错时会报AssertionError的错误,可以用异常处理去抓取。

    1 ...
    2 assert a==1
    3 ...

    动态模块导入

     importlib模块

     该模块可以导入以字符串的形式导入模块。主要用于反射或延迟加载模块。

    1 import importlib
    2 #aa = __import__("lib.aa")  #与下述方法效果相同
    3 aa = importlib.import_module("lib.aa")
    4 obj = aa.C()
    5 print(obj.name)

                                                                                                                                                                                                                                                                                               

  • 相关阅读:
    DirectX SDK版本与Visual Studio版本
    String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别
    LocalDateTime与字符串互转/Date互转/LocalDate互转/指定日期/时间比较
    MySQL触发器的正确使用与案例分析
    一篇很棒的 MySQL 触发器学习教程
    Java消息队列三道面试题详解!
    到底什么时候该使用MQ?
    mq使用场景、不丢不重、时序性
    Java 8时间和日期API 20例
    eclipse插件之Findbugs、Checkstyle、PMD安装及使用
  • 原文地址:https://www.cnblogs.com/monologuesmw/p/9521217.html
Copyright © 2020-2023  润新知