Python面向对象相关函数内置方法与反射

除了之前写的那些关于Python面向对象编程的基础知识外,还需要掌握几个在Python中与面向对象相关的内置函数及非常重要的只是反射。

python

在Python中与面向对象相关的常用内置函数有9个,见下图。

python-inner-func-oop0x00 定义特殊方法的装饰器

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的模块里。

发表评论