Python面向对象类继承中发生的私有属性访问错误问题

Posted 当初厉害就好了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python面向对象类继承中发生的私有属性访问错误问题相关的知识,希望对你有一定的参考价值。

按照Python100days项目中的该方法来访问私有属性,可正常访问到:

class Test:

    def __init__(self, foo):
        self.__foo = foo

    def __bar(self):
        print(self.__foo)
        print('__bar')


def main():
    test = Test('hello')
    test._Test__bar()
    print(test._Test__foo)


if __name__ == "__main__":
    main()

但是,当下面的项目中使用objectname._classname__attributionname的方式就访问不到对象u = Ultraman('骆昊', 1000, 120)的私有属性__name了。代码中使用了print('name: ', u._Ultraman__name),出现AttributeError: _Ultraman__name

"""
"""
@author: Zhangsan
@date: 2023/4/3 17:31
@file: day08_eg1.py
@Software: Pycharm
"""
# !/usr/bin/env python3
# coding=utf-8

from abc import ABCMeta, abstractmethod
from random import randint, randrange

class Fighter(object, metaclass=ABCMeta):

    __slots__ = ('_name', '_hp')

    def __init__(self, name, hp):
        self._name = name
        self._hp = hp

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

    @property
    def hp(self):
        return self._hp

    @hp.setter
    def hp(self, hp):
        self._hp = hp if hp >= 0 else 0

    @property
    def alive(self):
        return self._hp > 0

    @abstractmethod
    def attack(self, other):
        pass


class Ultraman(Fighter):

    __slots__ = ('_name', '_hp', '_mp')

    def __init__(self, name, hp, mp):
        """

        :param name:
        :param hp: 生命值
        :param mp: 魔法值
        """
        super().__init__(name, hp)
        self._mp = mp

    def attack(self, other):
        other.hp -= randint(15, 25)  #zh:为什么是“other.hp”,而不是“other._hp”  # 是不是因为在对象的外部访问不到私有属性,使用使用getter和setter方法
        print('other._hp: ', other._hp)

    def huge_attack(self, other):
        """若自身魔法值充足(大于50),则至少打掉对方50点或四分之三的血量

        :param other: 被攻击的对象
        :return: 使用成功,返回True;否则,返回False
        """
        if self._mp >= 50:
            self._mp -= 50
            injury = other.hp * 3 // 4
            injury = injury if injury >= 50 else 50
            other.hp -= injury
            return True
        else:
            self.attack(other)
            return False
    def magic_attack(self, others):
        if self._mp >= 20:
            self._mp -= 20
            for temp in others:
                if temp.alive:
                    temp.hp -= randint(10, 15)
            return True
        else:
            return False

    def resume(self):
        incr_point = randint(1, 10)
        self._mp += incr_point
        return incr_point

    def __str__(self):
        return f'~~~self._name奥特曼~~~\\n生命值:self._hp\\n' \\
               f'生命值:self._hp\\n魔法值:self._mp\\n'

class Monster(Fighter):

    __slots__ = ('_name', '_hp')

    def attack(self, other):
        other.hp -= randint(10, 20)

    def __str__(self):
        return f'~~~self._name~~~\\n生命值:self._hp\\n'

def is_ang_alive(monsters):
    """判断有没有小怪兽是活着的"""
    for monster in monsters:
        if monster.alive > 0:
            return True
    return False

def select_alive_one(monsters):
    """选择一只活着的小怪兽"""
    monsters_len = len(monsters)
    while True:
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:
            return monster

def display_info(ultraman, monsters):
    """显示奥特曼和小怪兽的信息"""
    print(ultraman)
    for monster in monsters:
        print(monster)

def main():
    u = Ultraman('骆昊', 1000, 120)

    print('name: ', u._Ultraman__name)

    m1 = Monster('狄仁杰', 250)
    m2 = Monster('白远方', 500)
    m3 = Monster('王大锤', 750)
    ms = [m1, m2, m3]
    fight_round = 1
    while u.alive and is_ang_alive(ms):
        print('===========第%02d回合===========' % fight_round)
        m = select_alive_one(ms)  # 选中一只小怪兽
        skill = randint(1, 10)  # 通过随机数来随机选取使用的技能
        if skill <= 6:  # 60%的概率使用普通攻击
            print('%s使用普通攻击打了%s.' % (u.name, m.name))
            u.attack(m)
            print('%s的魔法值恢复了%d点' % (u.name, u.resume()))
        elif skill <= 9:  # 30%的概率使用魔法攻击
            if u.magic_attack(ms):
                print('%s使用了魔法攻击.' % u.name)
            else:
                print('%s使用魔法攻击失败.' % u.name)
        else:  # 10%的概率使用究极必杀技
            if u.huge_attack(m):
                print(f'u.name使用了究极必杀技虐杀了m.name')
            else:
                print('%s使用了普通攻击打了%s' % (u.name, m.name))
                print('%s的魔法值恢复了%d点' % (u.name, u.resume()))
        if m.alive > 0:
            print('%s回击了%s' %(m.name, u.name))
            m.attack(u)
        display_info(u, ms)  # 每个回合结束显示奥特曼和小怪兽的信息
        fight_round += 1
        if u.alive > 0:
            print(f'u.name奥特曼胜利了')
        else:
            print('小怪兽胜利了')


if __name__ == '__main__':
    main()

目前尚未找到原因,先记录下(2023.04.04)

解决方法

由于上述代码中使用到了一个抽象类Figher和它对应的两个继承类Ultraman和Monster,因而在类的内部或外部中使用_classname_attribute来调用类的私有属性时,会出现AttributeError。其主要原因是:继承类中的私有属性是源自抽象类的私有属性,当在继承类中使用基类的私有属性时需要使用_baseclassname_attribute来调用类的私有属性,而不是_inheritingclassname_attribute来调用类的私有属性。
下面先分析基类Fighter(抽象类)和它的两个继承类Ultraman和Monster的具体情况。

Fighter:  __slots__ = ('__name', '__hp')  
          # 故假设存在私有属性: _Fighter__name 和 _Fighter__hp
Ultraman:  __slots__ = ('__name', '__hp', '__mp')  
          # 重点:故在Ultraman类中实际存在的私有属性是: _Fighter__name 、 _Fighter__hp、               _Ultraman__mp  (而不是_Ultraman__name)
Fighter:  __slots__ = ('__name', '__hp')  
          # 故存在私有属性: _Fighter__name 和 _Fighter__hp

上述过程可通过debug发现如下结构:

发现在Ultraman类中_Ultraman__name为TracebackError
修改后的正确代码如下:(**此处代码和上述代码中命名私有属性的方式不同:**上面将_name视为私有属性,下面代码中将__name视为私有属性)

from abc import ABCMeta, abstractmethod
from random import randint, randrange

class Fighter(object, metaclass=ABCMeta):

    __slots__ = ('__name', '__hp')

    def __init__(self, name, hp):
        self.__name = name
        self.__hp = hp

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

    @property
    def hp(self):
        return self.__hp

    @hp.setter
    def hp(self, hp):
        self.__hp = hp if hp >= 0 else 0

    @property
    def alive(self):
        return self.__hp > 0

    @abstractmethod
    def attack(self, other):
        pass


class Ultraman(Fighter):

    __slots__ = ('__name', '__hp', '__mp')

    def __init__(self, name, hp, mp):
        """

        :param name:
        :param hp: 生命值
        :param mp: 魔法值
        """
        super().__init__(name, hp)
        self.__mp = mp

    def attack(self, other):
        other.hp -= randint(15, 25)  #zh:为什么是“other.hp”,而不是“other._hp”  # 是不是因为在对象的外部访问不到私有属性,使用使用getter和setter方法
        # print('other.__hp: ', other.__hp)
        print('other.__hp: ', other.hp)


    def huge_attack(self, other):
        """若自身魔法值充足(大于50),则至少打掉对方50点或四分之三的血量

        :param other: 被攻击的对象
        :return: 使用成功,返回True;否则,返回False
        """
        if self.__mp >= 50:
            self.__mp -= 50
            injury = other.hp * 3 // 4
            injury = injury if injury >= 50 else 50
            other.hp -= injury
            return True
        else:
            self.attack(other)
            return False
    def magic_attack(self, others):
        if self.__mp >= 20:
            self.__mp -= 20
            for temp in others:
                if temp.alive:
                    temp.hp -= randint(10, 15)
            return True
        else:
            return False

    def resume(self):
        incr_point = randint(1, 10)
        self.__mp += incr_point
        return incr_point

    def __str__(self):
        return f'~~~self._Fighter__name奥特曼~~~\\n生命值:self._Fighter__hp\\n' \\
               f'魔法值:self._Ultraman__mp\\n'

class Monster(Fighter):

    __slots__ = ('__name', '__hp')

    def attack(self, other):
        other.hp -= randint(10, 20)

    def __str__(self):
        return f'~~~self._Fighter__name~~~\\n生命值:self._Fighter__hp\\n'

def is_ang_alive(monsters):
    """判断有没有小怪兽是活着的"""
    for monster in monsters:
        if monster.alive > 0:
            return True
    return False

def select_alive_one(monsters):
    """选择一只活着的小怪兽"""
    monsters_len = len(monsters)
    while True:
        index = randrange(monsters_len)
        monster = monsters[index]
        if monster.alive > 0:
            return monster

def display_info(ultraman, monsters):
    """显示奥特曼和小怪兽的信息"""
    print(ultraman)
    for monster in monsters:
        print(monster)

def main():
    u = Ultraman('骆昊', 1000, 120)

    # print('name: ', u._Ultraman__name)  # zh:AttributeError: _Ultraman__name
    print('name: ', u._Fighter__name)

    m1 = Monster('狄仁杰', 250)
    m2 = Monster('白远方', 500)
    m3 = Monster('王大锤', 750)
    ms = [m1, m2, m3]
    fight_round = 1
    while u.alive and is_ang_alive(ms):
        print('===========第%02d回合===========' % fight_round)
        m = select_alive_one(ms)  # 选中一只小怪兽
        skill = randint(1, 10)  # 通过随机数来随机选取使用的技能
        if skill <= 6:  # 60%的概率使用普通攻击
            print('%s使用普通攻击打了%s.' % (u.name, m.name))
            u.attack(m)
            print('%s的魔法值恢复了%d点' % (u.name, u.resume()))
        elif skill <= 9:  # 30%的概率使用魔法攻击
            if u.magic_attack(ms):
                print('%s使用了魔法攻击.' % u.name)
            else:
                print('%s使用魔法攻击失败.' % u.name)
        else:  # 10%的概率使用究极必杀技
            if u.huge_attack(m):
                print(f'u.name使用了究极必杀技虐杀了m.name')
            else:
                print('%s使用了普通攻击打了%s' % (u.name, m.name))
                print('%s的魔法值恢复了%d点' % (u.name, u.resume()))
        if m.alive > 0:
            print('%s回击了%s' %(m.name, u.name))
            m.attack(u)
        display_info(u, ms)  # 每个回合结束显示奥特曼和小怪兽的信息
        fight_round += 1
        if u.alive > 0:
            print(f'u.name奥特曼胜利了')
        else:
            print('小怪兽胜利了')


if __name__ == '__main__':
    main()

面向对象 封装 继承

一、封装

目的保护类,让类更加安全。

做法:让类里面的成员变量变为私有(即访问修饰符)的,做相应的方法或者属性去间接的操作成员变量

※访问修饰符

private 私有的              只能在该类中访问

protected 受保护的       只能在该类和它的子类中访问

public 公有的                在任何地方都可以访问

internal 内部的,默认的   程序集中可以访问,程序集就是命名空间

 

封装成员方法来间接操作类里面的成员变量

例:

class Ren
    {
        private string name;
        private int age;  //年龄必须在18-50之间
        //取age的值
        public int GetAge()
        {
            return age;
        }
        //给age赋值
        public void SetAge(int a)
        {
            if (age >= 18 && age <= 50)
            {
                age = a;
            }
            else
            {
                age = 18;
            }
        }
        //取name的值
        public string GetName()
        {
            return name;
        }
        //给name赋值
        public void SetName(string n)
        {
            name = n;
        }
    }
View Code
static void Main(string[] args)
        {
            Ren r = new Ren();
            r.SetAge(8);
            r.SetName("张三"); //将’张三‘赋值给成员name
            Console.WriteLine(r.GetAge+r.GetName);
            Console.ReadLine();
        }
View Code

△使用成员属性来间接访问类里面的成员变量

  定义变量的时候最好用_下划线开头

  选中要做的成员变量右键→重构→封装字段(ctrl+r,e)

      自动生成的代码不是成员变量也不是成员方法  是成员属性

      造好之后可以通过成员属性操作成员变量

例:

class Student
    {
        private string _Name;
        public string Name
        {
            get { return _Name; }//简化的方法
            set { _Name = value; }
        }
        private int _Age;
        public int Age 
        {
            get { return _Age; }
            set 
            {
                if (value >= 18 && value <= 50)
                {
                    _Age = value;
                }
                else
                {
                    _Age = 18;
                }
            }
        }

        private string _Sex;
        public string Sex {get;set;}//简写 缺点:不能控制值,不能用于构造函数
    }
View Code
static void Main(string[] args)
        {
            Student s = new Student();
            s.Name="赵四";
            s.Sex="";
            s.Age=19;
            Console.WriteLine(s.Sex+s.Name+s.Age);
            Console.ReadLine();
        }
View Code

构造方法(函数):

作用:造对象所要调用的方法,用来创建对象

      每一个类都有一个构造方法,不写也有只不过看不到而已

特殊性:写法特殊 (方法名=类名;没有返回值)

      执行时间特殊(创建对象时执行)

用途:可以对成员进行初始化

举个例子:

class Student
    {
        public Student(string s,string n)
        {
            _Sex = s;
            _Name = n;
        }
    }
View Code
static void Main(string[] args)
        {
           Student s = new Student("","张三");//new Student()就是调用了一个方法

            Console.WriteLine(s.Sex+s.Name);
            Console.ReadLine();
        }
View Code

二、继承

继承语法: 类名 : 父类名

子类 , xxx的派生类,超类

父类 , 基类

一个父类可以有无数个子类
一个子类可以有1个父类(亲爹)

子类并不是可以继承父类中所有的东西,而是可以继承父类中允许子类继承的内容,这个允许条件是依靠 访问修饰符 来做的权限

 

 

以上是关于Python面向对象类继承中发生的私有属性访问错误问题的主要内容,如果未能解决你的问题,请参考以下文章

Python面向对象特性

python -- 面向对象编程(继承重写)

oldboy 21th day. I love Python. 面向对象之封装, 多态, 继承 三大特性

Python中的面向对象(高级)之私有方法、多继承、多态

Python基础(二十三):面向对象之继承介绍

Python面向对象的三大特点:封装,继承和多态(示例)