面向对象(二)----运算符对应的魔术方法、数据和自省、多态

一、运算符对应的魔术方法

 问题:数值之间能用“-”进行运算,为什么字符型、列表、元组又为什么不行?而字符型可以进行“+”运算?

答案:数值类型的对象实现了算术运行符对应的魔术方法,而字符型、列表、元组没有实现;字符型实现了“+”运算对应的魔术方法。

1、常用的算术运算符对应的魔术方法

a、__add__(self,other)  定义加法的行为:+

b、__sub__(self,other)  定义减法的行为:-

c、__mul__(self,other)  定义乘法的行为:*

d、__truediv__(self,other)  定义真除法的行为:/

e、 __floordiv__(self,other)  定义整数除法的行为://

f、__mod__(self,other)  定义取余算法的行为:%

2、更多魔术方法参考地址:https://www.cnblogs.com/nmb-musen/p/10861536.htm

3、示例

class MyStr:

def __init__(self, value):
self.value = value

def __add__(self, other):
return self.value + other.value

def __sub__(self, other):
return self.value.replace(other.value, "")


m1 = MyStr("12345")
m2 = MyStr("34")
print(m1 + m2)
print(m1 - m2)
__add__魔术方法:进行加法的时候触发运行的方法
__sub__魔术方法:进行减法的时候触发运行的方法

4、不同对象之间也可以进行算术运算,只要各自的类中都实现了同样的方法或者有同样的属性就能操作

  1)不同类之间都实现了相同的方法,有相同的属性,就可以进行相应的操作

class MyStr:

def __init__(self, value):
self.value = value

def __add__(self, other):
return self.value + other.value

def __sub__(self, other):
return self.value.replace(other.value, "")


class MyStr2:

def __init__(self, value):
self.value = value

def __add__(self, other):
return self.value + other.value

def __sub__(self, other):
return self.value.replace(other.value, "")


m1 = MyStr("12345")
m2 = MyStr2("34")
print(m1 + m2)
print(m1 - m2)
以上运行结果如下

 2)运算触发的主体(运算符前面的对象是触发的主体)

def __add__(self, other):
return self.value + other.value
print(m1 + m2)
以上加法 m1对象是主体,
self 为m1,other为m2
故m2对象不需要实现对应的__add__模式方法,只需要有对应的实例属性value就可以进行m1+m2运算,主体需要有对应的处理方法和属性

   3)运算触发的非主体对象可以不用实现任何对应的方法,只需要有对应的属性

class MyStr:

def __init__(self, value):
self.value = value

def __add__(self, other):
return self.value + other.value

def __sub__(self, other):
return self.value.replace(other.value, "")


class MyStr2:

# def __init__(self, value):
# self.value = value
#
# def __add__(self, other):
# return self.value + other.value
#
# def __sub__(self, other):
# return self.value.replace(other.value, "")
pass


m1 = MyStr("12345")
m2 = MyStr2()
m2.value = "34"
print(m1 + m2)
print(m1 - m2)

 

  运行结果:

 练习:

"""
自定义一个列表类型,实现对象可以之间可以 使用 - 来进行操作
# 要求:如果一个对象减去另一个对象,则把和被减对象中一样的数据给删除掉
# 如下:
li1 = MyList([11, 22, 33,22, 44])
li2 = MyList([1, 22])
res = li1 - li2
# res 打印的结果为[11,33,44]
"""


class MyList:
def __init__(self, list_value):
self.list_value = list_value

def __sub__(self, other):
return [i for i in self.list_value if i not in other.list_value]


li1 = MyList([11, 22, 33, 22, 44])
li2 = MyList([1, 22])
res = li1 - li2
print(res)

二、类的属性操作

1、类的属性分为:私有属性和公有属性

1)私有属性:只允许类内部使用的变量,外部使用会报错

 a、定义私有属性:

    以两个下划线开头的变量,如__name,外部使用会报错

    以一个下划线开头的变量,是申明这个变量是私有的,但是外部还是可以访问

class MyTest:
_name = "test"
__testcase = "测试用例"

@classmethod
def print_message(cls):
print("类内部访问私有属性_name:", cls._name)
print("类内部访问私有属性__testcase:", cls.__testcase)

MyTest.print_message()
print("类外部访问私有属性_name:", MyTest._name)
print("类外部访问私有属性__testcase :", MyTest.__testcase)

运行结果:

 

 

 

 b、私有属性还是有办法调用的

  python中将私有属性会修改一个名字,“_类名”+ 私有属性变量名,导致访问的时候报无属性的错误

  

class MyTest:
_name = "test"
__testcase = "测试用例"

@classmethod
def print_message(cls):
print("类内部访问私有属性_name:", cls._name)
print("类内部访问私有属性__testcase:", cls.__testcase)


# MyTest.print_message()
# print("类外部访问私有属性_name:", MyTest._name)
# print("类外部访问私有属性__testcase :", MyTest.__testcase)
print(MyTest.__dict__)
print("通过修改的名称访问私有属性:",MyTest._MyTest__testcase)
MyTest._MyTest__testcase = "这是修改的值"
print("通过修改的名称改变私有属性的值:",MyTest._MyTest__testcase)

运行结果:

 

 b、私有属性的意义:

  1、定义私有属性是为了方便开发者自己,定义类的时候不需要外部知道这个变量是做什么的,只需要自己使用。

  2、私有属性的更新不需要对外做说明,如果你使用了别人的私有属性,别人变更属性你是不知道的,由于这样导致的错误,这个责任就在你。

 

三、对象属性的限制

1、想想为什么基本数据类型是对象为什么不能该类型去添加属性呢?

是因为定义基本数据类型(整数、字符串、list、元组、字典、布尔等)类中都使用限制对象属性的__slots__

2、__slots__:限制对象属性。

3、__slots__:作用

  1)在类里面定义了__slots__属性之后,会覆盖__dict__属性(类就不会创建__dict__属性)

    节省内存:因为每个对象都有自己的属性,每创建一个对象都会有一个自己的__dict__属性,如果使用__slots__属性,会覆盖__dict__属性,__slots__属性是相同的,只有一份

  2)类实例化出来的对象,只能定义__slots__指定的属性

class Demo:
__slots__ = []


d = Demo()
d.name = "Demo"
运行报错:

print(d.__dict__)
运行报错:

 

 

 示例2:指定属性,只能定义指定的属性

class Demo:
__slots__ = ["name","age"]


d = Demo()
d.name = "Demo"
d.age = 18
print(d.name)
print(d.age)
运行结果:

 

 

 

d.name1 = "Demo1"
d.age1 = 19
print(d.name)
print(d.age)
运行结果:报属性错误

 

 

 初始化方法也只能使用指定的属性

class Demo:
__slots__ = ["name","age"]

def __init__(self,name,age):
self.name = name
self.age = age


d = Demo("Demo",18)
# d.name1 = "Demo1"
# d.age1 = 19
print(d.name)
print(d.age)
可运行成功:

 

如果初始化方法传入的是非指定的属性名称,则运行报错

 

运行结果如下:

 

四、属性的访问机制

1、自定义属性访问机制

实例属性的增删查改

def __setattr__(self, key, value):用给对象设置属性触发的魔术方法
def __getattribute__(self, item):获取属性触发的魔术方法
def __getattr__(self, item):在__getattribute__方法没有获取到对应的属性时,触发该方法
def __delattr__(self, item):删除属性触发的方法


2、使用场景
__setattr__方法:当你需要对某个属性的值做限制的时候就可以使用该方法对特定的属性值做限制
__getattribute__方法:当你需要对获取属性做处理的时候可以使用该方法,比如获取不存在的固定属性时返回一个值
__delattr__方法:当你需要对属性删除做处理的时候可以使用该方法,比如对固定属性不做删除时,可以抛出一个异常

__getattr__方法:可对获取不存在的属性做统一的处理

示例:
class MyTest:
def __getattr__(self, item):
print("属性{}不存在".format(item))
return None

def __getattribute__(self, item):
if item == 'name1': # 如果获取的属性是name1,则给返回一个默认值
return "Demo"
else:
return super().__getattribute__(item)

def __setattr__(self, key, value):
"""设置属性"""
if key == 'name': # 对name属性进行判断,选择性进行设置
if isinstance(value, str):
super().__setattr__(key, value)
else:
raise ValueError("你传入的类型错误,请传入字符类型")
else: # 对其他属性可正常设置
super().__setattr__(key, value)

def __delattr__(self, item):
if item == "name": # 对name属性不能删除进行处理
raise AttributeError("该属性不能删除!")
else:
super().__delattr__(item)

操作:
m = MyTest()
m.name = 199
运行报错:

 

 

m = MyTest()
print(m.name1)
运行结果:

 

print(m.name)

 

 

m = MyTest()
del m.name

 

 练习:

"""

自定义一个类
1、通过上课的相关知识点对这个类创建的对象,进行属性限制,对象只能设置这个三个属性: title money data
2、通过相关机制对设置的属性类型进行限制,title只能设置字符串类型数据
money设置为int类型数据 data可以设置为任意类型
3、通过相关机制实现,data 属性不能进行删除
4、当money设置的值少于0时,确保查询出来的值为0,
"""


class MyDemo:
__slots__ = ["title", "money", "data"]

def __init__(self, title, money, data):
self.title = title
self.money = money
self.data = data

def __getattr__(self, item):
print("属性{}不存在".format(item))
return None

def __getattribute__(self, item):
# print(self.money)
if item == 'money': # 当money设置的值少于0时,确保查询出来的值为0
if super().__getattribute__(item) < 0:
return 0
else:
return super().__getattribute__(item)
else:
return super().__getattribute__(item)

def __setattr__(self, key, value):
"""设置属性"""
if key == 'title': # 对title属性进行判断,选择性进行设置
if isinstance(value, str):
super().__setattr__(key, value)
else:
raise TypeError("你传入的类型错误,请传入字符类型")
elif key == "money": # 对title属性进行判断,选择性进行设置
if isinstance(value, int):
super().__setattr__(key, value)
else:
raise TypeError("你传入的类型错误,请传入整数类型")
else: # 对其他属性可任意设置
super().__setattr__(key, value)

def __delattr__(self, item):
if item == "data": # 对data属性不能删除进行处理
raise AttributeError("该属性不能删除!")
else:
super().__delattr__(item)

五、多态

Python中函数的参数是没有类型限制的,所以多态在Python中的体现并不是很严谨,多态的概念是应用于像(如C、C++、Java)强类型语言中,而Python崇尚“鸭子类型”。

六、鸭子类型

鸭子类型:它并不要求严格的继承体系,关注的不是对象的类型本身,而是它是如何使用的(行为)-----(走起路来像鸭子,看起来像鸭子,那么它就是鸭子)。

鸭子类型的体现:

1)静态语言:对于静态语言(Java,C#)来讲传参的时候必须是指定的类型或者它的子类

2)动态语言:对于动态语言Python来讲,参数并没有类型限制,只关注参数能有没有对象的行为

鸭子类型
class MyTest:
def run(self):
print("我要去买菜----!")


class MyTest2:
def run(self):
print("我要去洗菜----!")


class MyTest3:
def run(self):
print("我要去做菜----!")



def work(test: MyTest):
test.run()

m = MyTest()
m2 = MyTest2()
m3 = MyTest3()

# work函数依然可以接受其他类对象,可正常运行
work(m)
work(m2)
work(m3)

 

运行结果:

 

posted @ 2021-10-29 20:36  %女王%  阅读(178)  评论(0编辑  收藏  举报