面向对象
封装
面向对象也是一种代码
封装
的方法对象 = 属性(静态特征) + 方法(能做的事)
创建对象前通过类将相关的属性和方法打包到一起,然后通类来生成相印的对象
好比工厂在批量生产前会先制作一个模具,而类就是这个模具
首先我们需要创建一个类(class),类可以理解成摸具,更具这个摸具我们可以创造很多相似的对象。
class Animal():
eyes = 2
legs = 4
def run(self):
print("我在运动")
def call(self):
print("我在叫")
使用我们的类创建对象并使用内部方法和属性
...
a1 = Animal()
a1.run() # 我在运动
a1.call() # 我在叫
print(a1.legs) # 4
a1.legs = 3
print(a1.legs) # 3
两个对象之间互不干扰
a1 = Animal()
a2 = Animal()
a1.legs = 3
print(a1.legs) # 3
print(a2.legs) # 4
继承
可以使用现有类的所有功能,并且在无需重新编写代码的情况下对这些代码进行扩展
通过继承创建的新类我们称之为子类,被继承的类称之为父类或者基类
# 父类
class Animal():
eyes = 2
legs = 4
def run(self):
print("我在运动")
def call(self):
print("我在叫")
class Cat(Animal):
def call(self):
print("猫在叫")
c = Cat()
c.run()
c.call()
- isinstance()
- 判断对象是不是属于某个类
- issubclass()
- 检测一个类是否为某个类的字类
...
print(isinstance(c, Cat)) # T
print(isinstance(c, Animal)) # T
print(issubclass(Cat, Animal)) # T
print(issubclass(Animal, Cat)) # F
组合
我们可以把不相关的类放到另外的一个类中一起调用
class Animal():
eyes = 2
legs = 4
name = ""
def run(self):
print("我在运动")
def call(self):
print("我在说话")
class Cat(Animal):
name = "猫"
varieties = ""
def call(self):
print("喵喵喵")
class Dog(Animal):
name = "狗"
def call(self):
print("油猴!")
class PetShop():
c = Cat()
d = Dog()
def call(self):
self.c.call()
self.d.call()
pet = PetShop()
pet.call()
"""
喵喵喵
油猴!
"""
构造函数 init
我们在编写类的时候可以使用 init 来接收外部传入的参数
class Class():
def __init__(self, x, y):
self.x = x
self.y = y
def add(self):
print(self.x + self.y)
def mul(self):
print(self.x * self.y)
c = Class(4, 5)
c.add()
c.mul()
重写
如果我们对于父类的某个属性或某个方法不满意的话完全可以重写写一个同名的属性或方法对其进行覆盖这种行为我们称之为 重写
class Animal():
eyes = 2
legs = 4
def run(self):
print("我在运动")
def call(self):
print("我在叫")
class Cat(Animal):
def call(self):
print("猫在叫")
如上代码所示,猫类继承动物类,内部有和动物类同名的方法,这样就实现了猫类对动物类call方法的重写
super
我们可以使用super调用父类中的方法,这样更安全。
class Animal():
def __init__(self, eyes, legs):
self.eyes = eyes
self.legs = legs
def run(self):
print("我在运动")
def call(self):
print("我在叫")
class Cat(Animal):
def __init__(self, eyes, legs):
super(Cat, self).__init__(eyes, legs)
def call(self):
print("喵喵喵")
super(Cat, self).call()
c = Cat(4, 5)
c.call()
"""
喵喵喵
我在叫
"""
Mix-in
这个概念类似于插件,在不改变原本代码的情况下利用多继承让类多出一些功能
class Animal():
def __init__(self, eyes, legs):
self.eyes = eyes
self.legs = legs
def run(self):
print("我在运动")
def call(self):
print("我在叫")
class Cat(Animal):
def __init__(self, eyes, legs):
super(Cat, self).__init__(eyes, legs)
def call(self):
print("喵喵喵")
super(Cat, self).call()
如上代码所示,我现在想让猫类可以飞,如何最小的改动代码呢?
...
class FlyMixin():
def fly(self):
print("我会飞")
class Cat(FlyMixin, Animal):
...
只需要在猫类前创建一个飞的mixin 像插件一样直接继承就可以了
多态
当我们使用 + 和 * 的时候我们会发现当两边的类型发生变化时,结果也会发生变化
print(1 + 1)
print("1" + "1")
有类似特征的还有 len()
len("123")
len(['123'])
这种传入不同类型数据,返回不同结果的性质我们叫做多态。
首先我们要知道我门传入的都是对象,不同的对象。我们来构建类似的方法
class Animal():
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
print(f"我是{self.name},今年{self.age}")
class Cat(Animal):
pass
class Dog(Animal):
pass
class Pig(Animal):
pass
c = Cat('cat', '1')
d = Dog('dog', '1')
p = Pig('pig', '1')
def animal(obj):
obj.show()
animal(c)
animal(d)
animal(p)
私有变量、私有方法和__slots__
私有变量
我们都知道,class中的属性和方法,外部都可以通过对象直接调用,如果们不想被直接调用可以这样写
# coding=utf-8
class A():
def __init__(self, a):
self.__a = a
def get_a(self):
return self.__a
def set_a(self, a):
self.__a = a
return self.__a
a = A(1)
# print(a.__a) # AttributeError: 'A' object has no attribute '__a'
print(a.get_a()) # 1
print(a.set_a(2)) # 2
我们这样就只能通过我们指定的接口来访问了。但真的如此么?
...
print(a.__dict__) # {'_A__a': 2}
print(a._A__a) # 2
我们发现其实__a 只是被换了名字。
私有方法
私有方法就是在方法名前去添加__
...
def __b_func(self):
print("func")
a._A__b_func() # func
__ slots __
为什么python的对象可以动态的让我们添加属性呢?是因为对象的属性由dict存放.
class A():
def __init__(self, a):
self.a = a
a = A(1)
print(a.__dict__) # {'a': 1}
a.z = 30
print(a.__dict__) # {'a': 1, 'z': 30}
我们甚至可以通过修改这个__ dict __ 来修改对象的属性
class A():
def __init__(self, a):
self.a = a
a = A(1)
print(a.__dict__) # {'a': 1}
a.z = 30
print(a.__dict__) # {'a': 1, 'z': 30}
a.__dict__['z'] = 50
print(a.z) # 50
a.__dict__['un'] = 55
print(a.un) # 55
如果我们不需要动态修改对象的属性这样会造成内存空间的浪费,我们可以使用 __ slots __
class A():
__slots__ = ["a", "b"]
def __init__(self, a):
self.a = a
a = A(1)
a.b = 4
a.c = 3 # AttributeError: 'A' object has no attribute 'c'
魔法方法(类的生命周期钩子)
魔法方法: 在类中不需要我们显性调用的方法就是魔法方法 比如 __ init __ ()
__ new __ (cls,…)
new 也是一个魔法方法,他的作用就是创建一个类的实例,将其传递给 init 方法。 self 就是其返回的
class CapStr(str):
def __new__(cls, string):
string = string.upper()
return super().__new__(cls, string)
c = CapStr("python")
print(c) # PYTHON
__ del __ (self)
del 在对象被销毁后会调用
class C():
def __init__(self):
print("我来拉~")
def __del__(self):
print("我走拉~")
c = C() # 我来拉~
del c # 我走拉~
Python的垃圾回收机制,当一个对象没有任何引用的时候才会将其销毁。
也就是说 我们直接使用 del 删除可能不会触发 __ del __
class C():
def __init__(self):
print("我来拉~")
def __del__(self):
print("我走拉~")
def call(self):
print("呦吼~")
c = C() # 我来拉~
d = c
del c
d.call() # 呦吼~
del d # 我走拉~
对象的重生
对象的重生 不推荐使用了解原理就行
对象被del删除前将self送出去对象就算重生了
全局变量重生
class C():
def __init__(self, name):
self.name = name
print("我来拉~")
def __del__(self):
print("我走拉~")
global x
x = self
c = C("python") # 我来拉~
print(c) # <__main__.C object at 0x000002E69F331FD0>
print(c.name) # python
del c # 我走拉~
print(x) # <__main__.C object at 0x000002E69F331FD0>
print(x.name) # python
闭包重生
class C():
def __init__(self, name, func):
self.name = name
self.func = func
print("我来拉~")
def __del__(self):
print("我走拉~")
self.func(self)
def outter():
x = ""
def inner(y=None):
nonlocal x
if y:
x = y
else:
return x
return inner
f = outter()
c = C("python", f) # 我来拉~
print(c) # <__main__.C object at 0x000002AB9E0E3F70>
print(c.name) # python
del c # 我走拉~
print(f()) # <__main__.C object at 0x000002AB9E0E3F70>
print(f().name) # python
属性相关的魔法方法(hasattr()、getattr()、setattr()、delattr())
四个方法的基本使用
class Fish():
def __init__(self, name, age):
self.name = name
self.age = age
f = Fish("鱼", 18)
print(hasattr(f, "name")) # True
print(getattr(f, "name")) # 鱼
setattr(f, "name", "猪")
print(getattr(f, "name")) # 猪
delattr(f, "name")
print(getattr(f, "name", "1")) # 1
getattr和hasattr 的魔法方法 __ getattribute __ 、 __ getattr __
我们在类中重写这两个方法,并且输出一下方法名字,让我们看一下如何调用的。
class Fish():
def __init__(self, name, age):
self.name = name
self.age = age
def __getattribute__(self, item):
print("__getattribute__")
return super(Fish, self).__getattribute__(item)
def __getattr__(self, item):
print("__getattr__")
if item == "gtd":
print(f"hello {item}")
else:
return super(Fish, self).__getattr__(item)
f = Fish("鱼", 18)
print(hasattr(f, "name"))
"""
__getattribute__
True
"""
print(hasattr(f, "1"))
"""
__getattribute__
__getattr__
False
"""
发现 当用户使用 hasattr 时__ getattribute __ 方法会被调用,当属性不存在时 __ getattr __ 会被调用。
getattr()呢?
...
print(getattr(f, "name"))
"""
__getattribute__
鱼
"""
print(getattr(f, "1"))
"""
__getattribute__
__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
"""
调用逻辑一样,但当属性不存在时会报错。
我们可以给getattr()加第三个参数,来设置默认值如:
print(getattr(f, "1", "default"))
"""
__getattribute__
__getattr__
default
"""
除了他俩会调用方法,还有self.属性也会调用这个方法.
...
f = Fish("鱼", 18)
print(f.name)
"""
__getattribute__
鱼
"""
print(f.a)
"""
__getattribute__
__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
"""
setattr 的魔法方法__ setattr __
我们思考一下这个方法是如何实现的?直接使用self.key = value么?让我们来试试。
class Fish():
...
def __setattr__(self, key, value):
self.key = value
f = Fish("鱼", 18)
setattr(f, "name", "猪") # RecursionError: maximum recursion depth exceeded
为什么会出现递归的报错呢?其实更具上面的getattr()就明白 getatr()和self.属性 其实调用的是相同的方法。
那么setattr()和self.属性 = xxx 是不是也一样呢?
...
f.name = "猪" # RecursionError: maximum recursion depth exceeded
一样的报错,那么既然一样,递归就不难懂了。那么我们怎么避免呢?两种方法
super 避免递归
class Fish():
...
def __setattr__(self, key, value):
super(Fish, self).__setattr__(key, value)
f = Fish("鱼", 18)
f.name = "猪"
print(f.name) # 猪
__ dict __ 避免递归
class Fish():
...
def __setattr__(self, key, value):
self.__dict__[key] = value
f = Fish("鱼", 18)
f.name = "猪"
print(f.name) # 猪
delattr 的魔法方法__ delattr __
delattr 和 setattr 一样 要注意避免递归问题。
class Fish():
...
def __delattr__(self, item):
print("__delattr__")
self.__dict__.pop(item)
# del self.__dict__[item]
f = Fish("鱼", 18)
del f.name # __delattr__
# delattr(f, "name") # __delattr__
print(getattr(f, "name", "没值")) # 没值
索引、切片、迭代协议
索引和切片
索引和切片的魔法方法是 __ getitem __ 和 __ setitem __当我们对这个对象使用切片和索引操作就会调用这个方法
class Data():
def __init__(self, data):
self.data = data
def __getitem__(self, index):
print("__getitem__", index)
return self.data[index]
def __setitem__(self, index, value):
print("__setitem__", index, value)
self.data[index] = value
d = Data([1, 2, 3, 4, 5])
print(d[2])
"""
__getitem__ 2
3
"""
print(d[2:5])
"""
__getitem__ slice(2, 5, None)
[3, 4, 5]
"""
d[2] = 1 # __setitem__ 2 1
print(d[2])
"""
__setitem__ 2 1
__getitem__ 2
1
"""
d[0:3] = d[3:5] # __getitem__ slice(4, 5, None) __setitem__ slice(2, 3, None) None
print(d[0:3])
"""
__getitem__ slice(3, 5, None)
__setitem__ slice(0, 3, None) [4, 5]
__getitem__ slice(0, 3, None)
[4, 5, 4]
"""
迭代协议
__ getitem __ 除了切片操作外其实还拦截了循环
...
d = Data([1, 2, 3, 4, 5])
for a in d:
print(a)
"""
__getitem__ 0
1
__getitem__ 1
2
__getitem__ 2
1
__getitem__ 3
4
__getitem__ 4
5
__getitem__ 5
"""
虽然可以拦截,但这是一种求全的方法,正常我们应该使用 __ iter __ 和 __ next __
对象所属的类中拥有 __ iter __ 时他就是一个可迭代对象
当拥有 __ next __ 时他就是一个迭代器
__ iter __ 会返回一个迭代器,我们试试
x = [1, 2, 3, 4, 5]
# 列表不是一个迭代器
# next(x) # TypeError: 'list' object is not an iterator
iterator_x = iter(x)
while True:
try:
print(next(iterator_x))
except StopIteration:
break
已知 iter 可以返回一个迭代器,那让我们试着写一个可迭代的类吧
class Data():
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
print("__iter__")
return self
def __next__(self):
print("__next__")
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value
d = Data(1, 5)
for i in d:
print(i)
"""
__iter__
__next__
1
__next__
2
__next__
3
__next__
4
__next__
5
__next__
"""
那如果 getitem 和 iter next 同时存在 会走谁呢?会优先走 iter 和 next 如果没有这两个方法再寻找 getitem 这种行为叫做代偿
class Data():
def __init__(self, start, stop, data):
self.value = start - 1
self.stop = stop
self.data = data
def __iter__(self):
print("__iter__")
return self
def __next__(self):
print("__next__")
if self.value == self.stop:
raise StopIteration
self.value += 1
return self.value
def __getitem__(self, index):
print("__getitem__", index)
return self.data[index]
d = Data(1, 5, [1, 2, 3, 4, 5])
for i in d:
print(i)
"""
__iter__
__next__
1
__next__
2
__next__
3
__next__
4
__next__
5
__next__
"""
in 和 not in 的魔法方法__ contains __
class Data():
def __init__(self, data):
self.data = data
def __contains__(self, item):
print("__contains__")
return item in self.data
def __iter__(self):
self.i = 0
return self
def __next__(self):
if self.i == len(self.data):
raise StopIteration
item = self.data[self.i]
self.i += 1
return item
def __getitem__(self, index):
print("__getitem__")
return self.data[index]
d = Data([1, 2, 3, 4, 5])
print(2 in d)
"""
__contains__
True
"""
如果程序中没有 contains 那么会寻找 iter next 如果还没有 使用 getitem。
比较运算符
- < __ lt __(self,other)
- <= __ le __(self,other)
- > __ gt __(self,other)
- >= __ ge __(self,other)
- == __ eq __(self,other)
- != __ ne __(self,other)
举个例子 我觉得字符串比较时应该是去比较字符的长度这样更有意义。
class Data(str):
def __gt__(self, other):
return len(self) > len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __lt__(self, other):
return len(self) < len(other)
def __le__(self, other):
return len(self) <= len(other)
def __eq__(self, other):
return len(self) == len(other)
def __ne__(self, other):
return len(self) != len(other)
d = Data("123")
s = Data("789")
print(d > s) # False
print(d >= s) # True
print(d < s) # False
print(d <= s) # True
print(d == s) # True
print(d != s) # False
property()和描述符
property
通过这个装饰器我们可以不用加()实现直接调用
class Data(str):
@property
def get_data(self):
return "123"
d = Data()
print(d.get_data)
我们还可以这样用,使用property去管理我们的get、set、del三个方法
class D():
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx)
d = D()
print(d.x)
d.x = 2
print(d.x)
del d.x
print(d.__dict__)
描述符
如果一个类中有 __ set __ 、 __ get __ 、 __ delete __ 那么这个类被叫做描述符,用来管理别人的属性。
class A():
def __get__(self, instance, owner):
print("__get__", instance, owner)
def __set__(self, instance, value):
print("__set__", instance, value)
def __delete__(self, instance):
print("__delete__", instance)
class B():
x = A()
b = B()
print(b.x) # __get__ <__main__.B object at 0x107bc0fa0> <class '__main__.B'>
b.x = 1 # __set__ <__main__.B object at 0x107bc0fa0> 1
del b.x # __delete__ <__main__.B object at 0x107bc0fa0>
由代码返回值可知,instance 是被描述属性类的实例,owner 是被描述属性的类
由此我们可以实现,property的功能
class A():
def __get__(self, instance, owner):
print("__get__", instance, owner)
return instance._x
def __set__(self, instance, value):
print("__set__", instance, value)
instance._x = value
def __delete__(self, instance):
print("__delete__", instance)
del instance._x
class B():
def __init__(self):
self._x = 250
x = A()
b = B()
print(b.x) # 250
b.x = 1
print(b.x) # 1
del b.x
print(b.__dict__) # {}
这样虽然实现了类似功能,但是在一个类中调用别的类中的实例是不是有点不太优雅,我们可以这样改
class MyProperty():
def __init__(self, fget, fset, fdel):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
print("__get__", instance, owner)
return self.fget(instance)
def __set__(self, instance, value):
print("__set__", instance, value)
self.fset(instance, value)
def __delete__(self, instance):
print("__delete__", instance)
self.fdel(instance)
class D():
def __init__(self):
self._x = 250
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = MyProperty(getx, setx, delx)
d = D()
print(d.x)
d.x = 1
print(d.x)
del d.x
print(d.__dict__)
类方法和静态方法
类方法
通过类方法装饰器 @classmethod 可以定义一个类方法,第一个参数是cls表示当前的类。
我们可以通过这个来管理我们的对象实例,如下:
class Data():
count = 0
def __init__(self):
Data.count += 1
@classmethod
def get_count(cls):
return Data.count
d1 = Data()
d2 = Data()
d3 = Data()
print(Data.get_count()) # 3
静态方法
我们可以通过 @staticmethod 装饰器实现一个静态方法,也同样可以实现上述功能。
class Data():
count = 0
def __init__(self):
Data.count += 1
@staticmethod
def get_count():
return Data.count
d1 = Data()
d2 = Data()
d3 = Data()
print(Data.get_count())
对比
既然都能实现,那到底那种方法好一些呢?
两种方法都是类层级的方法,无需创建实例去使用。区别是类方法需要和类绑定,静态方法则不需要。
如果和类发生关系,则需要使用类方法,否则使用静态方法即可。
如果发生继承关系,我们想让每个类都能统计自己的实例数量,通过类方法就可以实现继承。静态适合在子方法中使用,或者某些全局情况下使用
class Data():
count = 0
@classmethod
def add_count(cls):
cls.count += 1
Data.count += 1
def __init__(self):
self.add_count()
@classmethod
def get_count(cls):
return cls.count
@staticmethod
def get_all_count():
return Data.count
class A(Data):
count = 0
class B(Data):
count = 0
class C(Data):
count = 0
A1 = A()
B1, B2 = B(), B()
C1, C2, C3 = C(), C(), C()
print(A.get_count()) # 1
print(B.get_count()) # 2
print(C.get_count()) # 3
# 静态方法实现
print(Data.count) # 6