Python基础之面向对象

一、编程思想

1、面向过程

​ 把完成某一个需求的 所有步骤,从头到尾,逐步实现,根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数,然后按照顺序调用不同的函数

  • 过程 是早期的一个编程概念
  • 过程 只能执行,没有返回值
  • 函数 不仅能执行,还可以返回结果

2、面向对象

​ 相比较函数,面向对象更大封装,根据职责在一个对象(类)中封装多个方法

  • 在完成某一个需求前,首先确定职责—— 要做的事情(方法
  • 根据职责确定不同的对象,在 对象内部封装不同的方法(多个
  • 最后完成的代码根据需求实例化对象然后在不同的地方调用多次

二、类与对象的创建

1、类的语法结构

class 类名:
	'''代码注释'''
	对象公共的数据
	对象公共的功能

1.class是定义类的关键字
2.类名几乎和变量名一致,使用的时候推荐类名的首字母大写
3.数据:
	变量名与数据值的绑定
  功能:
	功能(方法)其实就是具有一定功能的函数

2、定义与调用

类的定义:

class Student: 
    # 对象公共的数据
    school_name = '老男孩'
    
    # 对象公共的功能
    def python(self):pass
        
    def linux(self):pass

类的调用:

1、调用对象
    obj1 = Student()  
    # <__main__.Student object at 0x000001992C2759D0> 
    obj2 = Student()
    # <__main__.Student object at 0x000001992C278E50>
    obj3 = Student()
    # <__main__.Student object at 0x000001992C278E20>

2、查看对象内部数据
	obj1.__dict__ 
    
3、调用对象共同数据
    print(obj1.school_name)

3、给对象添加独有属性

class Student:
    # 对象公用的数据
    school_name = '老男孩'

    # 专门给学生添加独有数据的功能
    def __init__(self, name, age, hobby):
        self.name = name
        self.age = age
        self.hobby = hobby

stu1 = Student('kangkang', 18, 'python')

4、对象独有属性修改

# 只需要使用对象‘点’的数据绑定的变量名的方式重新赋值即可
stu1.name = 'zhaozhao'

# 当名字不存在的情况下,将直接新增数据
stu1.paw = 123

三、动态、静态方法

简介:

​ 动静态方法是指定义在类体代码种的功能(函数)具有多种特性

1、直接在类中定义的函数

​ 在类中直接定义函数,默认绑定给对象,类调用时有几个参数就要传几个参数,对象调用时该函数的第一个参数默认为对象

# 定义一个类
class Student:

    # 类公用方法(函数、功能)
    def curricula_variable(self):
        print(f'{self.name}正在选课')
        
# 类调用(需要先产生对象,然后将产生的对象当作参数传入)
stu1 = Student()
Student.curricula_variable(stu1) 

# 对象调用(直接用对象点方法名字加括号,不需要额外进行传参)
stu1.curricula_variable()

2、绑定给类的函数

​ 被@classmenthod修饰的函数,默认绑定给类,类调用第一个参数就是类自身,对象也可以调用,应且会自动将产生该对象的类当作第一个参数传入

# 定义一个类
class Student:

    @classmethod
    def func(cls):  # 绑定给类的函数,第一位形参为cls
        print('我是绑定给类的功能', cls)
        
        
# 类可直接调用
Student.func()

# 对象调用
stu1 = Student('kangkang', 18, 'mela')
stu1.func()

3、被修饰的普通函数

​ 被@staticmethod修饰的函数,是普通的函数,此函数无论是类还是对象调用,都必须手动传参

# 定义一个类
class Student:

    @staticmethod
    def func2(*args, **kwargs):
        print(我是被【staticmethod】被修饰的普通函数,不管谁调用我都需要手动传参' )

# 类直接调用
Student.func2('普通函数')
              
# 对象调用
stu1 = Student('kangkang', 18, 'mela')
stu1.func2('aaa','name=bbb')           

四、面向对象三大特征

1、继承

继承的概念:

​ 在编程的世界中,继承表示类与类之间的资源从属关系

例如:类a继承类b

继承的本质:

​ 继承本质应该分为两部分

​ 当多个类中含有相同部分时,我们就把相同的部分抽离出去,将相同的部分新成一个新的类,在用刚才被抽离出去相同部分的类,去继承新的那部分,这样的好处就是,当多个类需要一个相同用的功能时,可以节省代码,不用反复编写

抽象:将多个类相同的东西抽出去,形成一个新的类

继承:将多个类继承刚刚抽取出来的新的类

如何继承:

  • 在定义类的时候,类名后方可加括号,早括号内填入其他它类的名字,就意味着继承了其它类
  • python支持多类继承,括号内可填写多个类名,只需用逗号隔开即可
# 1、父类中的数据,子类无权直接使用,当我们在子类中继承父类后,子类和子类产生的对象都可以通过'点'的方式使用父类中的数据
class Father:
    money = 99999999
    house = '老北京四合院'


class Son(Father):
    pass


# 子类产生的对象使用父类中的数据
son_obj = Son()
print(son_obj.money)
-----------------------------------------------------------------------------
99999999

继承后名字的查找顺序:

  • 未继承

    • 对象(object)自身名称空间>>>类的名称空间
  • 单线继承

    • 对象(object)自身名称空间>>>子类的名称空间>>>父类的名称空间
  • 多线继承

    • 1、菱形继承: (广度优先,最后才会找闭环的定点)
    • 2、非菱形继承: (深度优先)

派生方法:

​ 派生是指,子类继承父类,派生出自己的属性与方法,并且重用父类的属性与方法

class People:
    school = '清华大学'
    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


class Teacher(People,XXX):
    def __init__(self, name, sex, age, title):
        super().__init__(name, age, sex)
        #调用的是绑定方法,自动传入self
        self.title = title
	 def teach(self):
         print(f'{self.name}是老师')
            
            
obj = Teacher('lili','female', 28, '高级教师') 

2、封装

封装的概念:

​ 指将数据和功能封装起来,封装是一种抽象的概念,封装也就是将某部分数据隐藏起来

封装的两个层面:

  • 第一个层面:
    • 我们创建的类、函数体,本身也是一种封装,想要调用类、函数内的功能和数据,就需要通过类、函数名的方式
    • 对于这一层面的封装,类名,函数名就是访问示例的接口
  • 第二层面:
    • 把类种的功能和数据隐藏起来(可以定义成私有的),无法通过类名的方式直接调用,只能在类中定义一些接口,通过接口的方式访问或修改

隐藏与调用:

隐藏:

​ Python的class机制采用采用双下划线开头的方式将属性隐藏起来(设置成私有),但这仅仅只是一种变形操作,类中所有双下划线开头的属性都会在定义阶段,检测语法时自动变成'_ 类名 __ 属性名'的形式

class MyClass:
    school_name = '老女孩大学'
    # 类在定义阶段 名字前面有两个下划线 那么该名字会被隐藏起来 无法直接访
   	 __age = 18

    def __choice_course(self):
        print('老北鼻正在选课')

    
'''
1、加了双下划线的属性,将无法通过对象、类点的方式调用
2、隐藏属性只能在定义阶段使用,后期无法通过添加的方式隐藏
3、可用通过 对象.__dict__ 的方法看到对象种所有属性和方法(包括隐藏的)
'''

调用:

​ 在类中的函数体代码时可以直接使用被隐藏的属性的,只需要在类中定义一个函数体代码,在外界调用这个函数体代码时就可以查看被隐藏属性`

class Person:
    
    def __init__(self, name, age, hobby):
        self.__name = name  # 对象也可以拥有隐藏的属性
        self.__age = age
        self.__hobby = hobby

    def get_info(self):
        print(self.__A)
        # 类体代码中 是可以直接使用隐藏的名字
        print(f"""
        姓名:{self.__name}
        年龄:{self.__age}
        爱好:{self.__hobby}
        """)
    
    # 隐藏的属性开放修改的接口 可以自定义很多功能
    def set_name(self, new_name):
        if len(new_name) == 0:
            raise ValueError('你好歹写点东西')
        if new_name.isdigit():
            raise ValueError('名字不能是数字')
        self.__name = new_name
 

obj = Person('kangkang',18,'python')
obj.get_info()

伪装:

  • 简介:

    • 是指将类中的功能伪装成数据
  • 作用:

    • 将函数伪装成功能后,调用函数时不需要加括号就可调用(该函数不可设置参数)
  • 伪装的实际应用:

BMI指数:衡量一个人的体重与身高对健康影响的一个指标
    体质指数(BMI)=体重(kg)÷身高^2(m)
	 EX:70kg÷(1.75×1.75)=22.86

class Person(object):
    def __init__(self, name, height, weight):
        self.name = name
        self.height = height
        self.weight = weight
    @property
    def BMI(self):
        return self.weight / (self.height ** 2)


p1 = Person('jason', 1.83, 78)
p1.BMI  # BMI应该作为人的基本数据而不是方法
# print(p1.BMI)  # 利用装饰器伪装成数据



class Foo:
    def __init__(self, val):
        self.__NAME = val  # 将属性隐藏起来

    @property
    def name(self):
        return self.__NAME

    @name.setter
    def name(self, value):
        if not isinstance(value, str):  # 在设定值之前进行类型检查
            raise TypeError('%s must be str' % value)
        self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise PermissionError('Can not delete')


f = Foo('jason')
print(f.name)
f.name = 'jason123'
print(f.name)
del f.name
# f.name = 'jason'  # 触发name.setter装饰器对应的函数name(f,’jason')
# f.name = 123  # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
# del f.name  # 触发name.deleter对应的函数name(f),抛出异常PermissionError

3、多态

多态的概念:

​ 多态在实际应用时较为抽象,指事物的多种形态,而相同的类,就应该具有这个类的基本功能

​ 就比如说,人作为一个种类,每一个人就相当于对象,在每个对象当中,我们都会走路、说话、吃饭,而每当我们听到这些名词时就会自然的联想到相应的动作,和作用。这就是多态的一种表现形式。

​ 也就是说,当我们在定义同一个父类中的多个子类时,就应该把每个子类中形态相同,而代码不同的函数名同一定义为同一种叫法

"""
鸭子类型:
只要你看上去像鸭子 走路像鸭子 说话像鸭子 那么你就是鸭子
"""

多态的的实际应用:

# 1、定义一个类
class Animal(self):
    def spark(self):
        '''叫的方法'''
        pass


class Cat(Animal):
    def spark(self):
        print('喵喵喵')


class Dog(Animal):
    def spark(self):
        print('汪汪汪')


class Pig(Animal):
    def spark(self):
        print('哼哼哼')

abc模块:

import abc


# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod  
    # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self):  
        # 抽象方法中无需实现具体的功能
        pass


class Cat(Animal):  
    # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):
        pass


cat = Cat()  
# 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化

五、反射

简介:

​ 主要是应用于类产生的对象上,在运行时,将对象中的属性和方法反射出来

1、使用场景

​ 可以动态的向对象中添加属性和方法。也可以动态的调用对象中的方法或者属性。

2、常用方法

hasattr()


srt = input('xxxx')
hasattr(obj, str)
'''
判断输入的str字符串在对象obj中是否存在(属性或方法),存在返回True,否则返回False	
'''

getattr()

getattr(obj, str)
'''
将按照输入的str字符串在对象obj中查找。
如找到同名属性,则返回该   属性 , 函数 
如找到同名方法,则返回方法的引用
想要调用此方法得使用 getattr(obj,str)()进行调用。
如果未能找到同名的属性或者方法,则抛出异常:AttributeError。
'''

callable()

setattr()

setattr(obj,name,value)
'''
根据字符串,给对象设置/修改数据
'''

delattr()

delattr(obj, str)
'''
根据字符串,删除对象里的名字
'''

3、通过反射实现增删改查

class User_cls_info():
    def __init__(self, name, age, gender, hobby, ):
        self.__name = name
        self.__age = age
        self.__gander = gender
        self.__hobby = hobby

    # 查看
    def check_info(self):
        for i in self.__dict__:
            v = getattr(self, i)
            i_1 = i.split('__')[1]
            print(f'数据名:{i_1},数据值:{v}')

    # 修改
    def alter_info(self):
        alter_k = input('请输入您需要修改的数据名>>>:').strip()
        alter_v = input('请输入修改后的数据值>>>:').strip()

        alter_k_ = f'_User_cls_info__{alter_k}'
        if alter_k_ not in self.__dict__:
            print('该数据名不存在')
            return

        if len(alter_v) <= 0:
            print('数据值不得为空')
            return

        setattr(self, alter_k_, alter_v)
        print(f'修改成功,{alter_k},已被修改为{alter_v}')

    # 增加
    def add_info(self):
        add_k = input('请输入您需要增加的数据名>>>:').strip()
        add_v = input('请输入对应的数据值>>>:').strip()
        if len(add_k) <= 0 or len(add_v) <= 0:
            print('数据值或数据名不得为空')
            return
        add_k_ = f'_User_cls_info__{add_k}'
        setattr(self, add_k_, add_v)
        print(f'已成功添加,数据名:{add_k},数据值:{add_v}')

    # 删除
    def del_info(self):
        del_k = input('请输入您需要删除的数据>>>:').strip()
        del_k = f'_User_cls_info__{del_k}'
        if del_k not in self.__dict__:
            print('该数据名不存在')
            return
        delattr(self, del_k)
        print(f'数据{del_k}已删除')

    def run(self):
        while True:
            print('''
            1.查看用户信息>>>:check_info
            2.修改用户信息>>>:alter_info
            3.增加用户信息>>>:add_info
            4.删除用户信息>>>:del_info
            ''')
            user_choice = input('请输入您需要执行的功能>>>:').strip()
            if hasattr(self, user_choice):
                getattr(self, user_choice)()
            else:
                print('该功能不存在')


user = User_cls_info('kangkang', 18, 'male', 'read')
user.run()

六、面向对象之魔法方法

1、简介

定义在类中的双下方法都可以称为魔法方法

不需要人为调用,在特定的条件下会自动触发,并运行

类似于__ init__, 当我在使用类产生对象时,会自动触发

2、常用魔法方法

__ init __

__init__(self):
    self: 调用者本身

'''
对象添加独有数据时,会自动触发
'''

__ str __

__str__(self):
    self: 调用者本身
    
'''
1、对象被执行打印操作时会自动触发
2、该方法下必须使用return设置返回值,且返回值必须为字符串,否则报错
'''

__ call __

__call__(self, *args, **kwargs):
    self: 调用者本身
    args:位置形参
    kwargs:关键字新参
    
'''
1、对象加括号调用时会自动触发
'''

__ getattr __

__getattr__(self, item):
    self: 调用者本身
    item:调用者点的名字
    
'''
1、对象点不存在的名字时会自动触发该函数体代码
2、当同一类中同时出现__getattribute__方法时,系统默认使用__getattribute__
'''

__ getattribute __

__getattribute__(self, item):
    self: 调用者本身
    item:调用者点的名字
    
'''
1、对象点名字时会自动触发该函数体代码
2、该方法形参内item为对象点出的名字,一旦该方法触发,就无法获取到对象点名字的值
'''

__ enter __

__enter__(self):  # self: 调用者本身
    retrun  self  # 返回什么f就是什么 
   


with obj1 as f:  # f = slef
	pass  

__ exit __

__exit__(self, exc_type, exc_val, exc_tb):
    self: 调用者本身
    
    
'''
1、with上下文管理运行完毕之后会自动触发(子代码体结束)
'''

__ bool __

__bool__(self):
    	self: 调用者本身
        
        return False


'''
1、当调用 bool(obj) 时,会调用 __bool__() 方法,返回 True 或 False
'''

__ new __

__new__(cls, *args, **kwargs):
        print("__new__()方法被调用了")
        print('这个是*agrs', *args)
        print('这个是kwagrs', **kwargs)
        
'''
1、__new__(cls, [...]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。

2、它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法。

3、__new__ 决定是否要使用该 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __new__ 没有返回实例对象,则 __init__ 不会被调用。

4、__new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string。

'''

__ del __

__del__(self):
    self: 调用者本身
    
'''
1、del__ 是析构方法,当对象的引用计数变为0时,这个对象会被销毁掉,此时就会调用__del__方法


'''

3、魔法方法笔试题

补全下列代码,使运行不会报错

class Context:
    pass

with Context() as f:
    f.do_something()
    

'''
推导思维:
	1、类加括号产生对象
	2、当对象被with传入上下文管理使,就要用到__enter__方法
	3、__enter__方法返回值为f,使用该方法返回对象本身(self)
	4、对象加点的方式调用功能
	5、将该功能补全至类体代码中
	6、子代码结束后,调用__exit__关闭with
'''

class Context:
    def do_something(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass


with Context() as f:
    f.do_something()

自定义字典类型并让字典能够通过句点符的方式操作键值对

'''
推导思维:
	1、使用类继承dict方法
	2、使用__setattr__方法,将键值对以索引赋值的方式传给self
	3、使用__getattr__的方法,当对象点名字时,使用return的方式将值传出
	4、上述两种方式分别可以实现以点的方式添加数值和取到数值
'''
	class MyDict(dict):
        def __setattr__(self, key, value):
            self[key] = value

        def __getattr__(self, item):
            return self.get(item)
        obj = MyDict()
        obj.name = 'jason'
        obj.pwd = 18
        obj.hobby = 'read'
        # print(obj)
        print(obj.name)
        print(obj.pwd)
        print(obj.hobby)
        # print(obj)
        # print(obj)  # 字典存储的数据  {}
        # print(obj.__dict__)  # 字典对象名称空间  {'name': 'jason'}

        print(type(obj))