[python]15、面向对象

目录

1、什么是面向对象

1.1、函数式编程

1.2、面向过程编程(函数)

1.3、面向对象编程(OOP——Object Oriented Programming)

1.4、面向过程VS面向对象

1.5、面向对象的基本概念

1.5.1、类(基类、子类)

1.5.2、类的好处

1.5.3、面向对象的好处

1.6、实例

1.7、属性

1.8、方法

1.9、类的基本特点

2、类的定义和使用

2.1、类的定义

2.2、实例化:创建一个类的实例

2.2.1、属性访问

2.2.2、属性添加

2.2.3、实例空间和类空间

2.3、__init__:实例初始化

2.4、__new__方法

 2.4.1、总结__new__和__init__方法

2.5、单例模式

2.5.1、单例的图解 

3、self讲解

3.1、self不必非写成self;也可以不写self

4、继承

4.1、类的继承

4.2、继承父类的属性和方法

4.2.1、super()

4.3、类和实例的关系

4.3.1、要注意的事项

4.3.2、isinstance()

5、多态

5.1、鸭子模型

5.2、对于python是否支持多态其实是有争议的

6、经典类和新式类的区别

6.1、经典式和新式类在类型上的区别 

6.2、在继承顺序上的区别

6.2.1、C3算法

7、静态方法和类方法

8、python的下划线

8.1、子类不能访问私有成员

8.1.1、类对象也不能访问私有成员

8.2、私有成员只能在内部进行访问

8.3、那么我们定义的私有属性到底能用类对象和子类来直接访问呢?

8.4、常见的以下划线开头和以双下划线结尾的特殊变量

8.4.1、__dict__

8.4.2、__name__

8.4.3、__class__

8.4.4、__module__

8.4.5、__doc__

9、python中常见的魔术方法⭐⭐⭐

9.1、析构函数(__del__)

9.2、调用方法(__call__)

9.3、__str__和__repr__

9.3.1、自定义异常类

9.4、__getitem__、__setitem__、__delitem__

9.5、其他魔术方法

10、python的自省

10.1、自省的4个方法

10.2、接受用户从键盘输入,按q退出。如果输入func1,就执行func1。其他情况,提示没有这个函数。

11、元类

11.1、type创建类

11.2、元类的作用

11.3、python面向对象关系

12、抽象基类

12.1、抽象基类不能实例化

13、要重点记的


1、什么是面向对象

常见的三种编程范式:

  • 函数式编程
  • 面向过程编程(函数)
  • 面向对象编程(类)

1.1、函数式编程

  • 函数可以作为参数传递、修改,或作为返回值
  • 函数内不修改外部变量的状态

1.2、面向过程编程(函数)

根据操作数据的语句块来实现功能。重点在于:做什么

1.3、面向对象编程(OOP——Object Oriented Programming)

把数据和功能结合起来,用称为对象的东西包裹起来组织程序的方法。重点在于:谁去做

1.4、面向过程VS面向对象

  • 面向过程是一件事"该怎么做", 着重于做什么。
  • 面向对象是一件事"该让谁来做",着重于谁去做

1.5、面向对象的基本概念

  • 对象:通过类定义的数据结构实例。
  • 对象包括两个数据成员(类变量和实例变量)和方法
  • python一切皆对象,类是对象,实例也是对象

1.5.1、类(基类、子类)

  • 类(class):用来描述具有相同的属性和方法(能做的事)的对象的集合。
  • 它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

类:就是描述某类物品的特征的东西

比如:

  • 车子的特征:轮子、车牌
  • 车子的方法:发动、会跑

1.5.2、类的好处

  • 代码重用、减少代码量。

1.5.3、面向对象的好处

  • 迭代更新方便
  • 结构清晰,易于管理

1.6、实例

  • 实例:具体的某个事物,某个类型实实在在的一个例子
  • 实例化:创建一个类的实例,类的具体对象
  • 类是一种生产实例的工厂

1.7、属性

  • 对象的描述信息,一种状态。==》变量
  • 例如
    • 空调的属性:品牌、功率、节能等级
    • 人的属性:身高、体重、姓名、性别、爱好

1.8、方法

  • 对象的行为:类中定义的函数
  • 空调的方法:制冷、制热
  • 人的方法:吃饭、打篮球、睡觉

1.9、类的基本特点

  • 封装
    • 在类中对数据的赋值,内部调用对外部用户是透明的
    • 把一些功能的实现细节不对外暴露
  • 继承
    • 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。
    • 为实现代码的重用,一个类可以派生出子类
    • 继承也允许把一个派生类的对象作为一个基类对象对待
  • 多态
    • 接口重用
    • 一个接口,多种实现(重写)

2、类的定义和使用

2.1、类的定义

  • 类命名的规范
    • 一般首字母大写(大驼峰)
    • Person、GoodInfo
  • 函数的命名
    • 由小写字母和下划线组成
    • scan_book、drink
  • 类的定义方式(关键字:class)
    • 经典类和新式类
      • 有两种类的划分:经典类和新式类
        • python2 显示继承object的类就成为新式类,反之就是经典类(例如下边的三种方法中,只有C是新式类)
        • python3  只有新式类 默认都是继承object,所以都是新式类(下面三种方法种,都是新式类)
# 定义类的三种方法
# class A:
#     pass
# class B():
#     pass
# class C(object):
#     pass

2.2、实例化:创建一个类的实例

class ATM():
    # 对象的描述信息,即属性
    money = 50000
    country = "china"
    bank = "gonghang"

    # 方法
    def get_money(self, money):
        print(f"get money:{money}")


# 实例化:创建一个类的实例
a1 = ATM()
a2 = ATM()
# 下边2.2节代码的打印都是这个主代码

2.2.1、属性访问

print(dir(a1))
print("实例访问".center(20,"*"))
print(a1.money, a2.country)
a1.get_money(500)
print("类访问".center(20,"*"))
print(ATM.money)

结果: 

2.2.2、属性添加

# 添加属性
a1.area = "hunan"
print(a1.area)
# 这样添加的话,a1和ATM都是没有这个属性的
# print(a2.area,ATM.area)  这个代码会报错

# 类添加,这种添加之后,类里边的对象都会有这个属性
ATM.color = "blue"
print(a1.color,a2.color)

结果:

2.2.3、实例空间和类空间

print("类空间".center(30,"*"))
print(ATM.__dict__)
print("a1的空间".center(30,"*"))
print(a1.__dict__)
print("a2的空间".center(30,"*"))
print(a2.__dict__)

a1.money = 20000
print(a1.money,a2.money)

# 结果为  20000 50000
# 属性查找的时候,会先在自己的空间里找,若是没有会一层一层的往上找。

总结

  • 类创建的时候会生成类空间
  • 实例化对象生成实例空间,不同的实例之间,空间都是独立的
  • 实例查找属性方法的时候,先在实例空间去查找,找不到就去类空间查找
  • 类空间找不到,就去父类空间找
  • 实例空间会有一个类对象指针,通过这个指针实例就可以访问类的属性方法了。 

2.3、__init__:实例初始化

  • 这个名称的开始和结尾都是双下划线。
  • 这个方法可以用来对你的对象做一些你希望的初始化任务。
  • __init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,它在类的一个对象被建立后,自动运行。
# 实例对象的构造方法(初始化方法)
# 实例化对象的时候自动调用__init__方法
class ATM():
    # 对象的描述信息,即属性
    # 类属性
    money = 50000
    country = "china"
    bank = "gonghang"

    def __init__(self, area):
        # 前边这个area是属性,后边这个area是形参
        # 实例属性
        self.area = area

    # 方法
    def get_money(self, money):
        print(f"get money:{money}")


# 实例化
atm1 = ATM("hunan")
atm2 = ATM("hubei")
print(atm1.area, atm2.area)

#######
# 结果
hunan hubei

2.4、__new__方法

class A:
    pass


class ATM():
    # 对象的描述信息,即属性
    # 类属性
    money = 50000
    country = "china"
    bank = "gonghang"

    def __init__(self, area):
        # 前边这个area是属性,后边这个area是形参
        # 实例属性
        # 当__new__返回的是当前这个对象的时候,才会执行__init__
        print("this is init>>>>>>>>>>>>>>")
        self.area = area

    # 这个地方一般不需要写
    def __new__(cls, *args, **kwargs):
        print("this is new method:", cls)
        print(args, kwargs)
        # cls,表示当前类
        # 若是换成A,那么结果就是
        return object.__new__(cls)

    # 方法
    def get_money(self, money):
        print(f"get money:{money}")


atm1 = ATM("changsha")  # 这个时候atm1是None,所以这个时候要在__new__的时候进行return
print(type(atm1))

结果: 

若是把__new__中"return"括号中换成A,结果为:

 2.4.1、总结__new__和__init__方法

  • __new__
    • 是创建实例的方法
    • 必须要传入一个参数(cls),表示当前这个类
    • 必须返回一个实例对象
    • 子类中没有定义__new__,就会去找父类的__new__
    • 新式类才有__new__
    • 属于静态方法
  • __init__
    • 是创建好的实例进行初始化的方法
    • 里边的self就表示__new__返回的实例,__init__再对这个实例进行初始化工作
    • 如果实例化对象和本身的class不一致,__init__不会执行。例如上边的那个例子
    • 是实例方法

2.5、单例模式

无论实例化多少次,都返回一个对象实例


class Danli:
    # 当看到class,就说明里边的属性(内空间)已经形成好了,并不会像循环那样,每次过来又重新执行一下里边的代码
    # 即instance只有最开始初始化是为None,但是能够随着我们的改变而发生改变
    # 一个简单的例子:买了个空房子,你给里边装修,这个还是那个房子,但里边东西发生了改变。你不可能再次进入房间,里边还是什么东西都没有
    instance = None

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = object.__new__(cls)
        return cls.instance


d1 = Danli()
d2 = Danli()
print(id(d1), id(d2))


########
# 运行结果
1246813568592 1246813568592

2.5.1、单例的图解 

最开始是指向None的,修改之后指向实例。但是它们都是同一个内存空间

3、self讲解

类的实例方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称。 在调用这个方法的时候你不为这个参数赋值,Python会提供这个值。 这个特别的变量指对象本身,按照惯例它的名称是self。

class User():
    name = "sc"

    def info(self):
        print(f"i am {self.name}")
        print(self)
        # 结果:<__main__.User object at 0x000002352C69B9D0>
        # 说明self是User的一个对象,内存空间在0x000002352C69B9D0
        print(self.__class__)


user1 = User()
user1.info()
user2 = User()
user2.info()

运行结果:

  • self代表类的实例,而非类。
  • 当哪个user调用User类的时候,self就表示哪个user

3.1、self不必非写成self;也可以不写self

self不必一定非要写成self,也可以写成其他的。它只是一个变量的名字,只是约定俗成写成self

class User():
    name = "sc"

    def info(self):
        print(f"i am {self.name}")
        print(self)
        print(self.__class__)

    def info2():
        print("this is info2")

user1 = User()
user1.info()  #########这个命令底层为  User.info(user1)
user1.info2()  ########## 这个命令底层为  User.info2(user1),但是因为info2并没有参数传入,所以会报错
user2 = User()
user2.info()   #########这个命令底层为User.info(user2)

运行结果:

去除掉那个错误的代码(用实例调用没有self的方法)之后的运行结果:

所以不写self的时候,若是用实例调用的话,调用不了。会自动把实例本身当作一个参数传入进去

但是我们要是直接使用类调用不写self的方法的话,是可以调用的。

class User():
    name = "sc"

    def info(self):
        print(f"i am {self.name}")
        print(self)
        print(self.__class__)

    def info2():
        print("this is info2")

User.info2()

############ 执行结果
this is info2

4、继承

面向对象的好处

  • 面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一就是继承机制。
  • 继承完全可以理解成类之间的类型和子类型的关系。
    • 可以节省很多的代码,不需要写,直接使用
    • 衍生出不同的子类,大部分代码一样,部分代码不一样

4.1、类的继承

  • 代码:父类Parent/子类Child
  • 子类有__init__:执行自己的初始化函数
  • 子类没有__init__:在子类实例化的时候会自动执行父类的构造函数
class Animal():
    species = "Animal"
    count = 0

    def __init__(self):
        self.name = "animal"
        Animal.count += 1
        print("初始化animal。。")

    def breath(self):
        print("i can breath")

    def eat(self):
        print("i can eat")


class Person(Animal):
    species = "Person"  # 重写父类的属性


animal1 = Animal()
print(animal1.count)
p = Person()  # 自己没有__init__,所以会调用父类的__init__,即Animal的__init__
print(p.species, p.count)
#### 运行结果
初始化animal。。
1
初始化animal。。
Person 2

4.2、继承父类的属性和方法

  • 对象属性查找,先在实例空间找,没有就去类空间找
  • 类空间也没有就去父类空间,层层往上寻找,直到找到object
class Dog(Animal):
    def __init__(self):  # 重写父类的__init__
        print("i am dog")

    def eat(self):  # 重写父类的eat方法
        print("dog is eating...")


d = Dog()
print(p.name)  # 调用父类中的name ,所以self就代表p了
# print(d.name)   # d没有name属性
d.eat()


class Pig(Animal):
    count = 0  # 重写父类的count属性

    def __init__(self):  # 重写父类的__init__方法
        # super().__init__()  # 子类去执行父类__init__方法属性;
        # 相当于执行了下面三行代码
        # self.name = "animal"
        # Animal.count += 1
        # print("初始化animal。。")
        super().eat()
        self.name = "pig"
        Pig.count += 1
        print("初始化Pig")
        # 若是super在这后边那么就会覆盖Pig的__init__。即会导致没有重写父类的__init__方法
        # super().__init__()  # 子类去执行父类__init__方法属性;


print("*" * 20)
pig1 = Pig()
print(pig1.count, animal1.count, pig1.name)

##### 运行结果
i am dog
animal
dog is eating...
********************
i can eat
初始化Pig
1 2 pig

4.2.1、super()

super().__init__(),就是表示子类去执行父类的__init__方法。

注意,若是我们要自己重写一个方法且要调用父类的相同方法的时候,我们需要把super放在这个方法的前面,这样就可以在继承父类的方法属性的同时,又添加我们想要添加的新的类属性。若是super()放在方法的后面,那么就会导致没有重写父类的方法。具体的

4.3、类和实例的关系

print("*" * 20)
print(isinstance(pig1, Pig),isinstance(pig1,Animal))
print(type(pig1))  # <class '__main__.Pig'> 属于正在运行的模块的Pig类
a = str(10)  # 工厂函数看起来是函数,也可以说是函数,但是本质上是 类
print(type(a),str.lower("ABC"), "ABC".lower())
#################运行结果
True True
<class '__main__.Pig'>  # 这个main就是表示当前正在运行的模块
<class 'str'> abc abc

4.3.1、要注意的事项

  • 工厂函数看起来是函数,也可以说是函数,但是本质上是 类

4.3.2、isinstance()

我们可以使用这个函数来查看,我们的实例是否和我们想要知道的类有继承关系。若是显示的结果是true,那么表示这个实例和类有关系;反之,就是没有关系。

5、多态

意义:接口重用,让程序更加灵活

class zhifubao():
    def pay(self):
        print("this is zhifubao pay")

class bank():
    def pay(self):
        print("this is bank pay")

class weixin():
    def pay(self):
        print("this is weixin pay")

z = zhifubao()
b = bank()
w = weixin()

def pay(obj):  # 接口的多种形态, 接口的重用
    obj.pay()


pay(z)
########运行结果
this is zhifubao pay

5.1、鸭子模型

  • 多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的 实现方式即为多态。
  • Python是一种多态语言,崇尚鸭子类型。
  • 在程序设计中,鸭子类型是动态类型的一种风格。
  •  鸭子模型是指:"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就 可以被称为鸭子。"
  • 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
  • 在鸭子类型中,关注的不是对象的类型本身,而是他如何使用的
  • 多态其实是一种行为的封装,你只需知道你所操纵的对象所能够做的事情(接口),那么你就在需要的时候叫它去做,具体怎么做由它自己去决定,你不需要知道而且没有必要知道。

5.2、对于python是否支持多态其实是有争议的

  • python不支持多态,语法上的多态,不需要额外实现多态代码按照多态的语法,不属于多态(父类作为参数类型,传递子类对象)
  • python 处处是多态 python是一个多态类型语言,本身实现了多态崇尚鸭子类型,不关心对象是什么类型,到底是不是鸭子,只关心有没有这个行为

6、经典类和新式类的区别

6.1、经典式和新式类在类型上的区别 

  • 经典类 通过type查看到的实例都是instance,所有的类都是classbj类型
    • 类和实例之间只能够通过__class__属性进行关联
  • 新式类 通过type查看到的实例类型就是类名
####在交互式环境中使用

# python2
>>> class A:pass
...
>>> a = A()
>>> type(a)
<type 'instance'>
>>> a.__class__    # python2中要使用__class__才能够知道实例所属的类
<class __main__.A at 0x7fd6de050258>
>>>

# python3
>>> class A:pass
...
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class '__main__.A'>
>>>
KeyboardInterrupt

6.2、在继承顺序上的区别

  • 经典列 -- 深度优先算法(一条路走到底)
  • 新式类 -- C3算法
class A:
    def test(self):
        print("from A")

class B(A):
    def test(self):
        print("from B")

class C(A):
    def test(self):
        print("from C")

class D(B):
    def test(self):
        print("from D")

class E(C):
    def test(self):
        print("from E")

class F(D,E):
    pass
    # def test(self):
    #     print("from F")

f = F()
f.test()
#######运行结果
from F

若是要证明算法的步骤,可以在类下面的方法注释掉,加入pass。如下图所示

 对于这两种算法的结果为:

  • 经典类(深度优先):(F-D-B-A-E-C)
  • 新式类(C3算法)(F - D - B - E - C - A)

6.2.1、C3算法

  • 首先,将自身类加入本序列,然后再对继承序列的元素,依次判断
  • 若某个元素不在其他序列或者它是所有继承序列的第一个,那么就把这个元素提取到本序列

 下面用图片说明这个算法的步骤。

7、静态方法和类方法

属性

  • 静态属性(所有的实例共用一份=>引用)
  • 普通属性(每个实例都不同的数据=>引用)

方法

  • 普通(实例)方法(使用实例中的数据) => 给实例使用(一定要传递一个实例,由实例调用)
  • 静态方法(无需使用实例封装的内容@staticmethod)
  • 类方法(会自动加当前类的类名 @classmethod) => cls表示类本身
class A:
    name = "class A"
    def __inin__(self):  # 普通方法
        self.contry = "china"

    # 实例方法
    # 第一参数代表实例本身  self -- 实例
    def normal_method(self, name):
        # 方法的里面,既可以访问类属性,又可以访问实例属性(self.name)
        print("normal:")
        print(self.name, name)

    # 类方法
    # 第一参数代表类, cls -- 类
    @classmethod  # 装饰器,类方法
    def class_method(cls, name):
        # 类方法里面  可以访问类属性
        print("classmethod")
        print(cls, cls.name)
    # 静态方法
    # 参数可传可不传
    @staticmethod  # 静态方法
    def static_method(name):
        # 静态方法里面,可以通过类名去访问类属性
        print("static_method:", name, A.name)

a1 = A()

调用方法

静态方法、实例方法、类方法都能通过实例或者类去调用

# 实例调用
a1.normal_method("a1")
a1.class_method("a1")
a1.static_method("a1")

# 类调用
print("#"*40)
A.normal_method(a1,"A")
A.class_method("A")
A.static_method("A")

####### 运行结果
normal:
class A a1
classmethod
<class '__main__.A'> class A
static_method: a1 class A
########################################
normal:
class A A
classmethod
<class '__main__.A'> class A
static_method: A class A

Process finished with exit code 0

8、python的下划线

  • 属性或者方法前边一个下划线叫做保护属性或者保护方法
  • 有两个下划线叫做私有属性和私有方法
class P:
    """
    this is p
    P test
    """
    _min = 1  # 保护属性
    __max = 10  # 私有属性

    def __init__(self):
        self.name = "sc"
        self._age = 4
        self.__desc = "it"

    def __make(self):
        print("这是一个私有方法")
        print(self.__desc)

    def _protectmake(self):
        print("这是一个保护方法")

    def show(self):
        print(self.__max, self.__desc)


class Child(P):
    def show(self):
        print(self.__max)


p = P()
c = Child()
print(c._min, p._min, Child._min, P._min)

#######执行结果
1 1 1 1

8.1、子类不能访问私有成员

8.1.1、类对象也不能访问私有成员

8.2、私有成员只能在内部进行访问

p.show()

####### 运行结果
10 it


"""
这是利用了实例p调用show方法来,然后show方法中有调用私有属性__max和__desc。所以就能够实现私有成员的调用
"""

8.3、那么我们定义的私有属性到底能用类对象和子类来直接访问呢?

# 查看实例p的属性
print(dir(p))

#####执行结果
['_P__desc', '_P__make', '_P__max', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_age', '_min', '_protectmake', 'name', 'show']

#######在这里我们发现了我们定义的__max,但是形式却改为了_P__max。

python中的私有都是伪私有

实际上python的内部就是把双下划线开头的标识符,改了一个名字存储而已。格式为:_类名__标识符。

我们知道这点之后就可以使用子类来直接调用私有属性和私有方法了

print(p._P__max)
print(p._P__make)

####### 执行结果
10
<bound method P.__make of <__main__.P object at 0x000002ABF6FF7FD0>>

8.4、常见的以下划线开头和以双下划线结尾的特殊变量

8.4.1、__dict__

作用:查看命名空间 得到的是一个字典

print(P.__dict__)  # 这是大写
print(p.__dict__)  # 这个是小写

######## 执行结果
{'__module__': '__main__', '__doc__': '\n    this is p\n    P test\n    ', '_min': 1, '_P__max': 10, '__init__': <function P.__init__ at 0x000001E9B75E69D0>, '_P__make': <function P.__make at 0x000001E9B75E6B80>, '_protectmake': <function P._protectmake at 0x000001E9C89524C0>, 'show': <function P.show at 0x000001E9C89951F0>, '__dict__': <attribute '__dict__' of 'P' objects>, '__weakref__': <attribute '__weakref__' of 'P' objects>}
{'name': 'sc', '_age': 4, '_P__desc': 'it'}

8.4.2、__name__

作用:查看类名

print(P.__name__)   #### 这里的P都是大写的

####### 执行结果
P

这可能看起来很鸡肋,但是我用一个例子来说明这个__name__的作用也许你就会眼前一亮。

def func1(cls):
    a = cls()
    print(f"{cls.__name__}进行了实例化")

class A:
    pass
class B:
    pass

func1(A)
func1(B)
################# 执行结果
A进行了实例化
B进行了实例化

8.4.3、__class__

作用:查看对象属于哪个类

print(p.__class__)


##### 执行结果
<class '__main__.P'>

8.4.4、__module__

作用:查看所在模块

print(P.__module__)


#### 执行结果
__main__

8.4.5、__doc__

作用:文档注释

print(P.__doc__)   # help会自动调用__doc__属性

########### 执行结果

    this is p
    P test
   


#这个结果其实就是我们在P类最开头定义的文档注释(第一个用""" """包裹起来的内容)

9、python中常见的魔术方法⭐⭐⭐

定义:一般以双下划线开头和双下划线结尾.而且是有特殊含义的方法,一般不需要手动去调用,他会在某种特定的场景下自动执行。

9.1、析构函数(__del__)

作用:在实例释放、销毁的时候自动执行的,通常用于做一些收尾工作, 如关闭一些数据库连接,关闭打开的临时文件

###### 实验1
class A:
    def __del__(self):
        print("this is A.del")

a1 = A()
# del a1
print("xxxxxxxxxxxxxxxxxx")


###### 执行结果
xxxxxxxxxxxxxxxxxx
this is A.del



########实验2
class A:
    def __del__(self):
        print("this is A.del")

a1 = A()
del a1
print("xxxxxxxxxxxxxxxxxx")


#### 执行结果
this is A.del
xxxxxxxxxxxxxxxxxx

9.2、调用方法(__call__)

定义:把类实例化后的对象当做函数来调用的时候自动被调用

class A:
    def __call__(self, name):
        print(f"i am A.__call__, name is {name}")

a1 = A()
a1("sc")


#########  执行结果
i am A.__call__, name is sc

9.3、__str__和__repr__

  • __str__ 给用户看,没有__str__,默认调用__repr__
  • __repr__ 给程序员看的、更加官方一点
# 返回对象的描述信息
ie = IndexError("text")
print(ie)

class A:
    def __str__(self):
        return "this is A"
a1 = A()
print(a1)


#########  执行结果
text
this is A

9.3.1、自定义异常类

class NotTitleType(Exception):
    def __str__(self):
        return "不是正确的标题格式"

num = input("请输入一个标题:")
try:
    if num.istitle():
        print(num)
    else:
        raise NotTitleType
except NotTitleType as fi:
    print(fi,"结果")



######### 执行结果
#结果1
请输入一个标题:Tile
Tile

#结果2
请输入一个标题:fidl
不是正确的标题格式  结果

9.4、__getitem__、__setitem__、__delitem__

对象以字典的形式去设置或获取参数

class A:
    def __init__(self):
        self.data = {}
    def __getitem__(self, key):
        print("get data")
        return self.data.get(key, 0)
    def __setitem__(self, key, value):
        print("set data:",key, value)
        self.data[key] = value
    def __delitem__(self, key):
        print("delete data")
        del(self.data[key])

a1 = A()
print(a1["key1"])
a1["key1"] = "xxxxxxxxx"
print(a1["key1"])
del a1["key1"]

####### 执行结果
get data
0   # 这里因为最开始没有定义key1,所以返回的是0
set data: key1 xxxxxxxxx
get data
xxxxxxxxx   # 这里后面定义了key1,所以返回的不是0了
delete data

9.5、其他魔术方法

__eq__(self, other) 定义了等号的行为, ==

__ne__(self, other) 定义了不等号的行为, !=

__lt__(self, other) 定义了小于号的行为, <

__gt__(self, other) 定义了大于等于号的行为, >=

__add__(self, other) +运算

__mul__(self, other) *运算

__len__(self, other) 获得长度

举例:

class A:
    def __init__(self, num):
        self.num = num
    def __add__(self, x):
        print("this is add")
        return self.num + x
a = A(4)
print(a + 6)  # 没有add方法,就不能相加。这里把6放入x中


###### 执行结果
this is add
10

10、python的自省

什么是自省?

在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么.自省向程序员提供了极大的灵活性和控制力。
有时我们要访问某个变量或是方法时并不知道到底有没有这个变量或方法,所以就要做些判断。判断是否存在字符串对应的变量及方法。 
我们知道访问变量时是不能加引号的,否则会被当成字符串处理。如果要通过字符串找到对应的变量,那该怎么办呢

10.1、自省的4个方法

  •  getattr(obj, 'name'): 获取成员

    • 根据字符串去获取obj对象里的对应的方法的内存地
  • hasattr(obj, 'name'): 检查是否含有成员
    • 判断一个对象obj是否有对应的name_str字符串的方法
  • ​​​​​​​setattr(obj, 'age', 18):设置成员
  • delattr(obj,"name"):删除成员
# sc.py

a = 10
def func1():
    print("this is func1")
    
import sc

print(hasattr(sc,"a"))  # 判断sc里有没有a属性,True表示有这个属性,False表示没有
# 只要xx.xx这种形式的属性都能够访问出来(自省)
print(hasattr(sc,"b"))
print(getattr(sc,"a"))  # 获取sc里的属性a的值
setattr(sc,"b","ftttt")  # 设置属性
print(sc.b)
delattr(sc,"b")  # 删除属性
print(hasattr(sc,"b"))

# 运行结果
True
False
10
ftttt
False

10.2、接受用户从键盘输入,按q退出。如果输入func1,就执行func1。其他情况,提示没有这个函数。

class A:
    px = "sc"

    def func1(self):
        print("i am func1")

    def func2(self):
        print("i am func2")

a = A()  # 进行实例化
while True:
    choose = input("please input function:")
    if choose.lower() == "q":
        break
    if hasattr(a, choose):
        func = getattr(a, choose)
        # 查看得到的成员有没有__call__这个属性,要是有的话,那么这个成员就是类。要是没有的话,说明这个成员是一个属性。
        # if hasattr(func, "__call__"):
        if callable(func):  # 或者直接使用callable函数检查这个成员是不是一个类
            func()
        else:
            print(func)
    else:
        print("没有这个函数")

11、元类

class A:
    pass

a1 = A()
print(type(a1)) # 结果<class '__main__.A'>
print(type(A))  # 结果<class 'type'>,A由type创建
# python中一切皆对象,类也是对象,类也是由另一个类创建的
# 创建类的类称之为元类
# type是最上层的元类

Python中一切皆对象, 包括函数和类。这意味着函数与类都可以作为参数提供、以类实例的成员形式存在 ,且可以完成其他对象所能完成的工作
对象在实例化的过程中,会调用__init__和__new__方法创建新对象,该过程对于类而言也不例外。一个类本身又是另一个类的实例,用于创建类的类。负责生成其他类的类就是 元类
(Metaclass)
元类就是用来创建类的类。

11.1、type创建类

语法: type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

我们一般定义类都是使用class定义类

class Animal():# 看到class就是调用type去创建
    def __init__(self,name):
        self.name = name
    def eat(self):
        print("i am eating.......")

print(Animal)
# 结果
<class '__main__.Animal'> 

我们直接使用type去创建上面一摸一样的类

def init(self, name):
    self.name = name


def eat(self):
    print("i am eating.......")


Animal = type("Animal", (object,), {"__init__": init, "eat": eat})
a1 = Animal("sc1")  # 这里传递了一个参数name给init
print(a1.name)

# 运行结果
sc1

11.2、元类的作用

  • 拦截类的创建
  • 修改类,返回修改之后的类
class MyMate(type):  # 继承type
    def __new__(cls, name, bases, attrs):
        if "ufo" not in attrs:
            raise TypeError("必须设置ufo属性")
        # return type.__new__(cls,name,bases,attrs)
class A(metaclass=MyMate):
    # 把A传递给name,object传给bases,这里没有什么传给attrs,所以MyMate那里就会抛出错误 
    # ufo = 'sc'
    pass

# 运行结果
Traceback (most recent call last):
  File "F:\Desktop\Pycharm练习\面向对象\11.元类.py", line 50, in <module>
    class A(metaclass=MyMate):
  File "F:\Desktop\Pycharm练习\面向对象\11.元类.py", line 48, in __new__
    raise TypeError("必须设置ufo属性")
TypeError: 必须设置ufo属性
# 自定义元类
class MyMate(type):  # 继承type
    def __new__(cls, name, bases, attrs):
        if "ufo" not in attrs:
            raise TypeError("必须设置ufo属性")
        return type.__new__(cls,name,bases,attrs)  # 这里要返回值,不然的话print(A)运行的结果是None
class A(metaclass=MyMate):
    # 把A传递给name,object传给bases
    ufo = 'sc'

print(A)

# 运行结果
<class '__main__.A'>

11.3、python面向对象关系

  1. 继承关系  object是所有类的父类,是最顶层的类

  2. 创建关系  实例与类的关系,type是最顶层的类

print(type(object))
print(type(type))
print(object.__bases__)
print(type.__bases__)

# 运行结果
<class 'type'>
<class 'type'>
()
(<class 'object'>,)

### 这个结果表示object = type(object),即type创建了object,同时又继承了object
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

12、抽象基类

抽象基类:定义了接口规范,子类必须实现抽象基类父类里的抽象方法。只是定义接口规范,不需要具体实现。

12.1、抽象基类不能实例化

from abc import ABC, abstractmethod

class Animal(ABC):
    # 定义抽象方法
    @abstractmethod
    def eat(self):   # 只是抽象基类中的抽象方法有要求,这里eat方法被@abstractmethod修饰了,所以是抽象方法
        print("this is eat")
    def drink(self):  # 不是抽象方法可以不用实现
        pass

class Dog(Animal):
    def eat(self):  # 若是没有定义这个方法,那么下面d1 = Dog()运行就会报错。
        print("dog is eating....")


d1 = Dog()
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

13、要重点记的

1.经典类和新式类的区别
2.常见的魔术方法
3.面向对象里有哪些方法

面向对象里超考点

其中,魔术方法考的最多:你知道哪些魔术方法,你用过哪些,介绍一下。魔术方法有什么用

init和new

经典类和新式类是怎么继承的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FanMY_71

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值