上一篇的链接:菜鸟初学python入门进阶第二节:面向对象,python中的魔法函数
这篇比较长,大凑合着看⑧
维基百科:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
在java、c++等静态语言中,如果要实现堕胎 多态,必须先写一个父类,然后再写子类来继承父类,并重写父类里的方法。
python之中,由于不需要事先指明对象(类对象)的类型(type),所以可以不用写出父类,只要子类里有同样名称的方法,就可以默认其继承了同一个父类。就像维基百科里说的那样。
class human(object):
def eat(self):
print("human can eat")
class rat(object):
def eat(self):
print("rat can eat")
class fly(object):
def eat(self):
print("fly can eat")
critters = [human, rat, fly]
for critter in critters:
critter().eat()
输出:
human can eat
rat can eat
fly can eat
还有一个例子,比如经常使用的列表扩展函数extend
critters = [human, rat, fly]
critters_2 = set()
critters_2.add("asshole")
critters_2.add("rich people")
print(critters_2)
critters.extend(critters_2)
print(critters)
输出:
{'rich people', 'asshole'}
[<class '__main__.human'>, <class '__main__.rat'>, <class '__main__.fly'>, 'rich people', 'asshole']
可以看见,列表里原先存的是不同种类的class(不仅仅是数字与字符串),然后用extend加入了一个集合,结果仍然适用。去看看extend的源码,发现
def extend(self, *args, **kwargs): # real signature unknown
""" Extend list by appending elements from the iterable. """
pass
(python3.7.7)
“通过追加可迭代对象里的元素来扩展原有list”
所以只要是可迭代的对象(tuple、set、)都可以用extend方法来添加到列表。
只要你为你自己写的类追加了可迭代属性,都可以调用extend方法,仿佛你自己写的类和list、set等有一个同样的父亲。这就是鸭子类型。
例子:
class crit(object):
def __init__(self, crit_list):
self.crit_list = crit_list
def __iter__(self):
return iter(self.crit_list)
critters = [human, rat, fly]
crits = crit(["the poors", "the sluts", "the dopers"])
critters.extend(crits)
print(critters)
输出:
[<class '__main__.human'>, <class '__main__.rat'>, <class '__main__.fly'>, 'the poors', 'the sluts', 'the dopers']
抽象基类不能实例化
python是动态语言,动态语言是没有“类型”的,
变量只是用来指向不同对象的对象(指针)。
python抽象基类有点c++的纯虚函数的味道。
父类不指明方法具体实现步骤,子类必须将父类里的所有方法都具体实现一遍
既然python已经有鸭子类型了,为什么又需要抽象基类呢?
要去检查某个类是否有某种方法
class crit(object):
def __init__(self, crit_list):
self.crit_list = crit_list
def __iter__(self):
return iter(self.crit_list)
crits = crit(["the poors", "the sluts", "the dopers"])
print(hasattr(crits, "__iter__"))
输出
True
我们可以通过hasattr来判断类里是否有某种方法,但我们更倾向于去判断类是否属于某种数据类型(可迭代类型、Sized类型、、)
from collections.abc import *
print(isinstance(crits, Iterable))
print(isinstance(crits, object))
输出:
True
True
判断的过程需要检查其是否有目标数据类型的方法
我们需要强制某个子类必须实现某种方法
模拟一个抽象基类:
class shit_base():
def smell(self, smell):
raise NotImplementedError
def color(self, color):
raise NotImplementedError
class shit01(shit_base):
pass
yellow_shit = shit01().color('yellow')
输出:
Traceback (most recent call last):
File "D:/python/python3/cn-bug/o.py", line 202, in <module>
yellow_shit = shit01().color('yellow')
File "D:/python/python3/cn-bug/o.py", line 199, in color
raise NotImplementedError
NotImplementedError
可以看到,如果子类不实现父类的方法,我们可以用raise Notimplementederror来让其报错,这样就模拟了一个抽象基类。
但这样只有在具体实现某个方法时才会抛出异常,不太好。
我们可以用abc模块:
import abc
class shit_base(metaclass=abc.ABCMeta):
@abc.abstractmethod
def smell(self, smell):
pass
@abc.abstractmethod
def color(self, color):
pass
class shit01(shit_base):
def smell(self, smell):
print(smell)
def color(self,color):
print(color)
class shit02(shit_base):
pass
yellow_shit = shit01().color('yellow')
blue_shit = shit02().color('blue')
输出:
yellow
Traceback (most recent call last):
File "D:/python/python3/cn-bug/o.py", line 210, in <module>
blue_shit = shit02().color('blue')
TypeError: Can't instantiate abstract class shit02 with abstract methods color, smell
这样更好
python的抽象基类在collections.abc模块当中,可以自行查看
isinstance方法还可以判断继承链
尽量使用isinstance而不是type
python中的 “is” 是判断两个对象的 id 是否相同,“==” 是判断两个对象的 值 是否相同。
isinstance(实例, 某种类型)
class A():
pass
class B(A):
pass
b = B()
print(isinstance(b, B))
print(isinstance(b, A))
print(type(b) is B)
print(type(b) is A)
输出:
True
True
True
False
isinstance方法可以判断继承链,type仅仅只看地址,可能会有误差
什么是类变量?
类变量是属于类本身的变量(类内定义,前面没有self.),对象变量是属于类的实例的变量(类内定义,前面有self.)
先看个例子
class A():
aa = 1
def __init__(self, x, y):
self.x = x
self.y = y
a = A(2, 3)
print(a.x, a.y, a.aa)
print(A.aa)
print(A.x)
输出:
2 3 1
1
Traceback (most recent call last):
File "D:/python/python3/cn-bug/o.py", line 234, in <module>
print(A.x)
AttributeError: type object 'A' has no attribute 'x'
要注意,self是一个实例,2,3,传递给了类的实例,而不是类本身。
可以看出,当print到a.aa时,实例a没有变量aa,python会往上找类本身的变量,结果输出类变量aa的值。
后面因为类没有变量x(x是属于实例的实例变量),所以报错了。
如果我们这样操作,多加上a.aa的赋值语句
a = A(2, 3)
A.aa = 114514
a.aa = 0x3f3f3f3f
print(a.x, a.y, a.aa)
print(A.aa)
输出:
2 3 1061109567
114514
可以看到,实例a会多出一个aa属性,并且与A的类变量是独立的
由上一个例子可以看到,该查找顺序是从下往上的,先找实例有没有该属性,再找实例的类有没有该属性
python3现在的属性查询算法是C3算法
↓↓↓↓
C3 线性化算法与 MRO
↑↑↑↑↑
可以说是很详细了
看一个例子,一般在类内定义的,参数带有self的都是实例的方法
class looser:
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def next_year(self):
self.age += 1
def __str__(self):
return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
if __name__ == '__main__':
luoyonghao = looser('luoyonghao', 44, 168)
print(luoyonghao)
luoyonghao.next_year()
print(luoyonghao)
输出:
luoyonghao/44/168
luoyonghao/45/168
如上,我们用魔法函数给looser类增加了一个str的属性,使其可以按一定的格式输出。
如果我们输入的类参数格式不太对怎么办?
我们可以用静态方法来进行预处理。
class looser:
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def next_year(self):
self.age += 1
def __str__(self):
return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
@staticmethod
def pre_executed(information):
name, age, height = tuple(information.split("-"))
return looser(name, int(age), int(height))
if __name__ == '__main__':
luoyonghao = looser.pre_executed('luoyonghao-44-168')
print(luoyonghao)
输出:
luoyonghao/44/168
但是静态方法不太适合这样做,因为每次类名的改变,都需要改变静态方法的返回值里的类名。静态方法比较适合做某个类的预先检查,而不是顺带实例化。
python提供了类方法,类方法适合做真正的实例化。
class looser:
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def next_year(self):
self.age += 1
def __str__(self):
return "{name}/{age}/{height}".format(name=self.name, age=str(self.age), height=str(self.height))
@staticmethod
def pre_checked(information):
name, age, height = tuple(information.split("-"))
print(name,' ', int(age),' ', int(height))
@classmethod
def pre_executed(cls, information):
name, age, height = tuple(information.split("-"))
return cls(name, int(age), int(height))
if __name__ == '__main__':
luoyonghao = looser.pre_executed('luoyonghao-44-168')
print(luoyonghao)
输出
luoyonghao/44/168
cls是class的简写,是规范的写法,表示传递类本身
接着上面的looser类,我们建立一个crime类来进一步对looser进行封装。
python类中,以双下划线开头的属性是私有的,只能通过类里的方法进行访问。
class crime:
def __init__(self, inform):
self.__inform = inform
def get_age(self):
return self.__inform.age
if __name__ == '__main__':
luoyonghao = crime(looser.pre_executed('luoyonghao-44-168'))
print(luoyonghao.get_age())
print(luoyonghao._crime__inform)
print(luoyonghao.__inform)
输出:
44
luoyonghao/44/168
Traceback (most recent call last):
File "D:/python/python3/cn-bug/o.py", line 266, in <module>
print(luoyonghao.__inform)
AttributeError: 'crime' object has no attribute '__inform'
第一条输出,我们看到在建立luoyonghao这个对象时把inform先用looser类给实例化了,并把实例化的结果赋给crime类的类对象self.__inform。
输出luoyonghao的年龄。
第三条输出,由于luoyonghao.__inform是私有的,不能直接调用,所以输出报错了。
第二条输出,可见,尽管luoyonghao.__inform是私有的,通过一些手段我们还是能获取到数据的。
自省是通过一定的机制查询到对象的内部结构
1.通过dict查询属性❤
class looser:
'''this is a loser'''
name = "user"
class crime(looser):
def __init__(self, crime_name):
self.crimename = crime_name
if __name__ == "__main__":
asshole = crime("luoyonghao")
print(asshole.__dict__)
print(crime.__dict__)
print(looser.__dict__)
输出:
{'crimename': 'luoyonghao'}
{'__module__': '__main__', '__init__': <function crime.__init__ at 0x000001CC76673CA8>, '__doc__': None}
{'__module__': '__main__', '__doc__': 'this is a loser', 'name': 'user', '__dict__': <attribute '__dict__' of 'looser' objects>, '__weakref__': <attribute '__weakref__' of 'looser' objects>}
可以发现
: 第一条输出,dict可以找到实例的所有属性
: 第二条输出,dict可以找到类的所有属性
: 第三条输出,dict是不会向上查找属性的
dict其实可以动态增加/修改实例或类的属性
if __name__ == "__main__":
asshole = crime("luoyonghao")
print(asshole.__dict__)
# print(crime.__dict__)
# print(looser.__dict__)
asshole.__dict__["why"] = "liar"
print(asshole.__dict__)
输出:
{'crimename': 'luoyonghao'}
{'crimename': 'luoyonghao', 'why': 'liar'}
2.通过dir查询属性❤
dir比dict更加强大
if __name__ == "__main__":
asshole = crime("luoyonghao")
print(asshole.__dict__)
print(dir(asshole))
a = [1, 2]
print(dir(a))
print(a.__dict__)
输出:
{'crimename': 'luoyonghao'}
['__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__', 'crimename', 'name']
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Traceback (most recent call last):
File "D:/python/python3/cn-bug/o.py", line 279, in <module>
print(a.__dict__)
AttributeError: 'list' object has no attribute '__dict__'
一二条输出看出:dir比dict多输出了很多东西,而且进行了向上查找,连父类的属性也输出了。
三四条输出看出:list并没有魔法函数dict,但有dir
class A:
def __init__(self):
print('A')
class B(A):
def __init__(self):
print('B')
super().__init__()
b = B()
输出:
B
A
既然我们重写了构造函数,为什么还要调用super?
有时候一些参数我们可以直接用super里的(直接把参数赋给super的构造函数),就不用再在类内写属性了。
实际上,super调用的是一个MRO序列,详见:
5.类属性和实例属性以及查找顺序
实际上,我们并不推荐多继承(容易继承混乱)
可以使用mixin模式。
mixin模式特点:
1.Mixin类功能单一。
2.不和基类关联,可以和任意基类组合(基类可以不和mixin关联就能初始化成功)。
3.在mixin中不要哦使用super这种方法。
class AMixin(object):
def __init__(self):
print('A')
class B(mixins.AMixin):
def __init__(self):
print('B')
1.try except else finally语句
try:
print("a")
raise KeyError
except KeyError as e:
print("e")
else:
print("no keyerror")
finally:
print("finally")
输出:
a
e
finally
如果不raise错误:
try:
print("a")
# raise KeyError
except KeyError as e:
print("e")
else:
print("no keyerror")
finally:
print("finally")
输出:
a
no keyerror
finally
先试着执行try下的语句,如果抛出异常则执行except下的语句;没有异常执行else的语句;finally的语句最终一定会执行
要注意,如果函数在不同语句下有多个返回个值,则会先返回finally语句下的返回值。
2.python上下文管理器:
python是基于协议来进行编程的(必须要按照协议实现相应的魔法函数)
class Sample:
def __enter__(self):
print('enter now')
# 获取资源
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 释放资源
print('exit now')
def do_something(self):
print('doing sth')
with Sample() as s:
s.do_something()
输出:
enter now
doing sth
exit now
看例子:
import contextlib
@contextlib.contextmanager
def file_open(file_name):
print("file open")
yield {}
print("file close")
with file_open("None") as f_op:
print("opening")
输出:
file open
opening
file close
就到这里
下一篇的链接:菜鸟初学python入门进阶第四节:面向对象,自定义序列类
❤⭐✨✨✨d=====( ̄▽ ̄*)b
U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*UU•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-U•ェ•*U-