快捷搜索:  汽车  科技

python中类的四种定义方法(python入门系列深入类和对象)

python中类的四种定义方法(python入门系列深入类和对象)def extend(self iterable): pass 它的参数并未要求是一个列表,而是一个可迭代对象即可,所以,下面这种使用方都是可以的:li_a li_b =[1 2] [3 4] # 初学列表时,应该都知道列表的 extend()方法,将两个列表合并 li_a.extend(li_b) print(li_a) # result: # [1 2 3 4] 实际上,extend() 这个方法的原型是什么样的呢使用案例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") # 这里

python中类的四种定义方法(python入门系列深入类和对象)(1)


鸭子类型和多态

引言

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。

例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭子的对象,并调用它的 走 和 叫方法。

在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的 走 和 叫 方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的 走 和 叫 方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。

使用案例

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") # 这里又是一切皆对象的说明,把类作为对象放入 list animal_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 中的接口
  • 这个类无法实例化
  • 用来检测某个类是否具有某种方法属性
  • 用来模拟实现抽象类

使用案例

检测对象属性

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():

print(hasattr(language "__len__")) # result: # True

不过这种方式语义不够明显,我们可以像 Java 一样,采用更加适合于编码习惯的方式。 collections.abc 模块中定义好了很多抽象基类,每一个都具有不同的属性,下一小节会讨论一下 Sized 这个抽象基类。

python中类的四种定义方法(python入门系列深入类和对象)(2)

# 这里的 Sized 就是定义了某些必须实现函数的抽象基类 from collections.abc import Sized print(isinstance(language Sized)) """ isinstance放在下一个话题详细讨论 这里先理解为 language 有了 Sized 里定义的属性 """ # result: # True

构建抽象类

还有一个位于全局的直接 abc 模块,可以通过它来声明一个抽象的类,然后通过其他具体的类去继承实现这个类。强制约束类的行为。具体说明可以查看 这里

# 首先需要导入这个模块,abstract base class import abc class Base(metaclass=abc.ABCMeta): # metaclass? 放在元类编程里讲 @abc.abstractmethod # 采用装饰器将这个方法修饰成抽象方法 def get(self key): pass class Test(Base): pass # 未实现抽象方法 test = Test() # 实例化阶段就会报错 # result: # TypeError: Can't instantiate abstract class Test with abstract methods get

看看 Sized 这个抽象基类中都有什么东西:

""" 接上一小节 """ class Sized(metaclass=ABCMeta): __slots__ = () @abstractmethod def __len__(self): # 上一小节使用 Sized 做检测的关键 return 0 @classmethod def __subclasshook__(cls C): # 能打印 True 是因为这个魔法函数自动调用了 if cls is Sized: if any("__len__" in B.__dict__ for B in C.__mro__): return True return NotImplemented

关于 __subclasshook__(),参阅:https://docs.python.org/3/library/abc.html#abc.ABCMeta.subclasshook

小结

abc 模块可以让我们定义自己的抽象基类,但是你可能已经注意到,这样的功能似乎和 Python 的动态语言灵活特性相违背,所以,这里并不推荐采用这种方式来编写代码,鸭子类型才应该是我们关注的重点。

collections.abc 中定义了各种抽象基类,每一种都具有专门的特性,这些抽象基类不是为我们提供的,只是以一种类似文档的方式让我们了解 Python 灵活的各种内置对象的构成。

isinstance 和 type

引言

  • isinstance(obj class) : 判断前面的对象是不是后面类的一个实例。
  • type(obj): 获得对象的类型

使用案例

class A: pass 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()。

类变量和实例变量

引言

  • 类变量属于类,是由所有实例共享的
  • 实例变量只属于特定的实例

使用案例

class Vector: v = 1 def __init__(self x y): self.x = x self.y = y m = Vector(2 3) print(m.x m.y m.v Vector.v) # 实例可以访问到类变量 # result: # 2 3 1, 1 Vector.v = 11 print(m.x m.y m.v Vector.v) # 通过类修改了类变量的值 # result: # 2 3 11,11 """ 通过实例无法修改类变量 这样写本质上是给实例 m 增加了一个实例变量 v """ m.v = 111 print(m.x m.y m.v Vector.v) # result: # 2 3 111 11 MRO

引言

  • MRO:方法解析顺序(Method Resolution Order)
  • 对象在使用属性或者调用方法的时候会按照一定的顺序进行查找

使用案例

现在有这样两种多继承的关系结构:

python中类的四种定义方法(python入门系列深入类和对象)(3)

1 中若在A中调用了某一个方法,但是这个方法并不存在于A中,则它就会按照如下顺序向上查找并调用:A->B->D->C->E

2 中,会沿着这样的一个顺序:A->B->C->D

详细的算法步骤比较复杂,就不仔细讲解。我们可以通过__mro__属性来查看这种搜索顺序

""" 图2中的继承关系 """ class D: pass class B(D): pass class C(D): pass class A(B C): pass print(A.__mro__) # result: # (<class '__main__.A'> <class '__main__.B'> <class '__main__.C'> <class '__main__.D'> <class 'object'>) 类方法 静态方法 实例方法

引言

  • 类方法静态方法在使用时要加上特殊的装饰器
  • 类方法和整个类有关,而且需要引用这个类
  • 静态方法和类有关,但不需要引用类或者实例

使用案例

class Date: def __init__(self year month day): self.year = year self.month = month self.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)>0 valid_month = int(month)>0 and int(month)<=12 valid_day = int(day)>0 and int(day)<31 return 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 数据封装和私有属性

引言

  • Python中对象的私有属性都是通过双下划线开头
  • Python语言中并没有严格地数据私有化机制,而是通过名字重整,间接私有属性

使用案例

class Person: def __init__(self age): self.__age = age def get_age(): return self.age person = 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)查看更加详细的属性

使用案例

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

print(dir(me))

python中类的四种定义方法(python入门系列深入类和对象)(4)

这里给出的属性会更加详细,不过没有对应的值。通过给出的这些属性,我们大概就能猜到它实现了哪些魔法函数

关于super

引言

  • 调用父类的方法?
  • 多半用在构造函数中,既然重写了父类构造函数,为什么还要去调用?

使用案例

""" 定义自己的一个线程 可以重用父类的构造方法,完成线程创建 """ from threading import Thread class MyThread(Thread): def __init__(self myname user): self.user = user super().__init__(name=myname) # 调用父类的构造方法 """ 这里参考 MRO 小结中的第二种继承关系 可以看到,B被打印后并没有找到其父类D,进行D的打印,而是打印了C super() 调用顺序与 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的用法
  • 实现了上下文管理器协议的类都可以直接使用with语句

使用案例

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

import contexlib """ 使用contexlib库中提供的一个装饰器可以将一个函数变成上下文管理器 所修饰的函数必须是一个生成器 yield 语句之前的代码对应 __enter__()中的逻辑 yield 语句之后的代码对应 __exit__()中的逻辑 """ @contexlib.contexmanager def 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 喜欢python qun:839383765 可以获取Python各类免费最新入门学习资料!

猜您喜欢: