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; } }
static void Main(string[] args) { Ren r = new Ren(); r.SetAge(8); r.SetName("张三"); //将’张三‘赋值给成员name Console.WriteLine(r.GetAge+r.GetName); Console.ReadLine(); }
△使用成员属性来间接访问类里面的成员变量
定义变量的时候最好用_下划线开头
选中要做的成员变量右键→重构→封装字段(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;}//简写 缺点:不能控制值,不能用于构造函数 }
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(); }
构造方法(函数):
作用:造对象所要调用的方法,用来创建对象
每一个类都有一个构造方法,不写也有只不过看不到而已
特殊性:写法特殊 (方法名=类名;没有返回值)
执行时间特殊(创建对象时执行)
用途:可以对成员进行初始化
举个例子:
class Student { public Student(string s,string n) { _Sex = s; _Name = n; } }
static void Main(string[] args) { Student s = new Student("男","张三");//new Student()就是调用了一个方法 Console.WriteLine(s.Sex+s.Name); Console.ReadLine(); }
二、继承
继承语法: 类名 : 父类名
子类 , xxx的派生类,超类
父类 , 基类
一个父类可以有无数个子类
一个子类可以有1个父类(亲爹)
子类并不是可以继承父类中所有的东西,而是可以继承父类中允许子类继承的内容,这个允许条件是依靠 访问修饰符 来做的权限
以上是关于Python面向对象类继承中发生的私有属性访问错误问题的主要内容,如果未能解决你的问题,请参考以下文章