date: 2021-06-25title: Python:类的相关概念 #标题
tags: #标签
categories: python # 分类

记录下python中类的语法结构,包括父类、子类的关系及其他概念….

参考:老男孩教育

类的基础语法结构

  1. '''
  2. 子类想要调用父类的方法的同时还想执行自己的同名方法
  3. 猫和狗在调用eat的时候既调用自己的也调用父类的
  4. 在子类的方法中调用父类的方法:父类名.方法名(self)
  5. '''
  6. # 定义一个父类
  7. class Animal:
  8. def __init__(self, name, food):
  9. self.name = name
  10. self.food = food
  11. self.blood = 100
  12. self.waise = 100
  13. def eat(self):
  14. print(f'{self.name} is eating {self.food}...')
  15. def drink(self):
  16. print(f'{self.name} is drinking...')
  17. def sleep(self):
  18. print(f'{self.name} is sleeping...')
  19. # 定义第一个子类(类名后面加括号,并且传入父类的名称即可)
  20. class Cat(Animal):
  21. def __init__(self, name, food, eye_color): # 初始化变量时,调用父类的 init 方法,自己也有init
  22. Animal.__init__(name, food) # 调用了父类的初始化,去完成一些通用属性的初始化
  23. self.eye_color = eye_color # 派生属性
  24. def eat(self): # 假设猫吃了猫粮,血+100
  25. self.blood += 100
  26. Animal.eat(self) # 调用父类中的同名方法
  27. self.sleep() # 调用父类中的方法
  28. def climb_tree(self):
  29. print(f'{self.name} is climb_tree...')
  30. # 定义第二个子类
  31. class Dog(Animal):
  32. def eat(self):
  33. self.waise += 50 # 狗吃了狗粮,智商+50
  34. Animal.eat(self)
  35. print(self.waise)
  36. def kanjia(self):
  37. print(f'{self.name} is kanjia...')
  38. # 实例化对象
  39. xiaogou = Dog('小黑', '狗粮')
  40. xiaomao = Cat('小猫', '猫粮')
  41. xiaogou.eat()
  42. xiaomao.eat()
  43. print(xiaomao.blood)
  44. '''
  45. 父类和子类方法的选择:
  46. 子类的对象如果去调用方法,永远优先调用自己的
  47. 如果自己有,就用自己的,如果没有,用父类的
  48. 如果自己有,还想用父类的,那么直接在子类方法中调父类的方法:父类名.方法名(slef)
  49. '''

新式类和经典类

在Python 2及以前的版本中,由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”,都会获得所有“新式类”的特性;反之,即不由任意内置类型派生出的类,则称之为“经典类”。

“新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x之后的版本,因为所有的类都派生自内置类型object(即使没有显示的继承object类型),即所有的类都是“新式类”。

参考:官方文档。

继承顺序的区别

参考:Python中经典类和新式类的区别。

抽象类

所谓抽象类,就是一个开发规范,约束它的所有子类必须实现一些和它同名的方法。

我们一起看下面这个例子,原本代码中有支付宝和微信两种支付方式,并且支付的方法名都叫pay。但如果让你一个新同事再写一个苹果支付的方法,他没有给方法定义名称为pay,这样在后续调用会有不必要的麻烦,此时,我们就可以借助抽象类,来约定一些类中必须有哪些方法。

自定义抽象类
  1. # 定义一个抽象类Payment(这个抽象类什么也不做,就是来定义开发规范的,
  2. # 不过我为了简化代码,将name的接收值也放到了这里。)
  3. class Payment:
  4. def __init__(self, name):
  5. self.name = name
  6. def pay(self, money):
  7. raise NotImplementedError('请在子类中重写同名pay方法')
  8. # 接下来的每个支付类都要继承上面的抽象类Payment
  9. class WeChatpay(Payment):
  10. def pay(self, money):
  11. l1 = {'uname': self.name, 'price': money}
  12. print(f"用户{l1['uname']}通过微信支付{l1['price']}成功...")
  13. class Alipay(Payment):
  14. def pay(self, money):
  15. l1 = {'uname': self.name, 'price': money}
  16. print(f"用户{l1['uname']}通过支付宝支付{l1['price']}成功...")
  17. class Applepay(Payment):
  18. def pay(self, money): # 如果将这个方法定义名称不是pay,那么就会报上面抽象类返回的错误
  19. l1 = {'uname': self.name, 'price': money}
  20. print(f"用户{l1['uname']}通过苹果支付{l1['price']}成功...")
  21. # 其原理是,在这里找不到pay方法,就会去父类中找
  22. def pay(name, price, kind):
  23. if kind == 'Wechatpay':
  24. obj = WeChatpay(name)
  25. elif kind == 'Alipay':
  26. obj = Alipay(name)
  27. elif kind == 'Applepay':
  28. obj = Applepay(name)
  29. obj.pay(price)
  30. pay('lv', 500, 'Alipay')
  31. pay('lv', 500, 'Wechatpay')
  32. pay('lv', 500, 'Applepay')
  33. # 将上面的代码通过反射来实现,可以更加简洁
  34. import sys
  35. class Payment:
  36. def __init__(self, name):
  37. self.name = name
  38. def pay(self, money):
  39. raise NotImplementedError('请在子类中重写同名pay方法')
  40. # 接下来的每个支付类都要继承上面的抽象类Payment
  41. class WeChatpay(Payment):
  42. def pay(self, money):
  43. l1 = {'uname': self.name, 'price': money}
  44. print(f"用户{l1['uname']}通过微信支付{l1['price']}成功...")
  45. class Alipay(Payment):
  46. def pay(self, money):
  47. l1 = {'uname': self.name, 'price': money}
  48. print(f"用户{l1['uname']}通过支付宝支付{l1['price']}成功...")
  49. class Applepay(Payment):
  50. def pay(self, money): # 如果将这个方法定义名称不是pay,那么就会报上面抽象类返回的错误
  51. l1 = {'uname': self.name, 'price': money}
  52. print(f"用户{l1['uname']}通过苹果支付{l1['price']}成功...")
  53. # 其原理是,在这里找不到pay方法,就会去父类中找
  54. def pay(name, price, kind):
  55. this_modules = sys.modules['__main__']
  56. if hasattr(this_modules, kind): # 判断模块中是否存在此属性或方法
  57. obj = getattr(this_modules, kind)(name) # 获取kind对应的值,然后实例化对象
  58. obj.pay(price)
  59. pay('吕建钊', 500, 'Alipay')
  60. pay('吕建钊', 500, 'WeChatpay')
  61. pay('吕建钊', 500, 'Applepay')

python内置的抽象类

在上面的自定义抽象类中,还可以使用另一种方式来实现,就是通过python内置的模块来实现。

# 导入模块
from abc import ABCMeta, abstractmethod


# 定义metaclass
class Payment(metaclass=ABCMeta):
    def __init__(self, name):
        self.name = name

    @abstractmethod  # 给需要规范的模块添加此装饰器
    def pay(self, money):
        raise NotImplementedError('请在子类中重写同名pay方法')


class WeChatpay(Payment):
    def pay1(self, money):
        l1 = {'uname': self.name, 'price': money}
        print(f"用户{l1['uname']}通过微信支付{l1['price']}成功...")


# 会发现如果类中没有pay方法,连实例化都做不了,更不用说调用方法了
WeChatpay('alex')



# 报错如下:
Traceback (most recent call last):
  File "F:/software/llvjianzhao_python/python_demo/demo_01.py", line 21, in <module>
    WeChatpay('alex')
TypeError: Can't instantiate abstract class WeChatpay with abstract methods pay

类中的super方法

class User:
    def __init__(self, name):
        self.name = name


# 在单继承的程序中,super就是找父类的方法
class VIP_User(User):  # 如果要使用super方法,就必须要使用继承
    def __init__(self, name, level, start_date, end_date):
        # User.__init__(self,name)     # 一般不用这种方式
        # super().__init__(name)    # 如果只在Python3中运行,推荐这种写法
        super(VIP_User, self).__init__(name)  # 如果还要在python2中运行,则必须使用这种方法
        self.level = level
        self.start_date = start_date
        self.end_date = end_date


user = VIP_User('吕建钊', 6, '2020-01-01', '2022-01-01')
print(user.__dict__)

类的封装

'''
封装就是将我们不希望暴露到外部的内容进行私有化,
所有的私有化都是为了让用户不能在外部调用类中的某个名字
一般我们很少去定义一些私有化的方法,可能会在源码中看到一些
'''

import hashlib


class User:
    __default_name = '张三'  # 私有的静态变量

    def __init__(self, name, pwd):
        self.name = name
        self.__pwd = pwd  # 私有的实例变量

    def __get_md5(self):  # 私有的绑定方法
        md5 = hashlib.md5(self.name.encode('utf-8'))
        md5.update(self.__pwd.encode('utf-8'))
        return md5.hexdigest()

    def get_pwd(self):
        print(self.__default_name)
        return self.__get_md5()


user = User('lv', '123.com')
# print(user.__default_name)    # 无法直接调用私有的变量
print(user.get_pwd())
print(user.__dict__)  # 输出:{'name': 'lv', '_User__pwd': '123.com'}
# 可以看到Python将私有变量名__pwd改成了_User__pwd(user为类名)
# 根据规律,我们可以通过如下方式调用私有变量
print(user._User__default_name)
print(user._User__pwd)

property将方法伪装成一个属性

原代码

# 看下面的代码,是一个求面积的类

from math import pi


class A:
    def __init__(self, r):
        self.r = r  # 定义半径

    def area(self):  # 求面积
        return pi * self.r ** 2


c1 = A(5)
print(c1.r)  # 打印半径
print(c1.area())  # 打印面积

# 在我们的认知中,这个圆形的半径或面积,都应该是一个属性
# 而登陆或注册等功能,才是方法
# 所有最好可以将求面积这个功能伪装成一个属性,而不是方法

将方法伪装成属性的代码

from math import pi


class A:
    def __init__(self, r):
        self.r = r  # 定义半径
    @property     # 把一个方法伪装成属性,在调用这个方法的时候,不需要加()就可以直接调用
    def area(self):  # 求面积
        return pi * self.r ** 2


c1 = A(5)
print(c1.r)  # 打印半径
print(c1.area)  # 打印面积,可以不在方法名后加括号,像调用属性一样调用方法