- 鸭子类型和多态
- 抽象基类和abc模块
- 有了这个魔法函数,它的对象可以用 len()
- result:
- 2
- 小结
- isinstance 和 type
- result:
- True
- True
- result:
- True
- False
- MRO
- 类方法 静态方法 实例方法
- 1.测试实例方法
- result:
- 2019/8/2
- 2.使用 classmethod 完成初始化
- result:
- 2019/8/1
- 3.使用 staticmethod 进行格式校验
- result:
- False
- Python自省机制
- result:
- {‘school_name’: ‘Rity’}
- result:
- MetaTian
- result:
- {‘module‘: ‘main‘, ‘name’: ‘MetaTian’, ‘dict‘: , ‘weakref‘: , ‘doc‘: None}
- result:
- reading
- 关于super
- with和contexlib
- result:
- enter
- doing something
- exit
鸭子类型和多态
引言
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭子的对象,并调用它的 走 和 叫 方法。
在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的 走 和 叫 方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的 走 和 叫 方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
使用案例
class Cat(object):def say(self):print("I am a cat")class Dog(object):def say(self):print("I am a dog")class Duck(object):def say(self):print("I am a duck")# 这里又是一切皆对象的说明,把类作为对象放入 listanimal_list = [Cat, Dog, Duck]for animal in animal_list:animal().say()# result:# I am a cat# I am a dog# I am a duck
上面的案例中,我们并不需要确定 animal 的类型,只要具有了 say() 这个方法,就能满足调用的条件,程序就可以正常运行。如果在 java 中,则需要抽象出一个 Animal的类,其中包含了 say() 方法,让一系列子类来实现这个方法。这就是动态语言的灵活所在。
再来看一个平时经常使用的例子:
li_a, li_b =[1, 2], [3, 4]# 初学列表时,应该都知道列表的 extend()方法,将两个列表合并li_a.extend(li_b)print(li_a)# result:# [1, 2, 3, 4]
实际上,extend() 这个方法的原型是什么样的呢
def extend(self, iterable):pass
它的参数并未要求是一个列表,而是一个可迭代对象即可,所以,下面这种使用方都是可以的:
li_a = [1, 2]tuple_b = (3, 4) # 元组set_c = set([5, 6]) # 集合,用列表转换而成li_a.extend(tuple_b)print(li_a)li_a.extend(set_c)print(li_a)# result:# [1, 2, 3, 4]# [1, 2, 3, 4, 5, 6]
可迭代对象的本质就是定义了某些魔法函数,使用特定的语法时,魔法函数会隐式地去调用。这里和上一个案例中的 say() 方法是有些类似的。
抽象基类和abc模块
引言
- 可以类比于
java中的接口 - 这个类无法实例化
- 用来检测某个类是否具有某种方法属性
-
使用案例
检测对象属性
```python class Language(object): def init(self, lan_list):
self.lans = lan_list
有了这个魔法函数,它的对象可以用 len()
def len(self):
return len(self.lans) # 具体的实现委托给了底层列表
language = Language([“C”], [“Python”]) print(len(language))
result:
2
如果我们不知道某个对象能不能作为 `len()` 的参数,直接调用会报异常。我们可以采用一种方式来检验这个对象能否用 `len()`:```pythonprint(hasattr(language, "__len__"))# result:# True
不过这种方式语义不够明显,我们可以像 Java 一样,采用更加适合于编码习惯的方式。 collections.abc 模块中定义好了很多抽象基类,每一个都具有不同的属性,下一小节会讨论一下 Sized 这个抽象基类。
# 这里的 Sized 就是定义了某些必须实现函数的抽象基类from collections.abc import Sizedprint(isinstance(language, Sized))"""isinstance放在下一个话题详细讨论这里先理解为 language 有了 Sized 里定义的属性"""# result:# True
构建抽象类
还有一个位于全局的直接 abc 模块,可以通过它来声明一个抽象的类,然后通过其他具体的类去继承实现这个类。强制约束类的行为。具体说明可以查看 这里
# 首先需要导入这个模块,abstract base classimport abcclass Base(metaclass=abc.ABCMeta): # metaclass? 放在元类编程里讲@abc.abstractmethod # 采用装饰器将这个方法修饰成抽象方法def get(self, key):passclass Test(Base):pass # 未实现抽象方法test = Test() # 实例化阶段就会报错# result:# TypeError: Can't instantiate abstract class Test with abstract methods get
看看 Sized 这个抽象基类中都有什么东西:
"""接上一小节"""class Sized(metaclass=ABCMeta):__slots__ = ()@abstractmethoddef __len__(self): # 上一小节使用 Sized 做检测的关键return 0@classmethoddef __subclasshook__(cls, C): # 能打印 True 是因为这个魔法函数自动调用了if cls is Sized:if any("__len__" in B.__dict__ for B in C.__mro__):return Truereturn NotImplemented
关于 __subclasshook__(),参阅:https://docs.python.org/3/library/abc.html#abc.ABCMeta.subclasshook
小结
abc 模块可以让我们定义自己的抽象基类,但是你可能已经注意到,这样的功能似乎和 Python 的动态语言灵活特性相违背,所以,这里并不推荐采用这种方式来编写代码,鸭子类型才应该是我们关注的重点。collections.abc 中定义了各种抽象基类,每一种都具有专门的特性,这些抽象基类不是为我们提供的,只是以一种类似文档的方式让我们了解 Python 灵活的各种内置对象的构成。
isinstance 和 type
引言
class B(A): pass
b = B() print(isinstance(b, B)) print(isinstance(b, A)) # 内部会沿着继承链往上找
result:
True
True
print(type(b) is B) print(type(b) is A)
result:
True
False
如果要判断一个对象的类型,应尽量采用 `isinstance()`。<a name="581dc4d4"></a># 类变量和实例变量<a name="B6AJQ"></a>## 引言- 类变量属于类,是由所有实例共享的- 实例变量只属于特定的实例<a name="aTrVu"></a>## 使用案例```pythonclass Vector:v = 1def __init__(self, x, y):self.x = xself.y = ym = Vector(2, 3)print(m.x, m.y, m.v, Vector.v) # 实例可以访问到类变量# result:# 2, 3, 1, 1Vector.v = 11print(m.x, m.y, m.v, Vector.v) # 通过类修改了类变量的值# result:# 2, 3, 11,11"""通过实例无法修改类变量这样写本质上是给实例 m 增加了一个实例变量 v"""m.v = 111print(m.x, m.y, m.v, Vector.v)# result:# 2, 3, 111, 11
MRO
引言
- MRO:方法解析顺序(Method Resolution Order)
- 对象在使用属性或者调用方法的时候会按照一定的顺序进行查找
使用案例
现在有这样两种多继承的关系结构:
在 1 中若在A中调用了某一个方法,但是这个方法并不存在于A中,则它就会按照如下顺序向上查找并调用:A->B->D->C->E
在 2 中,会沿着这样的一个顺序:A->B->C->D
详细的算法步骤比较复杂,就不仔细讲解。我们可以通过__mro__属性来查看这种搜索顺序
"""图2中的继承关系"""class D:passclass B(D):passclass C(D):passclass A(B, C):passprint(A.__mro__)# result:# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
类方法 静态方法 实例方法
引言
- 类方法和静态方法在使用时要加上特殊的装饰器
- 类方法和整个类有关,而且需要引用这个类
-
使用案例
```python class Date: def init(self, year, month, day):
self.year = yearself.month = monthself.day = day
def str(self):
return "{y}/{m}/{d}".format(y=self.year, m=self.month, d=self.day)
def tommorrow(self):
self.day += 1
@classmethod def parse_from_str(cls, data_str):
year, month, day = tuple(data_str.split("-"))return cls(int(year), int(month), int(day))
@staticmethod def is_valid_str(data_str):
year, month, day = tuple(data_str.split("-"))valid_year = int(year)>0valid_month = int(month)>0 and int(month)<=12valid_day = int(day)>0 and int(day)<31return valid_year and valid_month and valid_day
1.测试实例方法
new_day = Date(2019, 8, 1) new_day.tommorrow() “”” 实例方法定义的时候会有 self 参数 实际调用时,不需要进行传递,谁调用谁就是self 解释器会把它转化成:tommorrow(new_day) “”” print(new_day)
result:
2019/8/2
2.使用 classmethod 完成初始化
new_day = Date.parse_from_str(“2019-8-1”) print(new_day)
result:
2019/8/1
3.使用 staticmethod 进行格式校验
date_str = “2019-8-32” print(Date.is_valid_str(date_str))
result:
False
<a name="5ad42381"></a># 数据封装和私有属性<a name="9d1xe"></a>## 引言- `Python`中对象的私有属性都是通过**双下划线**开头- `Python`语言中并没有严格地数据私有化机制,而是通过**名字重整**,间接私有属性<a name="GXinL"></a>## 使用案例```pythonclass Person:def __init__(self, age):self.__age = agedef get_age():return self.ageperson = Person(20)print(person.age)# result:# AttributeError: 'Person' object has no attribute 'age'"""名字重整就是将双下划线开头的私有变量,在内部用另外一个名字替换掉了替换方式:_ClassName__property"""print(person._Person__age) # 还是访问到了我们所谓的 “私有属性”# result:# 20
Python自省机制
引言
- 自省是通过一定的机制查询到对象的内部结构
- 通过
__dict__来查询属性 - 通过
dir(obj)查看更加详细的属性使用案例
```python class Person: name = “MetaTian”
class Student(Person): def init(self, school_name): self.school_name = school_name
me = Student(“Rity”) print(me.dict) # 查看 me 的所有属性
result:
{‘school_name’: ‘Rity’}
“”” name 是属于 Person 的属性,能被打印不报错是因为 按照了一定的查找规则,找到了它,可以调用,但并不属于 me “”” print(me.name)
result:
MetaTian
“”” 类也是对象,但是它的属性结构要比对象复杂的多 “”” print(Person.dict)
result:
{‘module‘: ‘main‘, ‘name’: ‘MetaTian’, ‘dict‘: , ‘weakref‘: , ‘doc‘: None}
“”” 这就是对象属性存储的本质了 “”” me.dict[“hobby”] = “reading” print(me.hobby)
result:
reading
```pythonprint(dir(me))

这里给出的属性会更加详细,不过没有对应的值。通过给出的这些属性,我们大概就能猜到它实现了哪些魔法函数
关于super
引言
- 调用父类的方法?
多半用在构造函数中,既然重写了父类构造函数,为什么还要去调用?
使用案例
"""定义自己的一个线程可以重用父类的构造方法,完成线程创建,一定要先调用父类的构造函数,再加入自己的逻辑"""from threading import Threadclass MyThread(Thread):def __init__(self, myname, user):super().__init__(name=myname) # 调用父类的构造方法self.user = user # 我们自己加入的属性
"""这里参考 MRO 小结中的第二种继承关系可以看到,B被打印后并没有找到其父类D,进行D的打印,而是打印了Csuper() 调用顺序与 mro 中定义的顺序是一样的"""class D:def __init__(self):print("D")class B(D):def __init__(self):print("B")super().__init__()class C(D):def __init__(self):print("C")super().__init__()class A(B, C):def __init__(self):print("A")super().__init__()a = A()# result:# A# B# C# D# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)
小结
super()函数一般用在子类的构造函数中,可以让我们重用父类构造函数的代码,特别是父类构造函数非常复杂的情况下,因为重写构造函数后,父类构造函数将不会被自动调用。
这里的父类也不是严格的继承关系上的父类,而是MRO顺序中的上一个,对于复杂的继承关系结构,把super()简单地理解为调用父类是不准确的。with和contexlib
引言
就是上下文管理器,涉及到两个魔法函数
__enter__()和__exit__()- 用来简化
try和finally的用法 -
使用案例
```python class Sample: def enter(self):
print("enter") # 获取资源return self
def exit(self, exc_type, exc_val, exc_tb):
print("exit") # 释放资源
def do_sth(self):
print("doing something")
with Sample() as sample: sample.do_sth()
result:
enter
doing something
exit
```pythonimport contexlib"""使用contexlib库中提供的一个装饰器可以将一个函数变成上下文管理器所修饰的函数必须是一个生成器yield 语句之前的代码对应 __enter__()中的逻辑yield 语句之后的代码对应 __exit__()中的逻辑"""@contexlib.contexmanagerdef file_open(file_name):print("file open")yield {} # 模拟一下,后面部分会详细讲解生成器print("file end")with file_open("Metatian.txt") as f_opened:print("file processing")# result:# file open# file processing# file end
