目录
1、类属性的增删改查
class Person:
sex = '男'
def __init__(self, name):
self.name = name
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
# 查看
print(Person.sex) # 男
# 修改
Person.sex = "女"
print(Person.sex) # 女
p1 = Person("xu")
print(p1.__dict__) # {'name': 'xu'}
print(p1.sex) # 女
# 增加
Person.nation = "汉"
print(Person.nation) # 汉
# 删除
del Person.sex
print(Person.__dict__)
##===========方法================
def eat_food(self, food):
print(" {} 正在吃 {}".format(self.name, food))
# 添加方法
p2 = Person("xu")
Person.eat_food = eat_food
p2.eat_food("潮汕牛肉火锅") # xu 正在吃 潮汕牛肉火锅
2、实例属性的增删改查
class Person:
sex = '男'
def __init__(self, name):
self.name = name
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
# 查看
p3 = Person("xu3")
print(p3.sex) # 男
# 增加
p3.age = 18
print(p3.__dict__) # {'name': 'xu3', 'age': 18}
print(p3.age) # 18
# 修改
p3.name = "xu_3"
print(p3.name) # xu_3
# 删除
del p3.name
print(p3.__dict__) # {'age': 18}
3、实例属性 类属性 的寻找
(1)如果实例中没有添加属性,将会到类中查找,
在执行 p1.sex = "女" 之前,查找p1.sex ,在实例中找不到,到 class中查找
在执行 p1.sex = "女" 之后,在实例的作用域中有 sex 这个属性,并且不会影响到类的sex属性
class Person:
sex = '男'
def __init__(self, name):
self.name = name
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
# 如果实例中没有添加属性,将会到类中查找,
# 在执行 p1.sex = "女" 之前,查找p1.sex ,在实例中找不到,到 class中查找
# 在执行 p1.sex = "女" 之后,在实例的作用域中有 sex 这个属性,并且不会影响到类的sex属性
p1 = Person("xu1")
print(p1.sex) # 男
p1.sex = "女"
print("类:{}".format(Person.sex)) # 类:男
print("实例:{}".format(p1.sex)) # 实例:女
print(p1.__dict__) # {'name': 'xu1', 'sex': '女'}
(2)实例中寻找 counter,并没有找到;再到类中寻找 counter 也没有该属性
counter = "China"
class Person:
sex = '男'
def __init__(self, name):
self.name = name
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
# 实例中寻找 counter,并没有找到;再到类中寻找 counter 也没有该属性
p2 = Person("xu2")
print(p2.counter) # AttributeError: 'Person' object has no attribute 'counter'
(3)print("__init___ {}".format(counter)) 中的 counter 并不是 实例或是类中的属性,在 init 中没有定义,会到定义类的作用域中寻找,找到module 中定义的 counter
counter = "China——-----------module"
class Person:
sex = '男'
counter = "China----------- class"
def __init__(self, name):
self.name = name
print("__init___ {}".format(counter)) # __init___ China——-----------module
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
# print("__init___ {}".format(counter)) 中的 counter 并不是 实例或是类中的属性,
# 在 init 中没有定义,会到定义类的作用域中寻找,找到module 中定义的 counter
print(Person.__dict__)
print(Person.counter) # China----------- class
p3 = Person("xu3")
print(p3.counter) # China----------- class
(4)修改实例属性 cards,属性来自类作用域,如果该属性是可变的,修改的效果将作用于类属性
class Person:
sex = '男'
cards = ["card1", "card2"]
def __init__(self, name):
self.name = name
def play_ball(self, ball):
print("{} 正在打 {}".format(self.name, ball))
p4 = Person("xu4")
print(p4.cards) # ['card1', 'card2']
# p4.cards = ["c1", "c2", "c3"]
# print(p4.__dict__) # {'name': 'xu4', 'cards': ['c1', 'c2', 'c3']}
# print(Person.cards) # ['card1', 'card2']
# 修改实例属性 cards,属性来自类作用域,如果该属性是可变的,修改的效果将作用于类属性
p4.cards.append("new_card")
print(p4.__dict__) # {'name': 'xu4'}
print(Person.cards) # ['card1', 'card2', 'new_card']
@property
Python内置的@property装饰器就是负责把一个方法变成属性调用的
把一个getter方法变成属性,只需要加上@property就可以了,
此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,
于是,我们就拥有一个可控(对数据进行校验)的属性操作:
class Student():
_score = 0
@property
def score(self):
return self._score;
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError("score must be an integer!")
if value < 0 or value > 100:
raise ValueError("score must between 0 - 100")
self._score = value
s = Student()
print(s.score) # 0
s.score = 60
print(s.score) # 60
# s.score = 101 # ValueError: score must between 0 - 100
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
下面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
class Student():
_birth = 0
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2021 - self._birth
@classmethod
当我们需要和类直接进行交互,而不需要和实例进行交互时,类方法是最好的选择。
类方法与实例方法类似,但是传递的不是类的实例,而是类本身,
第一个参数是cls。我们可以用类的实例调用类方法,也可以直接用类名来调用。
class A:
class_attr = "attr"
@classmethod
def class_foo(cls):
print("classmethod -> attr:{}".format(cls.class_attr))
# 类直接调用类方法
A.class_foo() # classmethod -> attr:attr
# 实例化调用类方法
a = A()
a.class_foo() # classmethod -> attr:attr
@staticmethod
staticmethod 装饰器也会改变方法的调用方式, 但是第一个参数不是特殊的值。
其实, 静态方法就是普通的函数, 只是碰巧在类的定义体中, 而不是在模块层定义。
可以实现实例化使用 C().f(),当然也可以不实例化调用该方法 C.f()。
静态方法类似普通方法,参数里面不用self。
这些方法和类相关,但是又不需要类和实例中的任何信息、属性等等。
如果把这些方法写到类外面,这样就把和类相关的代码分散到类外,使得之后对于代码的理解和维护都是巨大的障碍。
而静态方法就是用来解决这一类问题的。
# 检查是否开启了日志功能,这个和类相关,但是跟类的属性和实例都没有关系。
log_enabled = True
class B:
class_attr = "attr"
@staticmethod
def static_foo():
if log_enabled:
print("log is enabled")
else:
print("log is disabled")
B.static_foo() # log is enabled
继承
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,
新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,
因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:
class Animal:
def run(self):
print("Animal is running...")
class Dog(Animal):
pass
class Cat(Animal):
pass
d = Dog()
d.run() # Animal is running...
当然,也可以对子类增加一些方法,比如Dog类:
继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...和Cat is running...,因此,对Dog和Cat类改进如下:
class Dog(Animal):
def run(self):
print("Dog is running ...")
def eat(self):
print("Eating meat")
class Cat(Animal):
def run(self):
print("cat is running ...")
d = Dog()
d.run() # Dog is running ...
# 判断是否是继承类的实例
print(isinstance(d, Animal)) # True
print(isinstance(d, Dog)) # True
接口继承
接口继承实质上是要求“做出一个良好的抽象”,这个抽象规定一个兼容接口,是的外部调用者无需关心具体细节,
可一视同仁的处理实现了特定接口的所有对象。———— 这个程序设计上,叫归一化
import abc
# 定义一个抽象接口的类
class All_file(metaclass=abc.ABCMeta):
@abc.abstractmethod
def read(self):
ellipsis
@abc.abstractmethod
def write(self):
pass
# 继承抽象接口类并实现其抽象方法
class Disk(All_file):
def read(self):
print("disk 实现 read 抽象方法")
def write(self):
print("disk 实现 write 抽象方法")
d = Disk()
d.read() # disk 实现 read 抽象方法
d.write() # disk 实现 write 抽象方法
继承方法的查找顺序
Python到底是如何实现继承的,对于定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表,
这个MRO列表就是一个简单的所有基类的线性顺序列表。
1、子类会先于父类被检查
2、多个父类会根据他们在列表中的顺序被检查
3、如果对下一个类存在两个合法的选择,选择第一个父类
class A:
def test(self):
print("test -> A")
class B:
def test(self):
print("test -> B")
class C(A, B):
pass
C().test() # test -> A
super()
通过super调用父类方法
class Vehicle:
countey = "china"
def __init__(self, name, speed, load, power):
self.name = name
self.speed = speed
self.load = load
self.power = power
def run(self):
print("开动了")
class Subway(Vehicle):
def __init__(self, name, speed, load, power, line):
super().__init__(name, speed, load, power)
self.line = line
def run(self):
super().run()
print("{} {} 线开始启动".format(self.name, self.line))
def show_info(self):
print(self.name, self.speed, self.load, self.power, self.line)
l = Subway("广州地铁", "60km/h", 1000, "电", "3")
l.show_info() # 广州地铁 60km/h 1000 电 3
l.run()
# 开动了
# 广州地铁 3 线开始启动
多态
多态:由不同的类实例化得到的对象,调用同一个方法,执行的逻辑不同。
多态的概念指出了对象如何通过他们共同的属性和动作来操作以及访问,而不需要考虑他们的具体类。
多态表明了动态(运行时)绑定的存在,允许重载及运行时类型确定和验证。
class H2O:
def __init__(self, name, temperature):
self.name = name
self.temperature = temperature
def turn(self):
if self.temperature < 0:
print("[{}]温度太低结冰了".format(self.name))
elif 0 <= self.temperature < 100:
print("[{}]液化变成水".format(self.name))
elif self.temperature >= 100:
print("[{}]温度太高变成水蒸气".format(self.name))
class Water(H2O):
pass
class Ice(H2O):
pass
class Steam(H2O):
pass
w = Water("水", 40)
i = Ice("冰", -4)
s = Steam("水蒸气", 110)
# w.turn()
# i.turn()
# s.turn()
# 不同实例调用相同的方法,执行不同的行为
def turn(obj):
obj.turn()
turn(w)
turn(i)
turn(s)
封装
隐藏对象的属性和实现细节,对外提供公共访问方式。
好处:将变化隔离,提高安全性
原则:
1、将不需要对外提供的内容隐藏起来
2、把属性都隐藏,提供公共方法对其访问
私有变量
#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
1.类中定义的__x
只能在内部使用,如self.__x
,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x
这个名字访问到的。
3.在子类定义的__x
不会覆盖在父类定义的__x
,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
(约定俗成)类中以_或者__的属性,都是私有属性,禁止外部调用。虽然我们可以通过特殊的手段获取到,并且赋值,但是最好不要这么做
私有方法
#正常情况
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定义成私有的,即__fa
>>> class A:
... def __fa(self): #在定义时就变形为_A__fa
... print('from A')
... def test(self):
... self.__fa() #只会与自己所在的类为准,即调用_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;
而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。
这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
400
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
property属性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
@property
@obj.setter
@obj.deleter
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name
,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
class Room:
def __init__(self, name, owner, width, length):
self.name = name
self.owner = owner
self.__width = width
self.__length = length
def cal_area(self):
return self.__length * self.__width
@property
def width(self):
return self.__width
@width.setter
def width(self, width):
if not isinstance(width, int):
print("传入数据类型不对")
elif width < 0:
print("宽度必须大于0")
else:
self.__width = width
@property
def length(self):
return self.__length
@length.setter
def length(self, length):
if not isinstance(length, int):
print("传入数据类型不对")
elif length < 0:
print("长度必须大于0")
else:
self.__length = length
room = Room("r001", "xu", 10, 10)
print(room.cal_area()) # 100
room.width = 20
room.length = 20
print(room.cal_area()) # 400
反射
反射是一个很重要的概念,它可以把字符串映射到实例的变量或者实例的方法然后可以去执行调用、修改等操作。
下面是反射的四个基本方法:
getattr 获取指定字符串名称的对象属性
setattr 为对象设置一个对象
hasattr 判断对象是否有对应的对象(字符串)
delattr 删除指定属性
通过字符串去操作对象的属性和方法
什么对象可以用反射?
实例化对象、类、其他模块、本模块只有以上四个才能使用,因为他们都能通过 . 的方式获取或调用,这也算是一种前提
实例化对象的反射操作
class A:
country = "China"
area = "guangzhou"
def __init__(self, name, age):
self.name = name
self.age = age
def func(self):
return "call func return"
a = A("xu", 100)
print(hasattr(a, "name")) # True
# 一般 hasattr 与 getattr 结合起来使用
if hasattr(a, "name"):
print(getattr(a, "name")) # xu
# 可以设置一个默认值,目的是防止程序报错; 如果没有该属性,就返回默认值
print(getattr(a, "sex", None))
fun1 = getattr(a, "func")
print(fun1()) # call func return
# 给对象设置属性
setattr(a, "sex", "男")
print(a.__dict__) # {'name': 'xu', 'age': 100, 'sex': '男'}
print(a.sex) # 男
类的反射操作
class A:
country = "China"
area = "guangzhou"
def __init__(self, name, age):
self.name = name
self.age = age
def func(self):
return "call func return"
# 获取类A的静态属性country
print(getattr(A, "country")) # China
# 获取类A 的func 方法并执行
print(getattr(A, "func")(None)) # call func return
继承的方式完成包装
继承+ 派生, 继承现有类,重写部分方法
ListStr 继承list ,这个类的实例里面添加的元素必须都是字符串
class ListStr(list):
def append(self, p_object):
if type(p_object) is str:
super().append(p_object)
else:
print("只能添加字符串类型元素")
l = ListStr("abc")
print(l) # ['a', 'b', 'c']
l.append("12")
print(l) # ['a', 'b', 'c', '12']
l.append(12)
print(l) # ['a', 'b', 'c', '12']
组合的方式完成授权
授权:
授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制。
这种做法可以新建、修改或删除原有产品的功能。其他的则保持原样。
授权的过程,即是所有更新的功能都是新类的某部分来处理,但已存在的功能就授权给对象的默认属性
需求:对文件进行写操作时,再写入每一行内容之前添加时间
import time
class FileHandle:
def __init__(self, filename, mode='r', encoding="utf-8"):
self.file = open(filename, mode, encoding=encoding)
self.mode = mode
self.encoding =encoding
# 通过 getattr 调用原来已存在的功能
# f.read 先去对象里面找,找不到再去类里找, 找不到调用此方法
def __getattr__(self, item):
return getattr(self.file, item)
# 重写 write 方法
def write(self, line):
time_str = time.strftime("%Y-%m-%d %X")
self.file.write("{} {}".format(time_str, line))
f = FileHandle("a.txt", "r+")
f.write("这是写入的内容")
f.flush() # 将内容写入硬盘
f.seek(0) # 文件内光标移动到第一行开始文字
line1 = f.read() # 读取文件
print(line1) # 2021-04-26 21:07:41 这是写入的内容