面向对象是python中非常重要的特性,使用对象,我们可以减少重复代码的编写,同时使得代码的逻辑结构更加清晰。但本文不是一篇《python面向对象使用说明书》,而是是带你了解面向对象的思想和python中面向对象过程中的黑科技。
一切皆对象
在python中一切皆对象。是的,虽然你可能在写代码的时候不会使用对象,但其实是你无时无刻都在使用对象。翻开CPython的源码,你会发现在python中随处可见的1
、"apple"
、[1, 2, 3]
、{"apple": 1}
这些全部都可以表示成一个PyObject
结构,这也是“一切皆对象”这句话最直接的证据。
C语言可以有对象吗?
学过C语言的同学都知道C语言不支持面向对象,与之相对的C++是有面向对象的。其实不然,面向对象是一种思想,如果有一门语言自称支持面向对象,其实他只是恰好在语言层面实现了面向对象思想。所以我可以告诉你C语言是支持面向对象的,只是写法与其他的直接在语言层面实现面向对象的写法有所区别。下面看两段代码,分别是python语言和C语言对同一个对象Person
的实现。
typedef struct{ int age; char* name; char* email; }Person; /** * @brief 初始化Person * * @param age * @param name * @param email * @return Person* */ Person* initPerson(int age, char* name, char* email){ Person* p; if((p=malloc(sizeof(Person)))==NULL){ return NULL; } p->age = age; p->name = name; p->email = email; return p; } /** * @brief 修改年龄 * * @param p * @param age */ void changeAge(Person* p, int age){ p->age = age; } /** * @brief 修改姓名 * * @param p * @param name */ void changeName(Person* p, char* name){ p->name = name; } /** * @brief 修改邮箱 * * @param p * @param email */ void changeEmail(Person* p, char* email){ p->email = email; }
class Person: def __init__(self, age, name, email): """ 初始化 """ self.age = age self.name = name self.email = email def changeAge(self, age): """ 修改年龄 """ self.age = age def changeName(self, name): """ 修改姓名 """ self.name = name def changeEmail(self, email): """ 修改邮箱 """ self.email = email
从上面的代码中,虽然二者是两种不同的语言,但是仔细对比,会发现在逻辑上存在相似。首先很直观的是,python代码量比C语言会少了很多,但是代码量少,并不意味着Python的运行速度会快过C(实际上是,虽然我们一般使用的Python是使用C语言来实现的,但是二者的运行速度相差很多。后面有时间可以写一篇,关于为什么Python运行速度比C慢那么多)。函数(方法)参数
- C中需要传入待修改的Person对象和新的值
- Python中需要传入self和新的值
在python中self通常指代当前方法所属于的实例对象,也就是说,相当于C中待修改的Person对象。到这里你会发现两种语言在面向对象上的实现思路是一致的。只是python在语言层面帮我们省了很多代码而已。从数据角度来看一下面向对象,每一个对象其实可以看做一个存放数据的螃蟹,里面会有若干个属性,而方法则是螃蟹上的腿,这条腿可以对螃蟹内部的数据进行处理。
以上就是,面向对象的思想,或者是面向属性(数据)的思想。在刚接触Python面向对象的时候,可能会觉得对象可以继承,多态,封装,但忽略了其实对象是一个存放数据的容器,并且不同的对象可以存放不同的数据。下面为大家介绍一些python中常用的黑科技
魔术方法
在python的对象中,魔术方法被使用到python的方方面面,想了解的同学,可以看下面这篇文章
super
在类的继承关系中,super代表当前类的继承类,通过这一点,我们可以在继承类方法返回的结果的基础上再做进一步的处理。
class A: def eat(self): print('A') class C(A): def eat(self): # super()获得A对象 return super().eat() C().eat() # A
在多继承关系中,python会根据调用方所属类的__mro__
属性中,选择在当前类后面紧挨的一个类作为super()的结果。
class A: def eat(self): print('A') class B: def eat(self): print('B') class C(B,A): def eat(self): return super().eat() if __name__ == '__main__': print(C.__mro__) C().eat() # (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>) # C后面紧跟的是B,所以super().eat()是调用的B的eat方法 # B
单例
这个应该属于是一种设计模式,效果是,无论对类初始化了多少次,返回的实例都是相同的一份。这一点在一些配置类中非常有用,比如可能一个配置类被好几个地方使用,我们希望在A这里修改配置后,在B那里也可以同时修改,那么A和B使用的如果是同一个的话,这一点实现起来就非常方便。在python中通常会使用重写python的__new__
方法来控制类的创建,或者是使用装饰器来进行实现。这里介绍一种使用重写__new__
方法的方法
from threading import Lock # 线程锁 lock = Lock() class Conf: # 唯一一份实例 instance = None @classmethod def __new__(cls, *args, **kwargs): # 加锁,保证线程安全 lock.acquire() # 判断是否有实例被创建 # 没有的话,就创建一份,保存到instance上 if cls.instance is None: # super() -> object(基类) cls.instance = super().__new__(cls) lock.release() # 返回实例 return cls.instance if __name__ == '__main__': conf1 = Conf() conf2 = Conf() print(id(conf1)) print(id(conf2))
通过type来创建类
在python中type不只是可以用来判断类型,也可以动态地创建类。
class A: def eat(self): print('eat') # 第一个参数是类的名字 # 第二个参数是tuple类型,为需要继承的类 # 第三个参数是需要绑定的属性,注意,这里是类属性 Conf = type('Conf', (A, ),{'name': 1}) if __name__ == '__main__': conf1 = Conf() print(conf1.name) conf1.eat()
四个有用的函数
下面的函数只能对实例后的对象进行增删改查,换句话说,对于类属性是不起作用的。
- hasattr 判断一个对象是否有一个属性或方法
- getattr 获取一个对象的属性或方法,没有会报错
- setattr 为对象设置一个新的属性
- delattr 删除一个对象的属性
class A: def eat(self): print('eat') Conf = type('Conf', (A, ),{'name': 1}) if __name__ == '__main__': conf1 = Conf() print(hasattr(conf1, 'name')) # True print(getattr(conf1, 'name')) # 1 setattr(conf1, 'name', 'apple')# 设置name为apple print(getattr(conf1, 'name')) # apple delattr(conf1, 'name') # 删除name属性 print(hasattr(conf1, 'name')) # 1 这里为什么会是1哪?
最后留一个问题 类实例化后,属性被保存到了哪里?