抽象类、多态、绑定(classmethod)和非绑定方法(staticmethod)、反射的四种具体使用、魔法方法

【一】抽象类

【1】什么是抽象

  • 与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化

【2】抽象类方法

  • 所有继承父类的子类必须重写父类的某些方法,这个父类就叫抽象类
# 一切皆文件
# 利用abc模块实现抽象类
import abc

class Animal(metaclass=abc.ABCMeta):
    def __init__(self, color, foot, hand):
        self.color = color
        self.foot = foot
        self.hand = hand

    def speak(self):
        print(f'任何动物都能叫')

    @abc.abstractmethod
    # 在子类中必须重写父类的当前方法
    def walk(self):
        ...


class BlackBear(Animal):
    def __init__(self, color, foot, hand):
        super().__init__(color, foot, hand)

    # 如果不重写父类的方法就会报错
    # Can't instantiate abstract class BlackBear with abstract methods walk
    def walk(self):
        ...

bear = BlackBear('black',2,2)
print(bear.color) # black
bear.speak() # 任何动物都能叫

【3】示例

import os


class FileCheck(metaclass=abc.ABCMeta):
    def __init__(self):
        self.BASE_DIR = os.path.dirname(__file__)
        self.encoding = 'utf-8'

    @abc.abstractmethod
    def read_data(self):
        print(f'读取数据方法')
        ...

    @abc.abstractmethod
    def save_data(self):
        ...


# 文本文件处理类
class TextFileCheck(FileCheck):
    def __init__(self):
        super().__init__()
        self.file_path = os.path.join(self.BASE_DIR, 'data.text')

    def read_data(self):
        with open(file=self.file_path, mode='r', encoding=self.encoding) as fp:
            data = fp.read()
        return data

    def save_data(self):
        with open(file=self.file_path, mode='w', encoding=self.encoding) as fp:
            fp.write('真帅!')


obj_text = TextFileCheck()
print(obj_text.read_data()) # 真帅!
# 先进行 print(obj_text.save_data())操作
# 再进行读取


# json 文件处理

import json

class JsonFileCheck(FileCheck):
    def __init__(self):
        super().__init__()
        self.__ensure_ascii = False
        self.file_path = os.path.join(self.BASE_DIR, 'data.json')

    def read_data(self):
        with open(file=self.file_path, mode='r', encoding=self.encoding) as fp:
            data = json.load(fp=fp)
        return data

    def save_data(self):
        with open(file=self.file_path, mode='w', encoding=self.encoding) as fp:
            json.dump(obj={'username': "chosen"}, fp=fp, ensure_ascii=self.__ensure_ascii)


json_obj = JsonFileCheck()
print(json_obj.read_data()) # {'username': 'chosen'}
# 先进行 print(json_obj.save_data())操作
# 再读取

# 二进制数据处理类

【二】多态和多态性

【1】什么是多态

  • 多态指的是一类事物有多种形态

【2】示例

  • 比如动物有多种形态:猫、狗、猪
# 总的类动物类
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def talk(self):
        pass

# 具体的类:猫类
class Cat(Animal):
    def talk(self):
        print(f"喵喵喵...")

# 具体的类:狗类
class Dog(Animal):
    def talk(self):
        print(f'汪汪汪...')

【3】静态多态性和动态多态性

[0]多态性

  • 指事务的多种形态的性质
  • 在继承下会实现

[1]静态多态性

  • 运算 + 表达式

[2]动态多态性

# 【1】静态多态性
# 运算+表达式
# 【2】动态多态性

import abc

# 总的类动物类
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def talk(self):
        pass


# 具体的类:猫类
class Cat(Animal):
    def talk(self):
        print(f"喵喵喵...")


# 具体的类:狗类
class Dog(Animal):
    def talk(self):
        print(f'汪汪汪...')


class People(Animal):
    def talk(self):
        print(f"人会讲话!")

people = People()
cat = Cat()

talk(people) # 人会讲话!
talk(cat) # 喵喵喵...
        

# 更进一步,我们可以定义一个统一的接口来使用
def talk(obj):
    obj.talk()

【4】为什么要用多态性(多态性的好处)

  • 增加了程序的灵活性
    • 以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
  • 增加了程序额可扩展性
    • 通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
import abc

##同一类事物:动物
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def talk(self):
        pass


# 属于动物的另一种形态:猫
class Cat(Animal):
    def talk(self):
        print(f'喵喵喵...')


# 对于使用者来说,自己的代码根本无需改动
def func(animal):
    animal.talk()


# 实例出一只猫
cat1 = Cat()
# 甚至连调用方式也无需改变,就能调用猫的talk功能
func(cat1)
  • 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。
  • 使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)

[1]鸭子类型

  • 鸭子类型是一种编程风格,决定一个对象是否有正确的接口
    • 关注点在于它的方法或属性
    • 而不是它的类型(如果它看起来像鸭子,像鸭子一样嘎嘎叫,那么它一定是鸭子。)。
  • 通过强调接口而不是特定类型,设计良好的代码通过多态提高了灵活性。
    • 鸭子类型无需使用 type()isinstance() 进行检查(注意,鸭子类型可以用抽象基类来补充)
    • 相反,它通常使用 hasattr() 来检查,或是 EAFP 编程。
  • 但其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):
    • “如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。
    • 比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度
# 二者都像鸭子,因而就可以当鸭子一样去用
class NormalDuck():
    def eat(self):
        print(f"正常鸭子可以吃饭")

    def walk(self):
        print(f"正常鸭子可以走路")


class RockDuck():
    def eat(self):
        print(f"肉鸭子可以吃饭")

    def walk(self):
        print(f"肉鸭子可以走路")

【三】绑定方法和非绑定方法

  • 区别在于绑定给谁去使用

【1】绑定方法

[1]绑定到类的方法

  • 用classmethod装饰器装饰的方法。
  • 为类量身定制
  • 类.boud_method(),自动将类当作第一个参数传入
  • (其实对象也可调用,但仍将类当作第一个参数传入)
# 对象和类都可以任意调用的方法
class Student(object):
    def __init__(self, name):
        self.name = name

    # 【1】绑定给对象的方法
    # 对象可以直接调用 obj.talk()
    # 类调用需要传入生成的对象 clss.talk(obj)
    def talk(self):
        print(self)  # <__main__.Student object at 0x0000017EE9CDC640>
        print(f'{self.name} is talking')

    # 【2】绑定给类的方法
    @classmethod
    def read(cls, *args, **kwargs):
        '''

        :return: 调用当前方法的类
        '''
        # print(cls) # <class '__main__.Student'>
        obj = cls(*args, **kwargs)  # Student(*args,**kwargs)
        print(f'{obj.name} is reading')


stu = Student('chosen')
# print(Student) # <class '__main__.Student'>
# (1)对象调用绑定给类的方法, 默认将实例化得到当前对象的类自动传入
stu.read('chosen') # chosen is reading
# (2)类调用绑定给类的方法,类可以直接调用,默认将调用当前方法的类自动传入
Student.read('chosen') # chosen is reading

[2]绑定到对象的方法

  • 没有被任何装饰器装饰的方法
  • 为对象量身定制
  • 对象.boud_method(),自动将对象当作第一个参数传入
  • (属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
# 对象可以任意调用的方法
class Student(object):
    def __init__(self, name):
        self.name = name

    def talk(self):
        print(f'{self.name} is talking')

# (1)对象可以直接调用绑定给对象的方法
s = Student('chosen')
s.talk() # chosen is talking
# 默认将 s 作为 self 自动传入
# (2)类调用绑定给对象的方法,需要主动传入一个生成的对象
Student.talk(s) # chosen is talking

【2】非绑定方法

  • 用staticmethod装饰器装饰的方法
  • 不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。
  • 就是一个普通工具而已
import hashlib
import time


class MySQL:
    def __init__(self, host, port):
        self.id = self.create_id()
        self.host = host
        self.port = port

    @staticmethod
    # 就是一个普通工具
    def create_id():  
        m = hashlib.md5(str(time.time()).encode('utf-8'))
        return m.hexdigest()


# <function MySQL.create_id at 0x0000000001E6B9D8> 
# #查看结果为普通函数
print(MySQL.create_id)
conn = MySQL('127.0.0.1', 3306)

# <function MySQL.create_id at 0x00000000026FB9D8> 
# #查看结果为普通函数
print(conn.create_id)

【3】总结

# 【1】绑定方法
# (1)绑定给对象的方法
# 我们正常在函数内部定义的方法
# 特征就是自动补全self
# 对象调用直接调用(默认将当前的对象作为self默认参数传入) obj.func()
# 类调用需要传递额外的参数,额外的参数就是实例化得到的对象  class.func(obj)
'''
def func(self):
'''

# (2)绑定给类的方法
# 需要在类内部用 @classmethod 装饰的函数
# 特征就是自动补全cls 并且有 @classmethod 装饰
# 对象调用直接调用(默认将当前的对象的类作为cls默认参数传入) obj.func()
# 类调用直接调用,默认将当前类作为 cls 自动传入  class.func()
'''
@classmethod
def index(cls):
'''

# 【2】非绑定方法
# 需要在类内部用 @staticmethod 装饰的函数
# 特征不会自动补全任何参数
# 对象调用直接调用
# 类调用直接调用
'''
@staticmethod
def foo():
'''

【四】反射

【1】什么是反射

  • 反射是一种程序访问检测和修改本身属性和状态的方式
  • 在 python 内也有反射机制,通过字符串映射自己的内部是否具有某种属性

【2】在python中的四个方法

# 【1】获取属性
# getattr(obj,key)
# 【2】判断当前属性是否存在
# hasattr(obj,key)
# 【3】向当前对象中设置属性值和属性名
# setattr(obj,key,value)
# 【4】删除对象中的指定属性
# delattr(obj,key)

【3】具体使用

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def tell(self):
        print(f'{self.name} is {self.age} years old')

    @classmethod
    def talk(cls):
        print(cls.__name__)

    @staticmethod
    def swim():
        print(f'I am a swimming')


# 实例化得到一个对象
people = Person(name='chosen', age=18)

(1)getattr(obj,key)

# (1)数据属性
# 在对象中映射数据属性的时候,如果对象中存在当前属性值则直接将属性值拿出来
result = getattr(people,'name')
print(result) # chosen
result = getattr(people,'age')
print(result) # 18
# (2)函数属性
# 在对象中映射函数属性的时候,如果对象中存在当前属性名对应的数据属性
# 则直接获取当前函数属性的内存地址,可以直接调用当前函数
# result = getattr(people,'tell')
# print(result) # <bound method Person.tell of <__main__.Person object at 0x000001D755913E50>>
# result()
# (3)不存在
# 在对象中映射属性的时候,如果对象中不存在当前属性名对应的属性,会直接报错
result = getattr(people,'gender')
print(result) # AttributeError: 'Person' object has no attribute 'gender'

(2)hasattr(obj,key)

# (1)数据属性
# 在对象中映射数据属性的时候,如果对象中存在当前属性值则返回True
result = hasattr(people,'name')
print(result) # True
result = hasattr(people,'age')
print(result) # True
# (2)函数属性
# 在对象中映射函数属性的时候,如果对象中存在当前属性名对应的数据属性,则返回True
result = hasattr(people,'tell')
print(result) # True
# (3)不存在
# 在对象中映射属性的时候,如果对象中不存在当前属性名对应的属性,则返回False
result = hasattr(people,'gender')
print(result) # False

(3)setattr(obj,key,value)

# (1)数据属性
# 向对象中设置属性名和属性值,如果对象中存在当前属性则直接替换,否则新增
result = setattr(people, 'name', 'hope')
print(result) # None
result = setattr(people, 'gender', 'male')
print(result)  # None
print(people.name)  # hope
print(people.gender)  # male
# (2)函数属性
def read():
    print(f"这是外部的 read ")
# 在对象中映射函数属性的时候,如果对象中存在当前属性名对应的数据属性,则返回True
result = setattr(people, 'read', read)
print(result)  # None
print(hasattr(people, 'read'))  # True
print(getattr(people, 'read'))  # <function read at 0x00000139777D7B80>
getattr(people, 'read')()  # 这是外部的 read
print(people.tell)  # <bound method Person.tell of <__main__.Person object at 0x000001CDFDE8C640>>
print(people.talk)  # <bound method Person.talk of <class '__main__.Person'>>
print(people.swim)  # <function Person.swim at 0x000001997CB5C790>

(4)delattr(obj,key)

# (1)数据属性
# 在对象中删除数据属性的时候,如果对象中存在当前属性值则直接删除
print(hasattr(people,'name')) # True
result = delattr(people,'name')
print(result) # None
print(hasattr(people,'name')) # False
# (2)函数属性
# 在对象中删除函数属性的时候,要根据参数是对象还是类来做区分
print(hasattr(Person, 'tell'))  # True
# 如果参数是当前对象,则无法删除函数属性
result = delattr(people, 'tell')
result = delattr(people, 'talk')
result = delattr(people, 'swim')
# 如果参数是当前类,则可以删除函数属性
result = delattr(Person, 'tell') #
result = delattr(Person, 'talk')
result = delattr(Person, 'swim')
print(result)  # None
print(hasattr(Person, 'swim'))
# (3)不存在
# 在对象中删除属性的时候,如果对象中不存在当前属性名对应的属性,则直接报错
result = delattr(people,'gender')
print(result) #AttributeError: gender

【五】魔法方法(内置方法)

  • Python的Class机制内置了很多特殊的方法来帮助使用者高度定制自己的类
  • 这些内置方法都是以双下划线开头和结尾的,会在满足某种条件时自动触发
__init__	:初始化类时触发
__del__		:销毁类时触发
__new__		:构造类时触发
__str__		:str函数或者print函数触发
__repr__	:repr或者交互式解释器触发
__doc__		:打印类内的注释内容
__enter__	:打开文档触发
__exit__	:关闭文档触发
__getattr__ : 访问不存在的属性时调用
__setattr__ :设置实例对象的一个新的属性时调用
__delattr__ :删除一个实例对象的属性时调用
__setitem__	:列表添加值
__getitem__ :将对象当作list使用 
__delitem__	:列表删除值
__call__	:对象后面加括号,触发执行
__iter__	:迭代器
  • 在类内部达到指定条件会自动触发的方法
  • __ init __ :实例化类得到对象的时候会自动触发
class Student(object):
    # 【1】__init__ : 实例化类得到对象的时候会自动触发
    def __init__(self, name):
        print(f"实例化类的时候触发")
        self.name = name
        # self.fp = open('./data.text', 'r', encoding='utf-8')
        # print(f"当前文件对象已打开 :>>>> {self.fp.closed}")
        print('-------')

    # 【2】__del__ : 当对象/对象关闭销毁的时候自动触发
    # 场景:打开文件 open --> close
    def __del__(self):
        # self.fp.close()
        # print(f"当前文件对象已关闭 :>>>> {self.fp.closed}")
        print(f"当前在销毁的时候触发")
        print('-------')

    # 【3】__str__ : 在打印当前对象的时候可以定制打印当前对象的显示内容
    # 必须且只能返回字符串类型的数据
    def __str__(self):
        print(f'打印当前对象的时候会触发')
        print('-------')
        return self.name

    # 【4】__repr__ : 交互解释器会触发
    def __repr__(self):
        print(f"与解释器交互的时候会触发")
        print('-------')
        return self.name

    # 【5】__doc__ : 打印类里面的注释内容的时候会触发
    # 对象.__doc__
    # 类.__doc__
    __doc__ = "这是一个学生类"

    # 【6】__enter__  : 打开文档的时候会触发 with 语句触发
    def __enter__(self):
        print(f"打开文档的时候会触发")
        print('-------')
        return self

    # 【7】__exit__ : 关闭文档的时候会触发 with 语句触发
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"关闭文档的时候会触发")
        print('-------')
        ...

    # 【8】__getattr__ : 获取当前对象的不存在的属性的时候触发
    # __getattribute__ : 值不存在的时候会触发异常
    def __getattr__(self, item):
        print(f'当前对象属性不存在的时候会触发')
        print(item)  # 就是不存在的属性的变量名
        # 父类里面有一个 __getattribute__ 能主动抛出值不存在的异常
        super().__getattribute__(item)
        print('-------')

    # 【9】__setattr__ : 设置对象的属性值的时候会触发(包括 __init__ 初始化属性)
    def __setattr__(self, key, value):
        print(f'设置当前对象属性的时候会触发 对象.key=value')
        print(f"key :>>>>> {key}")
        print(f"value :>>>>> {value}")
        print('-------')

    # 【10】__setattr__ : 删除对象的属性值的时候会触发
    def __delattr__(self, item):
        print(f'当前在删除属性的时候会触发')
        print(item)
        print('-------')

    # 【11】__setitem__ : 对象[key]=value 设置值的时候会触发
    def __setitem__(self, key, value):
        print(f'设置属性的时候会触发 设置方式为 对象[key]=value')
        print(key, value)
        print('-------')
        # 用自己的名称空间字典放
        self.__dict__[key] = value

    # 【12】__getitem__ : 获取属性的时候会触发 设置方式为 对象[key]
    def __getitem__(self, item):
        print(f'获取属性的时候会触发 设置方式为 对象[key]')
        print('-------')
        return self.__dict__[item]

    # 【13】__delitem__ : 删除属性的时候会触发 删除方式为 del 对象[key]
    def __delitem__(self, key):
        print(f'删除属性的时候会触发 删除方式为 del 对象[key]')
        print(key)
        print('-------')
posted @ 2024-05-20 08:45  光头大炮  阅读(6)  评论(0编辑  收藏  举报