面向对象

简介: 面向对象

面向对象是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在语言层面帮我们省了很多代码而已。从数据角度来看一下面向对象,每一个对象其实可以看做一个存放数据的螃蟹,里面会有若干个属性,而方法则是螃蟹上的腿,这条腿可以对螃蟹内部的数据进行处理。

d523ff0900871bbf97411bd99d4f1ad1_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

以上就是,面向对象的思想,或者是面向属性(数据)的思想。在刚接触Python面向对象的时候,可能会觉得对象可以继承,多态,封装,但忽略了其实对象是一个存放数据的容器,并且不同的对象可以存放不同的数据。下面为大家介绍一些python中常用的黑科技

魔术方法

在python的对象中,魔术方法被使用到python的方方面面,想了解的同学,可以看下面这篇文章

ea063cc8444052dc596c19eca74cf18a_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

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哪?

最后留一个问题 类实例化后,属性被保存到了哪里?

相关文章
|
6月前
|
C++
c++面向对象
c++面向对象
42 0
|
6月前
|
C语言 C++
【c++】什么是面向对象
【c++】什么是面向对象
【c++】什么是面向对象
面向对象(1)
面向对象(1)
75 0
C#面向对象知识
C#面向对象知识
47 0
|
存储 搜索推荐 编译器
C++ 面向对象篇
C++程序在执行时,将内存大致分为四个区域; - 代码区:存放函数体的二进制代码,操作由系统管理 - 全局区:存放全局变量和静态变量以及常量 - 栈区:由编译器自动分配释放,存放函数的参数值(形参),局部变量等 - 堆区:由程序员分配和释放,若程序员不手动释放,系统在程序结束时自动回收
152 0
|
Java
2. 面向对象(三)
2. 面向对象(三)
151 0
面向对象几个问题
面向对象几个问题
90 0
|
Java
面向对象(一)
面向对象基础
97 0
|
Java
面向对象(二)
javase内容
75 0
下一篇
无影云桌面