除了之前写的那些关于Python面向对象编程的基础知识外,还需要掌握几个在Python中与面向对象相关的内置函数及非常重要的只是反射。
在Python中与面向对象相关的常用内置函数有9个,见下图。
0x00 定义特殊方法的装饰器
property
@property是一个将用户计算的东西伪装成为一个属性。
from math import pi
class Circle():
def __init__(self,r):
self.r = r
@property
def area(self):
return pi*self.r**2
c = Circle(2)
print(c.area) #12.566370614359172
在外部看来,area就成为了对象c的一个属性了!!其实不是,只是“伪装”成了属性了,毕竟在起名方法的时候area的名字就听着就像个属性名称,而不是get_area()。这样在外部就可以和调用动态属性一样的就取到结果了!
为什么不直接在定义动态属性的时候直接定义一个self.area = pi*self.r**2呢?这样的缺点是,当你一单生成一个对象,不管你是否能用到这个对象的area属性,他都会在内存中生成一个area属性,因而浪费了系统资源,所以才有property这个装饰器函数!
当我们尝试去给c.area去赋值的时候,Python就会报错了!!!这里你就需要用到@xxx.setter了。
from math import pi
class Circle():
def __init__(self,r):
self.r = r
@property
def area(self):
return pi * self.r**2
@area.setter
def area(self,new_r):
self.r = new_r
c = Circle(2)
print(c.area) #12.566370614359172
c.area = 3
print(c.area) #28.274333882308138
若没有@xxx.setter方法当执行c.area = 3的时候,那么程序将报错!这里加入了装饰器,则可以给c.area赋值。这里注意的是,赋值是给area()中需要的变量赋值,这里只能有一个参数,如果定义self.area = new_r的话,那么会存在无限递归,这将会报错。
那么如何删除c.area的属性呢?也是通过@xxx.deleter来删除,几乎用不到~来段代码。
from math import pi
class Circle():
def __init__(self,r):
self.__r = r
self.r = r
@property
def area(self):
return pi * self.__r**2
@area.setter
def area(self,new_r):
self.__r = new_r
@area.deleter
def area(self):
del self.__r
c = Circle(2)
print(c.area) #12.566370614359172
c.area = 3
print(c.area) #28.274333882308138
del c.area
print(c.area) #AttributeError
这里要删除area属性的话,必须在外部调用del c.area之后,等于调用了被@area.deleter装饰的函数,这里一定要记住内部还要写del呢!!值得注意的一点,如果这个属性是property的话,如果需要删除(@xxx.deleter)的话,那么在property中需要使用的参数进行封装即为上面的self.__r。
classmethod
类方法是直接由类自己调用,方法可以传入多个参数,但是第一个必须是cls,即类本身。Python中用类方法比较少,不过一般在操作静态属性的时候,最好使用类方法。
class Goods():
__discount = 1
def __init__(self,name,price):
self.name = name
self.__price = price
def get_price(self):
return self.__price * Goods.__discount
@classmethod
def change_discount(cls,new_discount):
Goods.__discount = new_discount
apple = Goods('apple',100)
print(apple.get_price()) #100
Goods.change_discount(0.8)
print(apple.get_price()) #80.0
上面的例子中,假设商店打折都是所有商品一起打折,这种时候你单独用某个对象来仅仅操作折扣那么就不合适了,所以采用类方法。
staticmethod
怎么说呢?感觉classmethod和staticmethod很相似啊!不过其中最大的区别就是classmethod最少有一个参数cls,而staticmethod可以没有参数,一般情况下,该方法中参数和该类(对象)的属性没有什么关系(甚至没有参数的情况下),可以选用staticmethod。
class Login():
def __init__(self,username,passwd):
self.username = username
self.__passwd = passwd
def login(self):pass
@staticmethod
def inputInfo():
username = input('>>>')
passwd = input('>>>')
return Login(username,passwd)
a = Login.inputInfo() #xzy(username) moe(passwd)
print(a.username) #xzy
可以看出这个Login类,获取用户输入密码这个函数的参数与类(对象)本身的属性没有什么关系,不过需要创建一个对象的时候需要用到它,在使用它的时候也不需要什么参数,那么这种情况下可以选用@staticmethod。
0x01 判断对象与类之间关系的函数
有两个函数,都非常的简单。一个时候isinstance(obj,class)另一个是issubclass(subclass,base)。
class A():pass
class B(A):pass
a = A()
b = B()
print(isinstance(a,A)) #True
print(issubclass(B,A)) #True
第一个是判断这个对象是不是这个类的实例化,第二个方法是判断B类是不是A类的子类。
0x02 反射
反射是Python中很重要的内容,能用到的地方也是非常的多。反射就是通过利用字符串类型的名字,去操作一个变量。其实仔细想一下貌似和eval的功能有点类似,不过呢eval存在在安全隐患,一般都不推荐使用。
反射相关的方法主要有4个:
getattr
hasattr
setattr
delattr
class Person():
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def func1(self):
print('1' + self.name)
def func2(self):
print('2' + self.age)
p = Person('xzymoe',20,'man')
#用户输入属性名称就查询该属性
inp = input('>>>') #name
print(getattr(p,inp)) #xzymoe
#当用户输入方法名称就调用相应方法
inp = input('>>>') #name
#这里返回一个绑定方法,为一个内存地址
ret = getattr(p,inp) #>
ret() #1xzymoe
这里可以看到当用户输入一个字符串数据的类型的时候,可以不用去判断输入的内容是什么,然后在选择返回什么属性或者方法。而反射自动可以帮你完成这项工作。不过上面的代码不是很完美,因为当用户随意输入一个不存在的变量怎么办呢?这个时候就可以和hasattr搭配使用了。
#用户输入属性名称就查询该属性
inp = input('>>>')
if hasattr(p,inp):
print(getattr(p,inp))
else:
print('没有这个属性或者方法')
hasattr就可以判断传入的字符串是否在这个对象中有属性或者方法,有着返回True,没有就返回False。
相比hasattr和getattr,setattr和delattr用的就不是那么多。
setattr(p,'weight',100)
print(p.__dict__) #{'age': 20, 'weight': 100, 'name': 'xzymoe', 'sex': 'man'}
delattr(p,'weight')
print(p.__dict__) #{'age': 20, 'name': 'xzymoe', 'sex': 'man'}
从上面的代码可以看出,使用setattr可以给对象添加、更改属性;而使用delattr则可以给对象删除属性。
反射不仅仅可以使用在对象里,还可以使用在模块里。
这里有一个t5模块,将被t4调用。
t5代码如下:
class Foo():
def func(self):
print('this is Foo Moudle')
t4代码如下:
import t5
f = t5.Foo()
ret = getattr(f,'func')
ret() #this is Foo Moudle
这样就可以方便的反射到了t5的模块里。