深入类和对象
1. 鸭子类型
鸭子类型:多个类都有一个相同的方法,我们可以分别实例化一个对象,定义另外一个函数统一调用多个类中的相同方法
class Person:
def walk(self):
print("i walk use my foot")
class Bird:
def walk(self):
print("i walk use my foot too")
jack = Person()
chiken = Bird()
def move(arg):
arg.walk()
move(jack)
move(chiken)
输出
i walk use my foot
i walk use my foot too
2. 抽象基类(abc类),了解就好
特点:抽象基类不能实例化;继承类必须重写抽象基类中的方法
python中有一个函数hasattr(),用于判断对象中是否有某个函数,例如
print(hasattr(b,"__init__")),若返回True,表示b有__init__函数
抽象基类用途1:判断对象的类型
isinstance()可用于判断一个对象是否为指定的类型,例如抽象基类Sized
from collections.abc import Sized
isinstance(b, Sized)
用途2:强制某个子类必须实现某些方法
3. isinstance 和 type的区别
class A:
pass
class B(A):
pass
b = B()
print(isinstance(b, B))
print(isinstance(b, A))
print(type(b))
输出:
True
True
<class '__main__.B'>
4. is和"=="
is用于判断2个对象内存地址是否相同,也就是id是否相同;==用于判断2个对象的value值是否相同
a=[1,2,3]
b=[1,2,3]
print(a is b)
print(a==b)
输出
False
True
注意:
当对象是比较小的int或者string类型时,为了提高内存利用效率,python采取重用对象内存的办法,此时a is b是True;
当a和b是tuple,list,dict或set型时,python都会给a,b重新分配内存地址,所以a is b为False
5. 类变量和类的实例变量
class A:
aa = 1
def __init__(self, x, y):
self.x = x
self.y = y
a = A(2, 3)
A.aa = 11
a.aa = 100
print(a.x, a.y, a.aa)
print(A.aa)
输出
2 3 100
11
说明
aa:是类变量;
self是实例,self.x, self.y 中的x, y为实例变量
A.aa = 11修改了A类的aa属性
a.aa = 100 相当于给__init__()增加了一个变量self.aa=100,并不影响原先类的属性aa
6. 复杂的继承关系中,类和实例属性的查找顺序,可用__mro__函数查找
class D():
pass
class C(D):
pass
class B(D):
pass
class A(B, C):
pass
print(A.__mro__)
输出
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
也就是先查找A中的属性,然后是B, C, D, object
7. 类方法,静态方法,实例方法。静态方法和类方法相当于在__init__之前对数据做一个预处理
class Date:
def __init__(self, year, month ,day):
self.year = year
self.month = month
self.day = day
def __str__(self):
return "{year}/{month}/{day}".format(year=self.year, month=self.month, day=self.day)
@staticmethod
def parse_from_string(date_str):
year, month, day = tuple(date_str.split("-"))
return Date(int(year), int(month), int(day))
@classmethod
def from_string(cls, date_str):
year, month, day = tuple(date_str.split("-"))
return cls(int(year), int(month), int(day))
@staticmethod
def valid_str(date_str):
year, month, day = map(int, date_str.split("-"))
if year > 0 and month > 0 and month <= 12 and day > 0 and day <= 31:
return True
else:
return False
if __name__ == "__main__":
date1 = Date(2018, 7, 2)
print(date1)
# 静态方法
date_str = "2018-12-3"
date2 = Date.parse_from_string(date_str)
print(date2)
# 类方法
date3 = Date.from_string(date_str)
print(date3)
# 不需要返回类时,使用静态方法好些
date4 = Date.valid_str("2018-13-2")
print(date4)
输出
2018/7/2
2018/12/3
2018/12/3
False
说明:
1)参数中有self的方法都是实例方法,其中的self表示实例对象
2) 如果 import 一个模块,那么模块__name__ 的值通常为模块文件名,不带路径或者文件扩展名。如果直接运行模块,__name__ 的值等于特别缺省值"__main__"。
3) tuple可以解包
>>> date = "2018-4-5"
>>> date.split("-")
['2018', '4', '5']
>>> year, month, day = tuple(date.split("-"))
>>> year
'2018'
提取字段也可以用map来实现,更方便,map也就是映射,把一组元素按照一个函数映射成另一组元素
>>> year1, month1, day1 = map(int, date.split("-"))
>>> year1
2018
4)静态方法的缺陷是函数内return语句要调用类的名字,如果类的名字变化,静态方法也要修改,也就是通常说的硬编码。可使用class method解决这个缺陷,可以说类方法是静态方法的升级版,功能很相似
5)类方法中的参数cls代表类本身,其中cls并不是非要这么写,也可改成其他字符。实例方法中的参数self也是可以修改的
8. 数据封装和私有属性
在上面类的基础上新加一个类User, 如下. 实现隐藏具体生日信息,只能得到年龄的数据
class User:
def __init__(self, birthday):
self.__birthday = birthday
def get_age(self):
return 2018 - self.__birthday.year
if __name__ == "__main__":
user = User(Date(1990, 2, 1))
print(user.get_age())
print(user._User__birthday)
输出
28
1990/2/1
说明:
1)以双下划线开头的变量是私有变量,不能用对象名+变量名直接调用,如果用user.__birthday的话会报错
2)但是可以通过类中的公有函数来调用
3)其实在python内部是把私有变量做了一个变形,"__属性名"变成"_类名__属性名".
9. python对象的自省机制,也就是通过一些方法查询对象的内部结构
class Person:
name = "jack"
class Student(Person):
"""
继承Person类
"""
def __init__(self, school_name):
self.school_name = school_name
if __name__ == "__main__":
user = Student("北大")
# 通过__dict__查询属性
print(user.__dict__)
print(Student.__dict__)
# 通过__dict__设置属性
user.__dict__["school_addr"] = "北京市"
print(user.school_addr)
# dir()获取对象的方法名和属性名
print(dir(Person))
print(dir(user))
输出
{'school_name': '北大'}
{'__module__': '__main__', '__doc__': '\n 继承Person类\n ', '__init__': <function Student.__init__ at 0x000001C82CC05510>}
北京市
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'school_addr', 'school_name']
说明:
1)__dict__可查看对象的属性和值,同时也能给对象定义新属性和值
2)python中字典是用c编写实现的,很多python的内部结构的数据都是用字典来编写存储的
3)dir()可查看对象的方法和属性名,不能查看其值
4)如果对象不是类,而是列表,那么是不能用__dict__的,只能用dir. 比如a=[1,2], 使用a.__dict__会报错
10. super函数,真的就是调用父类么
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
super().__init__()
class C(A):
def __init__(self):
print("C")
super().__init__()
class D(B, C):
def __init__(self):
print("D")
# 参数中B在C的前面,所以会先调用B
super().__init__()
if __name__ == "__main__":
print(D.__mro__)
d = D()
输出
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
D
B
C
A
说明:
如果super函数就是调用父类,那么输出结果的前三个应该是D, B, A,但经验证是D, B, C, A。 也就是和类的__mro__属性顺序一致
11. mixin 模式特点
1) mixin类功能单一
2)不和基类关联,可以和任意基类组合,基类可以不和mixin关联就能初始化成功
3)在mixin中不要使用super这种用法
12. with语句--上下文管理器,用于简化程序
里面包括两个魔法函数__enter__和__exit__
class Sample:
def __enter__(self):
print("enter")
# 获取资源
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 释放资源
print("exit")
def do_something(self):
print("doing sth")
with Sample() as sample:
sample.do_something()
返回结果
enter
doing sth
exit
python中内置了一个contextmanager,可以把函数变成上下文管理器,进一步简化代码
import contextlib
@contextlib.contextmanager
def file_open(filename):
print("file open")
yield {}
print("file end")
with file_open("1.txt") as sample:
print("file processing")
输出结果
file open
file processing
file end
其中yield上面的print相当于__enter__,下面的print相当于__exit__
努力生活,融于自然