除了之前写的那些关于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) #<bound method Person.func1 of <__main__.Person object at 0x0000022EFC543DA0>> 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的模块里。