Python基础学习笔记(25)super方法 封装 property装饰器 反射

Python基础学习(25)super方法 封装 property装饰器 反射

一、今日内容大纲

  • super 方法(继承内容补充)
  • 封装
  • property 装饰器
  • 反射

二、super 方法

super 方法会按照 MRO(Method Resolusion Order) 顺序来寻找当前类的下一个类,因为经典类不具有 mro 方法,所以经典类不支持 super 方法。由于单继承情况的 MRO 就是从父至子依次排序,所以在单继承时 super 方法在单继承情况下就是寻找父类。

# super方法
class A:
    def func(self):
        print('A')
class B(A):
    def func(self):
        super().func()
        print('B')
class C(B):
    def func(self):
        super().func()
        print('C')


C().func()  # A B C

class A:
    def func(self):
        print('A')
class B(A):
    def func(self):
        super().func()
        print('B')
class C(A):
    def func(self):
        super().func()
        print('C')
class D(B, C):
    def func(self):
        super(C, C()).func()
        print('D')

D().func()  # A B C D

# 日常使用情况
class User:
    def __init__(self, name):
        self.name = name

class VIPUser(User):
    def __init__(self, name, level):
        # User.__init__(self, name)  # 这样的话可以不用继承User类
        # super().__init__(name)
        super(VIPUser, self).__init__(name)
        self.level = level

tiabai = VIPUser('太白',6)
print(tiabai.__dict__)  # {'name': '太白', 'level': 6}

三、封装

封装,其实就是把属性或者方法装起来;可以细分为广义封装和狭义封装:

  • 广义:把属性和方法装起来,外面就不能直接调用了,要通过类的名字来调用(类其实就属于广义上的封装);
  • 狭义:把属性和方法藏起来,外面不能调用,只能在内部偷偷调用。

下面我们来主要介绍狭义的封装,狭义的封装具有三种使用情况:

  • 不让用户看到也不让用户修改;
  • 可以让用户看到但不让用户修改;
  • 可以让用户看到也可以让用户修改,但必须遵守一系列规则。

类中变量具有三种级别:

  • public 公有的 类内外都可以使用,父类子类也都可以使用(Python 支持);
  • protect 保护的 类内能用,父类子类也都可以使用,但类外不可以使用(Python 不支持);
  • private 私有的 类内能用,其他地方都不可以使用(Python 支持)。

我们今天介绍的狭义封装就是依靠私有变量实现的,那么类都可以四有哪些内容呢?

  • 私有静态变量
  • 私有实例变量
  • 私有绑定方法

如在平时的用户登录操作中,用户的密码不能随随便便被外部调用,所以我们利用私有变量隐藏起这个属性:

# 隐藏属性方法:
class User:
    def __init__(self, name, password):
        self.user = name
        self.__password = password  # 给一个属性前面加上了双下划线的时候,这个属性就变成了私有的


# 官方称其为私有变量
alex = User('alex', 'sbsbsb')
# print(alex.__password)  # AttributeError: 'User' object has no attribute '__password'

如果我们希望实现用户只能看到不能修改,可以在类内定义方法来实现读取私有变量:

# 所有的私有内容或者名字都不能在类的外部调用,只能在内部使用了
# 如果一定要返回私有变量,可以通过定义绑定方法的方式返回
# 这种绑定方法返回私有变量的情况,主要是想让用户看,但是不让用户该的情况
class User:
    def __init__(self, name, password):
        self.user = name
        self.__password = password  # 私有的实例变量/私有的对象属性

    def get_pwd(self):  # 表示的时用户不能改只能看
        return self.__password

    def change_pwd(self):  # 表示用户必须调用我们自定义的修改方式来进行变量的修改
        pass


alex = User('alex', 'sbsbsb')
print(alex.get_pwd())  # sbsbsb


# 返回经hashlib加密的变量
import hashlib


class User:
    def __init__(self, name, password):
        self.user = name
        self.__pwd = password  # 私有的实例变量

    def __get_md5(self):  # 私有的变量
        md5 = hashlib.md5(self.user.encode('utf-8'))
        md5.update(self.__pwd.encode('utf-8'))
        return md5.hexdigest()

    def getpwd(self):
        return self.__get_md5()


alex = User('alex', 'sbsbsb')
print(alex.getpwd())

那么,从内部实现的角度来说,为什么类内的变量名字加了下划线就不能从外部调用了呢?

class User:
    __Country = 'China'

    def func(self):
        print(self.__Country)  # 在类的内部使用的时候,自动地把当前这句话所在的类的名字拼在私有变量钱完成变形


print(User.__dict__)


# {'__module__': '__main__', '_User__Country': 'China', 'a': 'b', '__dict__': <attribute '__dict__' of 'User' objects>, '__weakref__': <attribute '__weakref__' of 'User' objects>, '__doc__': None}
# __Country -> _User__Country
# 所以其实非要找的话是找得到的
# print(User._User__Country)  # China


# 在类的外部根本不能定义私有的概念
# User.__aaa = 'bbb'  # 这是不可以运行的

我们知道,私有的内容是不可被继承的,那么这是什么原因呢?这是因为私有的内容在类内调用的时候会自动填补上_classname所以即使子类定义了同名私有变量,但是其实实际上定义的变量/函数名是不同的,所以私有的变量/函数只能在类内使用:

# 私有的内容能不能被子类使用呢?
class Foo(object):
    def __init__(self):
        self.func()

    def func(self):
        print('in Foo')


class Son(Foo):
    def func(self):
        print('in Son')


Son()  # in Son


# 私有的内容不可以被继承!
class Foo(object):
    def __init__(self):
        self.__func()  # 表面上在寻找__func,实际上是在寻找_Foo__func

    def __func(self):
        print('in Foo')


class Son(Foo):
    def __func(self):
        print('in Son')


Son()  # in Foo

四、property 装饰器

  1. property 装饰器

    为了方便 property 装饰器的理解,我们先引入一个之前举过的例子,圆类:

    from math import pi
    
    class Circle:
        def __init__(self, radius):
            self.radius = radius
    
        def area(self):
            return pi * self.radius ** 2
    
    
    c1 = Circle(5)
    print(c1.radius)  # 获取半径
    print(c1.area())  # 获取面积
    

    我们可以看到,圆在此类中获取半径和面积的方法是截然不同的,因为一个是内部方法,一个是实例属性;但是在我们正常逻辑理解中,圆的半径和面积应该都是属于名词,都应该是圆的属性,这样我们就可以利用 property 装饰器,将一个类内的内部方法伪装成属性在外部使用:

    from math import pi
    
    class Circle:
        def __init__(self, radius):
            self.radius = radius
    
        @property
        def area(self):
            return pi * self.radius ** 2
    
    
    c1 = Circle(5)
    print(c1.radius)  # 获取半径
    print(c1.area)  # 获取面积
    

    为了能更轻松理解 property 装饰器,我们再举两个例子:

    # property装饰器的应用场景1:
    import time
    
    
    class Person:
        def __init__(self, name, birth):
            self.name = name
            self.birth = birth
    
        @property
        def age(self):  # 装饰的这个方法,不可以有参数
            return time.localtime().tm_year - self.birth
    
    
    taibai = Person('taibai', 1998)
    print(taibai.age)
    
    
    # property的应用场景2:和私有属性合作
    class User:
        def __init__(self, user, password):
            self.user = user
            self.__password = password
    
        @property
        def pwd(self):
            return self.__password
    
    
    alex = User('alex', 'sbsbsb')
    print(alex.pwd)  # sbsbsb
    
    # 售货例子:苹果的打折
    class Goods:
        discount = 0.8
    
        def __init__(self, name, origin_price):
            self.name = name
            self.__price = origin_price
    
        @property
        def price(self):
            return self.__price * self.discount
    
    
    apple = Goods('apple', 5)
    print(apple.price)  # 4.0
    
  2. property_name.setter 装饰器

    当我们需要在外部修改苹果的价格时,单纯的使用 property 装饰器伪装的内部方法会暴露,所以我们必须再伪装一个同名内部方法来完成其修改的操作:

    class Goods:
        discount = 0.8
    
        def __init__(self, name, origin_price):
            self.name = name
            self.__price = origin_price
    
        @property
        def price(self):
            return self.__price * self.discount
    
        @price.setter
        def price(self, new_value):
            if isinstance(new_value, float):  # 起到约束数据类型的作用
                self.__price = new_value
    
    
    apple = Goods('apple', 5)
    print(apple.price)  # 调用的是被@property装饰的price
    apple.price = 10.0  # 调用的是被setter装饰的price
    print(apple.price)  # 8.0
    
  3. property_name.deleter 装饰器

    虽然已经支持修改和调用,但是如果我们在外部删除苹果的价格时,内部方法仍然会暴露,所以我们可以再伪装一个同名内部方法来完成其删除的操作:

    # 我们要删除苹果的价格的时候,又会产生新需求,这时我们需要deleter
    class Goods:
        discount = 0.8
    
        def __init__(self, name, origin_price):
            self.name = name
            self.__price = origin_price
    
        @property
        def price(self):
            return self.__price * self.discount
    
        @price.setter
        def price(self, new_value):
            if isinstance(new_value, float):  # 起到约束数据类型的作用
                self.__price = new_value
    
        @price.deleter
        def price(self):
            if isinstance():  # 起到约束数据类型的作用
                del self.__price
    
    apple = Goods('apple', 5)
    del apple.price  # 并不能真的删除什么东西,只是调用被装饰的方法而已
    

五、反射

  1. getattr()内部函数

    反射其实就是利用字符串数据类型的名字,来操作这个名字对应地函数/实例变量/绑定方法等。如我们举一个日常编写代码中的问题:

    name = 'alex'
    age = 123
    # birth
    # sex
    # job
    # phone
    # ...这么多怎么办?
    
    n = input('>>>')
    if n == 'name': print(name)
    elif n == 'age': print(age)
    # ...
    

    有些时候,你明明知道一个变量的字符串数据类型的名字,你想直接调用它但是调用不到,这时可以利用反射,反射支持以下多种情况:

    • 反射对象的实例变量;
    • 反射对象的静态变量/绑定方法等;
    • 反射模块中的所有变量:包括被导入模块,当前执行的 py 文件(脚本)。
    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def qqxing(self):
            print('qqxing')
    
    
    alex = Person('alex', 83)
    wusir = Person('wusir', 74)
    print(alex.name)
    print(alex.age)
    ret1 = getattr(alex, 'name')
    ret2 = getattr(wusir, 'name')
    print(ret1)  # alex
    print(ret2)  # wusir
    ret = getattr(wusir, 'qqxing')
    print(ret)  # <bound method Person.qqxing of <__main__.Person object at 0x00000226CAC4DBE0>>
    ret()  # qqxing
    

    这样,反射的基本用法已经介绍完了,我们现在举一个之前写过的例子:支付程序

    # 实际使用情况:归一化设计
    class Payment:
        def pay(self, money):
            raise NotImplementedError('请在子类中重写同名pay方法')
    
    
    # 然后将其他的支付函数继承这个Payment类
    class Alipay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'uname': self.name, 'price': money}
            # 想办法调用支付宝支付 url链接 把dic传过去
            print('%s通过支付宝支付%s成功' % (self.name, money))
    
    
    class Wechat(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'username': 'alex', 'money': 200}
            # 想办法调用微信支付 url链接 把dic传过去
            print('%s通过微信支付%s成功' % (self.name, money))
    
    
    class Applepay(Payment):
        def __init__(self, name):
            self.name = name
    
        def pay(self, money):
            dic = {'name': self.name, 'qian': money}
            # 想办法调用苹果支付 url链接 把dic传过去
            print('%s通过苹果支付%s成功' % (self.name, money))
    
    
    def pay(name, price, mode):
        if mode == 'Wechat':
            obj = Wechat(name)
        elif mode == 'Alipay':
            obj = Alipay(name)
        elif mode == 'Applepay':
            obj = Applepay(name)
        obj.pay(price)
    
    
    pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
    pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
    pay('alex', 500, 'Applepay')  # alex通过苹果支付500成功
    

    我们现在要利用反射来解决pay()函数中冗长的代码,但是这些变量并没有封装在模块里,该如何反射呢?我们先研究个小问题抛砖引玉:

    # 在本模块里前面没有点要怎么解决呢?
    import sys
    import temp  # 这是我们随手自己写的一个模块
    
    # 两者等价
    print(sys.modules['temp'].Alipay)  # <class 'temp.Alipay'>
    print(temp.Alipay)  # <class 'temp.Alipay'>
    # 两者等价
    print(getattr(temp, 'Alipay'))
    print(getattr(sys.modules['a'], 'Alipay'))
    

    所以我们从上面的小例子可以知道,如果想反射当前执行的脚本,可以使用下面的方法:

    wahaha = 'wahaha'
    print(getattr(sys.modules['__main__'], 'wahaha'))  # wahaha
    

    这样,pay()函数的优化问题就很好解决了:

    # 所以可对原先的归一化函数进行这样的修改
    def pay(name, price, mode):
        ret = getattr(sys.modules['__main__'], mode)
        obj = ret(name)
        obj.pay(price)
    
    pay('alex', 400, 'Wechat')  # alex通过微信支付400成功
    pay('alex', 500, 'Alipay')  # alex通过支付宝支付500成功
    pay('alex', 500, 'Applepay')  # alex通过苹果支付500成功
    

    要点小结:

    # 使用反射
    
    # 1.反射对象的实例变量
    # 2.反射对象的静态变量/绑定方法等
    # 3.反射模块中的所有变量:包括被导入的模块,当前执行的py文件-脚本
    class A:
        Role = 'fashi'
        def __init__(self):
            self.name = 'alex'
            self.age = 84
        def func(self):
            print('wahaha')
            return 666
    a = A()
    # 实例变量
    print(getattr(a, 'name'))  # alex
    # 静态变量/绑定方法
    print(getattr(a, 'func')())  # wahaha 666
    print(getattr(a, 'Role'))  # fashi
    # 反射导入模块
    # import module_name
    # getattr(module_name, 'variable_name')
    # 反射当前文件
    getattr(sys.modules['__main__'], 'variable_name')
    
  2. hasattr()内部函数和callable()内部函数

    反射在日常使用中,我们会发现,因为无法判断该变量/方法是否存在的原因,总会出现报错:

    class A:
        Role = 'fashi'
        def __init__(self):
            self.name = 'alex'
            self.age = 84
        def func(self):
            print('wahaha')
            return 666
    
    a = A()
    # print(getattr(a, 'gender'))  # AttributeError: 'A' object has no attribute 'gender'
    

    这时我们可以在反射的使用中假如hasattr()callable()来判断反射内容是否存在以及是否可以调用,来防止报错的发生:

    class A:
        Role = 'fashi'
        def __init__(self):
            self.name = 'alex'
            self.age = 84
        def func(self):
            print('wahaha')
            return 666
    
    a = A()
    if hasattr(a, 'gender'):  # 判断是否拥有属性,防止报错
        print(getattr(a, 'gender'))
    if hasattr(a, 'func'):
        if callable(getattr(a, 'func')):  # 判断是否可以调用,防止报错
            print(getattr(a, 'func')())  # wahaha 666
    
posted @ 2020-07-24 14:31  Raigor  阅读(129)  评论(0编辑  收藏  举报