Python学习记录day8-面向对象进阶、反射和异常处理

时间:2022-05-12 00:08:53

Python学习记录day8-面向对象进阶、反射和异常处理

@(学习)[python]

1. 静态方法

@staticmethod装饰器即可把其装饰的方法变为一个静态方法。静态方法,只是名义上归类管理,实际上在静态方法里访问不了类或实例中的任何属性。

#!/usr/bin/env python
# _*_coding:utf-8_*_
'''
* Created on 2017/2/27 20:18.
* @author: Chinge_Yang.
'''



class Cat(object):
def __init__(self, name):
self.name = name

@staticmethod # 把eat方法变为静态方法
def eat(self):
print("%s is eating" % self.name)


d = Cat("bana")
d.eat()

上面的调用会出以下错误,说是eat需要一个self参数,但调用时却没有传递,没错,当eat变成静态方法后,再通过实例调用时就不会自动把实例本身当作一个参数传给self了。

staticmethod.py", line 19, in <module>
d.eat()
TypeError: eat() missing 1 required positional argument: 'self'

想让上面的代码可以正常工作有两种办法

  1. 调用时主动传递实例本身给eat方法,即d.eat(d)
  2. 在eat方法中去掉self参数,但这也意味着,在eat中不能通过self.调用实例中的其它变量了
#!/usr/bin/env python
# _*_coding:utf-8_*_
'''
* Created on 2017/2/27 20:18.
* @author: Chinge_Yang.
'''



class Cat(object):
def __init__(self, name):
self.name = name

@staticmethod # 把eat方法变为静态方法
def eat(): # 此处去掉self
print("is eating")


d = Cat("bana")
d.eat()

2. 类方法

类方法通过@classmethod装饰器实现,其只能访问类变量,不能访问实例变量。

#!/usr/bin/env python
# _*_coding:utf-8_*_
'''
* Created on 2017/2/27 20:18.
* @author: Chinge_Yang.
'''



class Cat(object):
name = "类变量" # 需要在类中定义变量
def __init__(self, name):
self.name = name

@classmethod # 把eat方法变为类方法
def eat(self): # 此处有self
print("%s is eating" % self.name)


d = Cat("bana")
d.eat()

结果:实例传入的参数不能被使用,使用的是类内部变量。因为没有在类中定义name的话,会有错误提示。

类变量 is eating

3. 属性方法

属性方法通过@property把一个方法变成一个静态属性。

#!/usr/bin/env python
# _*_coding:utf-8_*_
'''
* Created on 2017/2/27 20:18.
* @author: Chinge_Yang.
'''



class Cat(object):
def __init__(self, name):
self.name = name

@property # 把eat方法变为属性方法
def eat(self):
print("%s is eating" % self.name)


d = Cat("bana")
d.eat # 此处不加括号

把一个方法变成静态属性有什么应用场景呢?
比如 ,你想知道一个航班当前的状态,是到达了、延迟了、取消了、还是已经飞走了, 想知道这种状态你必须经历以下几步:

  1. 连接航空公司API查询
  2. 对查询结果进行解析
  3. 返回结果给你的用户

因此这个status属性的值是一系列动作后才得到的结果,所以你每次调用时,其实它都要经过一系列的动作才返回你结果,但这些动作过程不需要用户关心, 用户只需要调用这个属性就可以。

class Flight(object):
def __init__(self,name):
self.flight_name = name


def checking_status(self):
print("checking flight %s status " % self.flight_name)
return 1

@property
def flight_status(self):
status = self.checking_status()
if status == 0 :
print("flight got canceled...")
elif status == 1 :
print("flight is arrived...")
elif status == 2:
print("flight has departured already...")
else:
print("cannot confirm the flight status...,please check later")


f = Flight("CA980")
f.flight_status

既然这个flight_status已经是个属性了, 那给它赋值又如何使用呢?
需要通过@proerty.setter装饰器再装饰一下,此时 你需要写一个新方法, 对这个flight_status进行更改。

class Flight(object):
def __init__(self,name):
self.flight_name = name


def checking_status(self):
print("checking flight %s status " % self.flight_name)
return 1


@property
def flight_status(self):
status = self.checking_status()
if status == 0 :
print("flight got canceled...")
elif status == 1 :
print("flight is arrived...")
elif status == 2:
print("flight has departured already...")
else:
print("cannot confirm the flight status...,please check later")

@flight_status.setter #修改
def flight_status(self,status):
status_dic = {
0 : "canceled",
1 :"arrived",
2 : "departured"
}
print("\033[31;1mHas changed the flight status to \033[0m",status_dic.get(status) )

@flight_status.deleter #删除
def flight_status(self):
print("status got removed...")

f = Flight("CA980")
f.flight_status
f.flight_status = 2 #触发@flight_status.setter
del f.flight_status #触发@flight_status.deleter

注意以上代码里还写了一个@flight_status.deleter, 是允许可以将这个属性删除。

4. 类的特殊成员方法

4.1 __doc__表示类的描述信息

class Foo:
""" 描述类信息 """

def func(self):
pass

print(Foo.__doc__)

结果:

描述类信息

4.2 __module____class__

__module__表示当前操作的对象在那个模块

__class__表示当前操作的对象的类是什么

cat lib/c.py

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'  

cat index.py

from lib.c import Foo

obj = Foo()
print(obj.__module__) # 输出 lib.c,即:输出模块
print(obj.__class__) # 输出 <class 'lib.c.Foo'>,即:输出类

4.3 __init__ 构造方法

__init__ 构造方法通过类创建对象时,自动触发执行。

4.4 __del__ 析构方法

析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的

4.5 __call__ call方法

对象后面加括号,触发执行。

class Foo(object):

def __init__(self):
pass

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


obj = Foo() # 执行 __init__
obj() # 执行 __call__
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

4.6 __dict__

__dict__ 查看类或对象中的所有成员   

class Province(object):

country = 'China'

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

def func(self, *args, **kwargs):
print('func')

# 获取类的成员,即:静态字段、方法、
print(Province.__dict__)
# 输出:{'__init__': <function Province.__init__ at 0x106a210d0>, 'country': 'China', 'func': <function Province.func at 0x106a21488>, '__doc__': None, '__dict__': <attribute '__dict__' of 'Province' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Province' objects>}


obj1 = Province('HeBei',10000)
print (obj1.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 10000, 'name': 'HeBei'}

obj2 = Province('HeNan', 3888)
print(obj2.__dict__)
# 获取 对象obj1 的成员
# 输出:{'count': 3888, 'name': 'HeNan'}

4.7 __str__ 方法

如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。

class Foo(object):

def __str__(self):
return 'ygqygq2'


obj = Foo()
print(obj)
# 输出:ygqygq2

4.8 __getitem____setitem____delitem__

用于索引操作,如字典。以上分别表示获取、设置、删除数据。

class Foo(object):
def __getitem__(self, key):
print('__getitem__', key)

def __setitem__(self, key, value):
print('__setitem__', key, value)

def __delitem__(self, key):
print('__delitem__', key)


obj = Foo()

result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'ygqygq2' # 自动触发执行 __setitem__
del obj['k1']

结果:

__getitem__ k1
__setitem__ k2 ygqygq2
__delitem__ k1

4.9 __new__ \ __metaclass__

class Foo(object):


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

f = Foo("ygqygq2")
print(type(Foo))
print(type(f))

结果:

<class 'type'>
<class '__main__.Foo'>

上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。

如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类的 构造方法 创建。

print type(f) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建
print type(Foo) # 输出:<class 'type'> 表示,Foo类对象由 type 类创建

所以,f对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象是通过type类的构造方法创建。
那么,创建类就可以有两种方式:
1). 普通方式

class Foo(object):

def func(self):
print('hello')

2). 特殊方式

def func(self):
print('hello')

Foo = type('Foo',(object,), {'func': func})

#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员

加上构造方法,所以 ,类是由 type 类实例化产生。

那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由谁来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看类 创建的过程。
Python学习记录day8-面向对象进阶、反射和异常处理

类的生成 调用 顺序依次是 __new__ –> __call__ –> __init__

5. 反射

python中的反射功能是由以下四个内置函数提供:hasattr、getattr、setattr、delattr,改四个函数分别用于对对象内部执行:检查是否含有某成员、获取成员、设置成员、删除成员。

hasattr(object, name)
判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。
需要注意的是name要用括号括起来。

getattr(object, name[,default])
获取对象object的属性或者方法,如果存在打印出来,如果不存在,打印出默认值,默认值可选。
需要注意的是,如果是返回的对象的方法,返回的是方法的内存地址,如果需要运行这个方法,
可以在后面添加一对括号。

setattr(object, name, values)
给对象的属性赋值,若属性不存在,先创建再赋值。

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'

def func(self):
return 'func'

obj = Foo()

# #### 检查是否含有成员 ####
print(hasattr(obj, 'name'))
print(hasattr(obj, 'func'))

# #### 获取成员 ####
print(getattr(obj, 'name'))
print(getattr(obj, 'func'))

# #### 设置成员 ####
setattr(obj, 'age', 18)
setattr(obj, 'show', lambda num: num + 1)

# #### 删除成员 ####
delattr(obj, 'name')
# delattr(obj, 'func') # 不允许删除

结果:

True
True
ygqygq2
<bound method Foo.func of <__main__.Foo object at 0x10bdc91d0>>

详细解析:
当我们要访问一个对象的成员时,应该是这样操作:

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'

def func(self):
return 'func'

obj = Foo()

# 访问字段
obj.name
# 执行方法
obj.func()

那么问题来了?
a. 上述访问对象成员的 name 和 func 是什么?
答:name是类的变量名,func是类的方法名。
b. obj.xxx ,obj.xxx()是什么意思?
答:
obj.xxx 表示去obj中或类中寻找变量名 xxx,并获取对应内存地址中的内容。
obj.xxx()是执行类中名为xxx的方法,并获取对应结果。

c. 需求:请使用其他方式获取obj对象中的name变量指向内存中的值 “ygqygq2”

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'

# 不允许使用 obj.name
obj = Foo()

答:有两种方式,如下:
法一:

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'

def func(self):
return 'func'

# 不允许使用 obj.name
obj = Foo()

print(obj.__dict__['name'])

法二:

class Foo(object):

def __init__(self):
self.name = 'ygqygq2'

def func(self):
return 'func'

# 不允许使用 obj.name
obj = Foo()

print(getattr(obj, 'name'))

d. 比较三种访问方式

obj.name
obj.__dict__['name']
getattr(obj, 'name')

答:第一种效率高,直接找对应的内存获取相应数据;第二种先获取全部属性和方法,然后再获取列表的值;第三种,先要判断是否存在,然后再获取。

结论:反射是通过字符串的形式操作对象相关的成员。一切事物都是对象!!!

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

import sys

def s1():
print('s1')

def s2():
print('s2')


this_module = sys.modules[__name__]

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

类也是对象

class Foo(object):

staticField = "ygqygq2"

def __init__(self):
self.name = 'wupeiqi'

def func(self):
return 'func'

@staticmethod
def bar():
return 'bar'

print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))

模块也是对象

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

"""
程序目录:
home.py
index.py

当前文件:
home.py
"""


def dev():
return 'dev'
#!/usr/bin/env python
# -*- coding:utf-8 -*-

"""
程序目录:
home.py
index.py

当前文件:
index.py
"""



import home as obj

#obj.dev()

func = getattr(obj, 'dev')
func()

动态导入模块

import importlib

__import__('import_lib.metaclass') #这是解释器自己内部用的
#importlib.import_module('import_lib.metaclass') #与上面这句效果一样,官方建议用这个

6. 异常处理

6.1 异常基础

在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户,而是现实一个提示的页面,通俗来说就是不让用户看见大黄页!!!

try:
pass
except Exception as e:
pass

需求:将用户输入的两个数字相加

while True:
num1 = input('num1:')
num2 = input('num2:')
try:
num1 = int(num1)
num2 = int(num2)
result = num1 + num2
except Exception as e:
print('出现异常,信息如下:')
print(e)

6.2 异常种类

python中的异常种类非常多,每个异常专门用于处理某一项异常!!!
以下是常见错误代码:

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

实例:

dic = ["ygqygq2", 'python']
try:
dic[10]
except IndexError as e:
print(e)

dic = {'k1':'v1'}
try:
dic['k20']
except KeyError as e:
print(e)

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

结果:

list index out of range
'k20'
invalid literal for int() with base 10: 'hello'

对于上述实例,异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。

# 未捕获到异常,程序直接报错

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

所以,写程序时需要考虑到try代码块中可能出现的任意异常,可以这样写:

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

万能异常
在python的异常中,有一个万能异常:Exception,他可以捕获任意异常,即:

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

接下来你可能要问了,既然有这个万能异常,其他异常是不是就可以忽略了!
答:当然不是,对于特殊处理或提醒的异常需要先定义,最后定义Exception来确保程序正常运行。

s1 = 'hello'
try:
int(s1)
except KeyError as e:
print('键错误')
except IndexError as e:
print('索引错误')
except Exception as e:
print('错误')

6.3 异常其他结构

try:
# 主代码块
pass
except KeyError as e:
# 异常时,执行该块
pass
else:
# 主代码块执行完,执行该块
pass
finally:
# 无论异常与否,最终执行该块
pass

6.4 主动触发异常

try:
raise Exception('错误了。。。')
except Exception as e:
print(e)

6.5 自定义异常

class MyException(Exception):

def __init__(self, msg):
self.message = msg

def __str__(self):
return self.message

try:
raise MyException('我的异常')
except MyException as e:
print(e)

6.6 断言assert

使用assert断言是学习python一个非常好的习惯,python assert 断言句语格式及用法很简单。在没完善一个程序之前,我们不知道程序在哪里会出错,与其让它在运行最崩溃,不如在出现错误条件时就崩溃,这时候就需要assert断言的帮助。

python assert断言的作用
python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达式为假。可以理解assert断言语句为raise-if-not,用来测试表示式,其返回值为假,就会触发异常。

assert断言语句的语法格式
expression assert 表达式
下面做一些assert用法的语句供参考:

assert 1==1
assert 2+2==2*2
assert len(['my boy',12])<10
assert range(4)==[0,1,2,3]

如何为assert断言语句添加异常参数
assert的异常参数,其实就是在断言表达式后添加字符串信息,用来解释断言并更好的知道是哪里出了问题。格式如下:
assert expression [, arguments]
assert 表达式 [, 参数]