本文是《Python基础教程(第2版 修订版)》第 7 章 更加抽象 和 第9章 魔法方法、属性和迭代器 的笔记,简要介绍了Python中类的创建、私有特性、类命名空间、超类和构造方法、成员访问、属性、迭代器、生成器以及如何进行面向对象设计等内容。
1. Objects
对象可以看作是数据及一系列可以操作这些数据的方法所组成的集合。
多态,即鸭子类型
意味着可以对不同类型的对象使用相同的操作。该操作会根据对象类型的不同而表现出不同的行为。
很多内建运算符和函数都有多态的性质。
唯一能毁掉多态的就是使用函数显式地检查类型,比如、以及函数等。
封装:对外部世界隐藏对象的工作细节。多态可以让用户对于不知道是什么类的对象进行方法调用,而封装是可以不用关心对象是如何构建而直接进行使用。
继承:以通用的类为基础建立专门的类对象。
2. Classes
2.1 create class
在旧版本的Python中,内建的对象是基于类型的,自定义的对象是基于类的,可以创建类但不能创建类型。而后期的版本对这种界限开始模糊,可以创建内建类型的子类或子类型,而这些类型的行为更类似于类。在Python3中不用再担心该问题,也不需要显式地子类化或者将元类设置为,所有的类都会隐式地成为的子类。
为了确保类是新型的,需要在模块或脚本开始的地方放置赋值语句 。或者(直接或间接)子类化内建类或其他新式类。
还可以在类的作用域中对变量赋值。这样只会为这个类设定元类,即其他类或类型的类。
创建类:
参数正是方法和函数的区别。方法(绑定方法)会将其第一个参数绑定到所属的实例上,因而不用显示地提供该参数。也可以将特性绑定到一个普通函数上,此时便不会有参数。
2.2 使用私有特性
只要在名字前加双下划线便可让方法或特性变为私有。
在类的内部定义中,所有双下划线开始的名字都会被“翻译”成前面加上单下划线和类名的形式。因此实际上还是能在类外访问这些私有方法。这里我测试了下在PyCharm里不行,但在命令行里可以。
如果不需要使用但是也不想让其他对象访问内部数据,可以使用单下划线。例如有单下划线的名字不会被语句导入。
2.3 类命名空间
所有位于语句中的代码都在该命名空间内。类作用域内的变量可以被所有实例访问。
2.4 superclass
使用超类:将其他类名写在语句后的圆括号内可以指定超类。
多重继承:
如果一个方法从多个超类继承,则先继承的类中的方法会重写后继承的类中的方法。
方法判定顺序:如果超类们共享一个超类,在查找给定方法或属性时访问超类的顺序。
内建的函数:查看一个类是否是另一个的子类。
要知道已知类的基类,即超类,可以直接使用。
可以使用方法检查一个对象是否是一个类的实例。
要知道一个对象属于哪个类,可以使用
2.5 interface and introspection 接口与内省
在处理多态对象时,只要关心它的接口或协议即可,即公开的方法和特性。在Python中,不用显式地指定对象必须包含哪些方法才能作为参数接收。
一般来说,只需要让对象符合当前的接口,即实现当前的方法即可。当然除了调用方法,还可以检查所需方法是否已经存在。若不存在,就需要做些事情。
函数可以检查对象是否有该特性。而函数可以检查是否可以调用,在Python3中已不可用,用代替
3. Thoughts on Object-Oriented Design
当考虑需要什么类以及类要有什么方法时,可以尝试下面的方法,得到面向对象模型的草图。
写下问题的描述,即程序要做什么。把所有名词、动词和形容词加下划线。
对于所有名词,用作可能的类。
对于所有的动词,用作可能的方法。
对于所有的形容词,用作可能的特性。
把所有的方法和特性分配到类。
还可以考虑类和对象之间的关系(如继承或协作)以及它们的作用,以精炼模型。
写下一系列的使用实例,即程序应用时的场景,试着包括所有的功能。
一步步考虑每个使用实例,保证模型包括所有需要的东西。如有遗漏,则进行添加。
4. Constructors 构造方法
当一个对象被创建后,会立即调用构造方法。与之对应的是析构方法,在对象要被垃圾回收之前调用。因发生具体调用的时间不可知,故应尽量避免使用。
如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则该对象可能不会被正确初始化,无法调用超类中的方法。
4.1 调用未绑定的超类构造方法
在调用一个实例方法时,该方法的参数会被自动绑定到实例上,即绑定方法。直接调用类的方法,如,就没有实例会被绑定,即未绑定方法,这样可以自由地提供需要的参数。
4.2 使用super函数调用超类的构造方法
当前类和对象可以作为函数的参数使用,调用函数返回的对象的任何方法都是超类的方法。函数是新式类中的函数。在Python3中,函数可以不带任何参数进行调用。
函数返回的是一个对象。该对象负责进行方法解析。当对其特性进行访问时,它会查找所有的超类,包括超类的超类,直到找到所需的特性或引发异常为止。
即使类继承多个超类,也只需要调用一次函数,但要确保所有的超类的构造方法都使用了函数。
大多数情况下,使用函数比调用超类的未绑定的构造方法更好。
5. Item Access 成员访问
规则:用来描述管理某种形式的行为的规则。规则说明了应该实现何种方法和这些方法应该做什么。Python中的多态性是基于对象的行为的。
5.1 基本的序列和映射规则
序列和映射是对象的集合。如果要实现它们基本的行为(规则),若对象是不可变的,则需要实现2个方法,若是可变的,则需要实现4个。
:返回集合中所含项目的数量。
对于序列,即元素的个数。
对于映射,即键——值对的数量。
如果返回0,且没有重写,则对象会被当做一个布尔变量中的假值进行处理。
:返回所给键对应的值。
对于序列,键应该是的整数,是序列的长度。
对于映射,可以使用任何类型的键。
:按照一定的方式存储和相关的,该值可以使用获取。只能为可以修改的对象定义该方法。
:该方法在对一部分对象使用语句时会被调用,同时必须删除和键相关的键。只能为可修改的对象定义该方法。
对上述方法的附加要求:
对于序列,如果键是负数,则从末尾开始计数。
如果键是不合适的类型,则应该引发一个异常。
如果序列的索引是正确的类型,但超出了范围,则应该引发一个异常。
5.2 子类化列表、字典和字符串
如果类的行为和内建类型的行为接近,可以通过子类化内建类型实现。标准库中、、可以直接使用。
6. Properties 属性
通过访问器定义的特性称为属性。
6.1 property函数
函数可以用0、1、3或4个参数来调用。4个参数分别叫做、、和.
如果没有参数,产生的属性既不可读,也不可写。
如果只使用一个参数(一个取值方法),产生的属性是只读的。
第3个参数(可选)是一个用于删除特性的方法(它不需要参数)。
第4个参数(可选)是一个文档字符串。
如果想要一个属性是只写的,并且有一个文档字符串,可以使用关键字参数的方式来实现。
函数的工作原理
是拥有很多特殊方法的类。
涉及的方法是、和。这3个方法合在一起,就定义了描述符规则。实现其中任一方法的对象,即描述符。
描述符的特殊之处在于是如何被访问的。例如,程序读取一个特性时,如果该特性被绑定到实现了方法的对象上,那么就会调用方法,而不是返回对象。
函数创建了属性,其中访问器函数作为参数。这样就不用担心的实现问题。其他属性也可以同样处理。
使用函数的缘由:
案例中,和方法是名为的假想特性的访问器方法,是由和组成的元组。如果后续改变类的实现,成真的特性,需要做大量的修改。
把所有的属性放到访问器方法中,访问器方法仅仅实现返回和存储,且要写较多访问器方法,不合适。
6.2 静态方法和类成员方法
静态方法和类成员方法在大部分情况下,可以使用函数或者绑定方法代替。
静态方法在创建时会被装入类型的对象中。其定义没有参数,且能够被类本身直接调用。
类成员方法在创建时会被装入类型的对象中。在定义时需要名为的参数。类成员方法可以直接用类的具体对象调用,参数会自动绑定到类。
装饰器:能对任何可调用的对象进行包装,可用于方法和函数。
多个装饰器应用时的顺序与指定顺序相反。
可以使用装饰器替换上例的手动包装和替换方法。
6.3 、等
拦截对象的所有特性访问,在访问特性的时候执行一些代码,需要实现一些方法。
:当特性被访问时会自动调用(只能在新式类中使用)。
:当特性被访问且对象没有相应的特性时被自动调用。
:当试图给特性赋值时会被自动调动。
:当试图删除特性name时。
当属性不是时,方法会被用来代替普通的特性赋值操作,以避免被再次调用而程序进入死循环。方法包含一个字典,该字典里面是所有实例的属性。
因为拦截所有特性的访问(在新式类中),也拦截对的访问。所以访问中与相关的特性时,使用超类的(使用函数)是唯一安全的途径。
7. Iterators 迭代器
只要对象实现了方法,即迭代器规则,就能对对象进行迭代。
方法会返回一个迭代器,即具有方法的对象。
方法在调用时不需要任何参数。
在调用方法时,迭代器会返回它的下一个值。
如果方法被调用,但迭代器没有值可以返回,会引发一个异常。
在Python3中,迭代器对象应该实现方法,而不是。新的内建函数函数可以访问该方法。
一个实现了方法的对象是可迭代的,一个实现了方法的对象是迭代器。
内建函数可以从可迭代对象中获得迭代器。
除了在迭代器和可迭代对象上进行迭代外,还能转换为序列。在大部分能使用序列的情况下,都能使用迭代器替换,除了索引和分片操作。
8. Generators 生成器
生成器是一种用普通的函数语法定义的迭代器。任何包含语法的函数即为生成器。
生成器每次使用语句产生一个值,函数就会被冻结。即函数停在那个点等待被重新唤醒。被重新唤醒后就从停止的那点开始执行。
生成器推导式,即生成器表达式,返回的是生成器。生成器推导式可以在当前的圆括号内直接使用。
当生成器被调用时,在函数体内的代码不会被执行,会返回一个迭代器。每次请求一个值,都会执行生成器中的代码,直到遇到一个或者语句,即生成一个值或不再生成任何东西。
生成器的特征是在开始运行后为生成器提供值的能力。表现为生成器和“外部世界”进行交流的渠道,需要注意两点:
外部作用域访问生成器的方法,就像访问方法一样,不同在于前者使用一个参数(要发送的“消息”——任意对象)。
在内部则挂起生成器,作为表达式而不是语句使用。当生成器重新运行时,方法返回一个值,即外部通过方法发送的值。如果方法被使用,那么方法返回。
使用方法只有在生成器挂起后才有意义,即函数第一次被执行后。如果需要对刚启动的生成器使用方法,则可以将作为其参数进行调用。
生成器还有其他两个方法:
方法(使用异常类型调用,还有可选的值以及回测对象):用于在生成器内引发一个异常,即在表达式中。
方法用于停止生成器,调用时不用参数。
方法也是建立在异常的基础上的。在需要的时候也会由Python垃圾收集器调用。它在运行处引发一个异常。所以,如果需要在生成器内进行代码清理的话,则可以将语句放在语句中。如果需要的话,还可以继续捕捉异常,但随后必须将其重新引发或引发另一个异常或直接返回。在生成器的方法被调用后再通过生成器生成一个值会导致异常。
领取专属 10元无门槛券
私享最新 技术干货