1.面向对象

#问题:

         ①写代码时,什么时候用面向对象:处理比较复杂的角色之间的关系(代码量大,功能多的时候),如复杂的电商程序、公司/学校的人事管理的系统;提高代码的可读性,清晰度(无论是开发者还是调用者,都能明确的分辨出每个角色拥有的方法和属性);增强代码的可扩展性;增加了复用性;代码更加规范

         ②如何理解Python中一切皆对象:每一个基础数据类型都是对象,有属于自己的类,每个类特有的方法,只能属于本类的对象去使用

  ③类型和自定义类的关系:类型和类是一个东西,自定义的每一个类都是一个新的数据类型;当type(obj)时,如果obj是一个对象,那么它的type就是它的类型

  ④创建一个类:class 类名;python解释器读到这句话是创建类(语法级别);类也是被创建出来的,通过type创建类,即类型的类型就是类型,默认为【class A(metaclass=type)】,可以说type就是这个类的元类

class Student:
    pass
s = Student()
print(type(s))        #对象的类型是类
print(type(Student))  #类的类型就是type
输出:
<class '__main__.Student'>
<class 'type'>
View Code

#对于抽象类【class A(metaclass=ABCMeta)】可以说ABCMeta创建了这个A类,ABCMeta就是A的元类

from abc import  ABCMeta
class A(metaclass=ABCMeta):pass
print(A)
输出:
<class '__main__.A'>
View Code

总结:type是所有类的元类,object是所有类的父类;元类是用来创建类的

  ⑤创建一个对象:类名()叫做实例化;__new__()创造了一个对象的空间;__init__进行一些简单的初始化工作

1)类:class Leiming

①类是什么时候被记载的

  #类是从头到尾加载到内存中的(所以类的执行和有无实例化没有关系),如果类中有函数,只加载函数名,函数中的内容,只有在调用函数时才执行

class A:
    name = "阿狸"
    print(name)
    def name(self):
        print("函数执行")
输出:
阿狸
View Code

②类名是什么时候生效的

#从头开始加载类,当把类中所有的变量加载完成后,才让类名去执行这个对象空间,所以在类中,使用类名去调用变量会报错

class A:
    name = "阿狸"      
    print(A.name)   #语法报错,只有在类中所以内容加载完成,才让类名执行这个内存空间
View Code

注:静态属性/静态字段/静态变量、动态属性/动态方法是存在类空间中的,不在对象空间中存,在对象空间中只从这些变量和方法的一个地址,需要时去类空间查找(即对象空间存的不是func的内存地址,而是存着func内存地址的一个变量的地址)

class A:
    name = "阿狸"
    def func(self):pass
a = A()
print(a.func)
print(A.func)
输出:(两个地址不一样)
<bound method A.func of <__main__.A object at 0x000001824E7B5EB8>>
<function A.func at 0x0000018257E2B048>

2)对象

①可以通过指针找到类的空间中的内容

②对象本身内部也存储了一些只属于对象的属性(只存属性,不存方法)

③类创造对象的过程就是实例化的过程:构造为__new__;初始化为__init__

3)组合:一个类的对象作为哦另一个类对象的属性(即什么有什么的关系,如人有武器)

4)继承:主要为了节省代码;继承表示什么是什么的关系

①单继承和多继承

#单继承:如果子类的对象调用调用某个方法,如果子类有,就调用子类的,如果子类没有,就调用父类的,如果还没有就报错;如果子类中有但是想要调用父类的,那么可以使用两种方法:

         super: 不用自己传self   格式:super(子类,self).方法名(除了self之外的参数)

         父类名:需要传self                格式:父类名.方法名(self,…)

注:在任何类中调用的方法,都要仔细分辨一下这个self到底是谁的对象

class Father:
    def __init__(self):       #self接受到的还是子类的对象,所以执行子类的func
        self.func()
    def func(self):
        print("In the Father...")
class Son(Father):
    def func(self):
        print("In the son...")
s = Son()
输出:
In the son...
View Code

#多继承

         新式类:默认继承object类,所以py3都是新式类;查找顺序遵循广度优先;可通过mro()方法查看继承顺序;在类的内部不用传子类名和self,如下super().func()

         经典类:没有继承object的类;查找顺序遵循深度优先;没有mro()方法;需要类名和self,如下super(子类名,self).func()

②抽象类和接口类的区别

         在python中接口类和接口类并没有特别大的区别,因为python是多继承的娥,而对于java而言,它是单继承的,只有抽象类中的方法,可以实现;但是接口中的所有方法只能写pass,接口支持多继承

抽象类:抽象类中的方法是可以实现的,但是只能单继承

接口类:可以单继承,但是这个类中的所有方法都不因该实现

         因为python是多继承的,所以怎么简单怎么写,没必要遵循上述要求

5)封装

①广义的封装:把方法和属性都封装在一个类型中,定义一个规范来描述一类事物

②狭义的封装:私有化,只能在类的内部进行访问;__静态变量、私有方法、私有对象属性、私有的类方法、私有的静态方法;

#在内存中通过【_类名__名字】形式存储;

#在内的内部使用,就知道自己在哪个类中,所有在类的内部可以使用双下划线访问

#在子类中不可以访问父类的私有变量;对于私有变量和方法,不能再类的外部使用也不能被继承

4)多态

①鸭子类型:所以带同一方法的类都可以叫做鸭子类型;规范全凭自觉

②python中处处是多态

#多态:一个类型的多种形态,如每个子类是父类的一种形态,一个父类被多个子类去继承,就是一种多态

5)property

①property是一个装饰器函数,通过property可以将类中的方法伪装成属性/特性

②通过property将方法伪装成属性,可以使得程序逻辑性更加合理

③修改被property装饰的属性会调用这个装饰器的方法【@方法名.setter】,除了self外海有另一个参数:被修改的值

④删除被property装饰的属性会调用这个装饰器装饰的的方法【@方法名.deleter】,本质上属性是不能够被删除的,因为对象无法操作类中的静态属性(只是执行类装饰器装饰的方法)

class A:
    @property
    def name(self):
        return "ali"
    @name.deleter
    def name(self):
        print("执行被@name.delerer装饰的方法")
a = A()
del a.name        #本质上对象不能删除静态属性,所以只是一个语法而已,没有实际意义
print(a.name)
输出:
执行被@name.delerer装饰的方法
ali
View Code

#可营造一个假象,高速用户把属性删除了(是个障眼法),这种方式比较偏其他语言,一般是将property和私有方法合用,这个时候更多的也会用到setter和deleter

class A:
    def __init__(self,name):
        self.__name = name

    @property
    def name(self):
        return self.__name
    @name.deleter
    def name(self):
        del self.__name
a = A("阿狸")
print(a.name)       #阿狸
del a.name        
print(a.name)  #AttributeError: 'A' object has no attribute '_A__name'
View Code

#只用property:一个方法的计算结果本身就是一个属性,但是这个属性会随着这个类/对象的基础变量的变化而变化,此时可以将这个方法设置为属性

class Circle:
    def __init__(self,r):
        self.r = r
    @property
    def area(self):
        return 3.14*self.r*self.r
a = Circle(5)
print(a.area)
输出:
78.5
View Code

#对于私有属性可以更改,但是可以对更改的值做约束,一是可以在类的内部再定义一个方法对私有属性进行更改,二是可以通过被装饰器【@方法名.setter】修饰的方法内部做约束

class A:
    def __init__(self,age):
        self.__age = age

    @property
    def name(self):
        return self.__age
    @name.deleter
    def name(self):
        del self.__age
    @name.setter
    def name(self,new_age):
        if type(new_age) is int:
            self.__age = new_age
a = A(18)
print(a.name)
a.name = 20    #调用方法给属性改值
print(a.name)
输出:
18
20
View Code

6)classmethod

〇classmethod类方法的装饰器(内置函数)

①类方法,使用类名调用,默认传类名作为第一个参数

②不用对象命名空间中的内容,而用到了类命名空间中的变量(静态属性),或者类方法和静态方法,这种场景就用类方法

#例:一个商场的购物程序(第一版,有问题)

class Goods:
    __discount = 0.8       #活动促销前,打折率
    def __init__(self,price):
        self.__price = price
    @property
    def price(self):
        return self.__price*self.__discount
    def change_discount(self,num):     #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性
        self.__discount = num
apple = Goods(10)
banana = Goods(8)
print(apple.price,banana.price)     #打折价格
apple.change_discount(1)   #改变折扣,只改变的是apple的,而banana的没有变化
print(apple.price,banana.price) #打折后的价格
输出:
8.0 6.4
10 6.4
View Code

#若要以整个类的折扣为准,此时可以将对象调用私有静态属性改为以类名调用

class Goods:
    __discount = 0.8       #活动促销前,打折率
    def __init__(self,price):
        self.__price = price
    @property
    def price(self):
        return self.__price*Goods.__discount
    def change_discount(self,num):     #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性
        Goods.__discount = num
apple = Goods(10)
banana = Goods(8)
print(apple.price,banana.price)     #打折价格
apple.change_discount(1)   #改变折扣,只改变的是apple的,而banana的没有变化
print(apple.price,banana.price) #打折后的价格
输出:
8.0 6.4
10 8
View Code

#对于以上程序,虽然实现了功能,但是恢复折扣时,是以apple对象调用的改变所有的商品的折扣,而这个对象也没有涉及到所有和apple对象操作相关的类,所以此时可以将change_discoun方法设置为静态方法,此时不需要任何实例化,就可以对商品中的所有商品进行折扣

class Goods:
    __discount = 0.8       #活动促销前,打折率
    def __init__(self,price):
        self.__price = price
    @property
    def price(self):
        return self.__price*Goods.__discount
    @classmethod
    def change_discount(cls,num):     #此处相当于给对象空间添加了一个__discount新属性,而不是改变了类中的__discount私有静态属性
        cls.__discount = num
apple = Goods(10)
banana = Goods(8)
print(apple.price,banana.price)     
Goods.change_discount(1)   #静态方法,通过类名调用
print(apple.price,banana.price) 
输出:
8.0 6.4
10 8
View Code

#对于一个学生管理系统,假如有school 类、学生、老师、课程、班级四个类,对于登录这个方法来说,只有学生类、老师类(两个类基础school类)才会调用,此时可以将登录的方法写入schoole类,并设置为静态方法

7)staticmethod

①如果一个类中的方法,既不需要用到self中的资源,也不用cls中的资源,相当于一个普通的函数,但是由于某种原因,还要把这个方法放到类中,这个时候,就可以将这个方法写成一个静态方法

②某种原因有:完全想用面向对象编程,所有的函数都必须写到类中;某个功能确实是这个类的方法,但是却没有用到和这个类有关系的任何资源

8)反射

①从某个指定的命名空间中,用字符串数据类型的变量名来获取变量的值

②反射的分类

         类名反射:静态属性、类方法、静态方法

         对象反射:对象属性、方法

         模块:反射模块中的方法

         自己模块:反射自己模块

③方法:hasattr()、getattr()、setattr()、delattr()

④使用:使用反射时,变量名必须是拿到一个字符串的版本,可以从文件中、用户交互、网络传输中获取到字符串

2.抽象类和接口类

1)作用:规范子类当作必须实现某个方法

2)抽象类和接口类不能被实例化

3)python中有原生的实现抽象类的方法,但是没有原生实现接口类的方法

4)抽象类和接口类的区别

         在python中接口类和接口类并没有特别大的区别,因为python是多继承的娥,而对于java而言,它是单继承的,只有抽象类中的方法,可以实现;但是接口中的所有方法只能写pass,接口支持多继承

①抽象类:抽象类中的方法是可以实现的,但是只能单继承

②接口类:可以单继承,但是这个类中的所有方法都不因该实现

         因为python是多继承的,所以怎么简单怎么写,没必要遵循上述要求

3.内置方法(魔术方法/双下方法)

1)格式:__名字__

2)一般由内置函数、面向对象的特殊语法、python提高的语法糖调用

 #所有的简单符号实现(加减乘除等),都对应了左右两个数据类型对应的一个方法,加:__add__     减:__sub__  乘:__mul__ 除:__div__

class Mytype:
    def __init__(self,s):
        self.s =s

    def __add__(self, other):
        return self.s.count('*') + other.s.count('*')
obj1 = Mytype("ajdio****dnao")
obj2 = Mytype("***sadi***")
print(obj1.__add__(obj2))
print(obj1+obj2)
输出:
10
10
View Code

3)

①__str__   str(obj),要求obj必须实现__str__,要求这个方法的返回值必须是字符串

②__call__        对象()       要求使用装饰器

③__len__   len(obj),要求obj必须实现了__len__方法;要求这个方法的返回值必须是数字

④__new__   在实例化过程中,最先执行的方法,在执行init之前,用来创造一个对象

⑤__init__        在实例化的过程中,在执行new方法之后,自动触发的一个初始化方法

4.__repr__方法

1)

引:对于str()方法,可以将数字转换为字符串,而对于repr()可以将字符串原形毕露

n = 123
n_new = str(n)
s ="123"
print(n_new)
print(type(n_new))
print(repr(s))
输出:
123
<class 'str'>
'123'
View Code

repr()方法的内部就是通过__repr__实现

补充:格式化输入%s和%r的区别:%r是带引号输出

print("----%s----" % ("abc"))
print("----%r----" % ("abc"))
输出:
----abc----
----'abc'----
View Code

2)__repr__对应得是repr(obj)这个方法和%r这个用法;__repr__是__str__的备胎,如果有__str__方法,那么print %s 、str()等操作都去执行__str__方法,并且使用__str__的返回值;如果没有__str__,那么print %s 、str()都会执行repr

class A:
    def __init__(self,name):
        self.name =name
    def __str__(self):
        return "____%s____" %self.name
    def __repr__(self):
        return "____%r____" %self.name
a = A("lol")
print(str(a),repr(a))
print("%s | %r" %(a,a))
输出:
____lol____ ____'lol'____
____lol____ | ____'lol'____
View Code

注:如果__str__和__repr__两个方法只能使用一个,首先考虑__repr__

3)在子类中使用__str__,先找子类的__str__,若子类没有就往上找(只要父类不是object,就执行父类的__str___),但是如果除了object之外的父类都没有__str__方法,就执行子类的__repr__方法,如果子类也没有repr方法,还要向上继续找父类的__repr-__方法,若一直找不到,再执行object类中的__str__方法

class A:
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return '**%s**'%self.name
    def __repr__(self):
        return self.name
#
class B(A):
    def __init__(self,name):
        self.name = name
    def __repr__(self):
        return '***'

a = B('lol')
print(a)
print(str(a),repr(a))
print('%s | %r'%(a,a))
输出:
**lol**
**lol** ***
**lol** | ***
View Code