Python一一类代码编写基础

类,和模块的三个不同之处:类支持多个对象产生、命名空间继承以及运算符重载

类产生多个实例对象

类对象提供默认行为,是实例对象的工厂。实例对象是程序处理的实际对象:各自都有独立的命名空间。类对象来自于语句,而实例来自于调用。

类对象提供默认行为

Python类主要特性的要点:

        class语句创建类对象并将其赋值给变量名。

        class语句内的赋值语句会创建类的属性。

        类属性提供对象的状态和行为

实例对象是具体的元素

        像函数那样调用类对象会创建新的实例对象。每次类调用时,都会建立并返回新的实例对象。实例代表了程序领域中的具体元素。

        每个实例对象继承类的属性并获得了自己的命名空间。由类所创建的实例对象是新的命名空间。一开始是空的,但是会继承创建该实例的类对象内的属性。

        在方法内对self属性做赋值运算会产生每个实例自己的属性。在类方法函数内,第一个参数(按惯例称为self)会引用正处理的实例对象。对self的属性做赋值运算,会创建或修改实例内的数据,而不是类的数据。

第一个例子

>>> class FirstClass:
	def setdata(self, value):
		self.data = value
	def display(self):
		print(self.data)

位于类中的函数通常称为方法。方法是普通的def。在方法函数中,调用时,第一个参数自动接收隐含的实例对象:调用的主体。

建立实例:

>>> x = FirstClass()
>>> y = FirstClass()

上述产生两个实例和一个类,结构如下:

如果对实例以及类对象内的属性名称进行点号运算,Python会通过继承搜索从类取得变量名(除非该变量名位于实例内):

>>> x.setdata("King Arthur")
>>> y.setdata(3.14159)

x或y本身都没有setdata属性,为了寻找这个属性,Python会顺着实例到类的连接搜索。而这就是所谓的Python的继承:继承是在属性点号运算时发生的,而且只与查找连接对象的变量名有关。

>>> x.display()
King Arthur
>>> y.display()
3.14159

在类内时,通过方法内对self进行赋值运算;而在类外时,则可以通过对实例对象进行赋值运算:

>>> x.data = "new value"
>>> x.display()
new value
>>> 

类通过继承进行定制

Python可以让类继承其他类,因而开启了编写类层次结构的大门,在阶层较低的地方覆盖现有的属性,让行为特定化。在这里和模块不一致:模块的属性存在于一个单一、平坦的命名空间之内(这个命名空间不接受定制化)。

在Python中,实例从类中继承,而类继承于超类。以下是属性继承机制的核心观点:

        超类列在了类开头的括号中。要继承另一个类的属性,把该类列在class语句开头的括号中就可以了。含有继承的类称为子类,而子类所继承的类就是其超类。

        类从其超类中继承属性。就像实例继承其类中所定义的属性名一样,类也会继承其超类中定义的所有属性名称。当读取属性时,如果它不存在于子类中,Python会自动搜索这个属性。

        实例会继承所有可读取类的属性。每个实例会从创建它的类中获取变量名,此外,还有该类的超类。寻找变量名时,Python会检查实例,然后是它的类,最后是所有的超类。

        每个object.attribute都会开启新的独立搜索。包括在class语句外对实例和类的引用(例如X.attr),以及在类方法函数内对self实例参数属性的引用。

        逻辑的修改是通过创建子类,而不是修改超类。在树种层次较低的子类中重新定义超类的变量名,子类就可取代并制定所继承的行为。

第二个例子

>>> class SecondClass(FirstClass):
	def display(self):
		print('Current value = "%s" ' % self.data)

定义一个新的类SecondClass,继承FirstClass所有变量名,并提供其自己的一个变量名。

由于继承搜索会从实例往上进行,之后到子类,然后到超类,直到所找的属性名称首次出现为止。这样,我们SecondClass覆盖了FirstClass中的display。我们把这种在树种较低处发生的重新定义的、取代属性的动作称为重载。

另外,SecondClass(以及其任何实例)依然会继承FirstClass的setclass方法:

>>> z = SecondClass()
>>> z.setdata(42)
>>> z.display()
Current value = "42" 

实例模型如下:

类是模块内的属性

当class语句执行时,这只是赋值给对象的变量,而对象可以用任何普通的表达式引用。

Python中的通用惯例指出,类名应该以一个大写字母开头,以使得它们更为清晰:

例如person.py下有Person类

import person
x = person.Person()

此外,虽然类和模块都是附加属性的命名空间,它们是非常不同的源代码结构:模块反应了整个文件,而类只是文件内的语句。

类可以截获Python运算符

运算符重载:就是让用类写成的对象,可截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。

以下是重载运算符主要概念的概要:

       以双下划线命名的方法(__X__)是特殊钩子。Python运算符重载的实现是提供特殊命名的方式来拦截运算。

       当实例出现在内置运算时,这类方法会自动调用。例如,如果实例对象继承了__add__方法,当对象出现在+表达式内时,该方法就会调用。该方法的返回值会变成相应表达式的结果。

       类可覆盖多数内置类型运算。

       运算符覆盖方法没有默认值,而且也不需要。如果没有定义或继承运算符重载方法。就是说相应的运算在类实例中并不支持。例如,如果没有__add__,+表达式就会引发异常。

       运算符可让类与Python的对象模型相集成。重载类型运算时,以类实现的用户定义对象的行为就会像内置对象一样,因此,提供了一致性,以及与预期接口的兼容性。

第三个例子

>>> class ThridClass(SecondClass):
	def __init__(self, value):
		self.data = value
	def __add__(self, other):
		return ThridClass(self.data + other)
	def __str__(self):
		return '[ThridClass: %s]' % self.data
	def mul(self, other):
		self.data *= other

函数调用:

>>> a = ThridClass('abc')      # __init__ called
>>> a.display()
Current value = "abc" 
>>> print(a)    # __str__: return display
[ThridClass: abc]
>>> 
>>> b = a+'xyz'   # __add__:makes a new instance
>>> b.display()   # b has all ThridClass methods
Current value = "abcxyz" 
>>> print(b)
[ThridClass: abcxyz]
>>> 
>>> a.mul(3)    # mul: changes instance in-place
>>> print(a)
[ThridClass: abcabcabc]
>>> 

从上述可以看出:

       ThridClass生成的调用现在会传递一个参数(例如上述“abc”),这是传给__init__构造函数内的参数value的,并将其赋值给self.data。直接效果是,ThridClass计划在构建时自动设置data属性,而不是在构建之后请求setdata调用。

       ThridClass对象现在可以出现在+表达式和print调用中。对于+,Python把左侧的实例对象传给__add__中的self参数,而把右边的对象传给other,__add__返回的内容成为+表达式的结果。对于print,Python把要打印的对象传递给__str__中的self;该方法返回的字符串看做是对象的打印字符串。使用__str__,我们可以用一个常规的print来显示该类的对象,而不是调用特殊的display方法。

运算符重载方法的名称并不是内置变量或保留字,只是当对象出现在不同的环境时Python会去搜索的属性。Python通常会自动进行调用,但偶尔也能由程序代码调用。

世界上最简单的类

>>> class rec:pass

建立这个类后,就可以完全在最初的class语句外,通过赋值变量名给这个类增加属性:

>>> rec.name = 'Bob'
>>> rec.age = 40

通过赋值语句创建该属性后,就可以使用一般的语法将它们取出。

>>> print(rec.name)
Bob

注意:其实该类还没有实例,也能这样用。类本身也是对象,也是没有实例。事实上,类只是独立完备的命名空间,只要有类的引用值,就可以在任何时刻设定或修改其属性。

建立两个实例:

>>> x = rec()
>>> y = rec()
>>> x.name,y.name
('Bob', 'Bob')

其实,这些实例本身没有属性,它们只是从类对象那里取出name属性。

事实上,命名空间对象的属性通常都是以字典的形式实现的,而类继承树(一般而言)只是连接至其他字典的字典而已。

>>> rec.__dict__.keys()
dict_keys(['__module__', '__dict__', '__weakref__', '__doc__', 'name', 'age'])

在这里,类的字典显示出我们进行赋值了的name和age属性。

Python的类模型相当动态。类和实例只是命名空间对象,属性是通过赋值语句动态建立。即使是方法也可以完全独立地在任意类对象的外部创建。

比如我们在任意类之外定义一个简单的函数:

>>> def upperName(self):
	return self.name.upper()

>>> upperName(x)
'BOB'
>>> 

如果我们把这个简单函数的赋值成类的属性,就会变成方法,可以由任何实例调用:

>>> rec.method = upperName
>>> x.method()
'BOB'
>>> 

在通常情况下,类是由class语句填充的,而实例的属性则是通过在方法函数内对self属性进行赋值运算而创建的。不过,重点在于并不是如此。Python中的OOP其实就是在已连接命名空间对象内寻找属性而已。

类与字典的关系

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值