1.序言

本文旨在说明:在Python里自定义class时,方法的第一个参数必须是该class的instance自身的引用(一般用self命名)。

在其他语言里,定义方法的时候,第一个参数不必是类实例的引用,一般约定俗成用this关键字来表示当前实例的引用,可是Python自成一派。由于网络上绝大部分文章都说成这是硬性规定,因此笔者觉得很有必要去研究一下Python里的class System是如何构筑起来的,并在此基础上说明self一词的作用。

2.面向对象编程

对象是数据和对数据的相关操作的封装。属于对象的数据与操作也可以称之为对象的属性(attributes)。对象具有层次构造,最下层的称之为instance,在其之上的称为class。class也具有层次构造,下层class会继承上层class的属性。有必要的时候可以再定义上层的属性。在Python里,一个class可以继承多个class(多重继承)。

更多内容详见Wikipedia (日本語) 和 Wikipedia (英語)

3.Python里class的一般写法

首先,我们使用Python的class system来写一段程序。

IT公司“LiveGate”雇佣了大量的IT技术人员,并用Python写了一个管理技术人员信息的程序。理应用数据库来储存这些信息,但这里出于演示方便就不使用了。接着我们看一下表示IT技术人员的class的代码:

[code1](workers.py)

 

代码

01:     #! /usr/bin/env python
02:
03:     """
04:     Workers in a IT company named LiveGate
05:     """
06:
07:     class Workers:
08:         """ This is a class of workers working in the company."""
09:
10:         def __init__(self, name, position, email, age, salary):
11:             self.name = name
12:             self.position = position
13:             self.email = email
14:             self.age = age
15:             self.salary = salary
16:
17:
18:     class ITWorkers(Workers):
19:         """ This is a class of IT engineers. """
20:
21:         OS = 'WinNT'
22:
23:         def __init__(self, language, *av):
24:             Workers.__init__(self, *av)
25:             self.language=language
26:
27:         def work(self, n):
28:             """ IT engineers should work."""
29:
30:             if self.position == 'web creator':
31:                 w = 'makes web site'
32:             elif self.position == 'server administrator':
33:                 w = 'checks the trafic'
34:             elif self.position == 'programmer':
35:                 w = 'writes programs'
36:
37:             print '%s %s for %d, hours using %s on %s' % (self.name, w, n, self.language, self.OS)
38:
39:     ##------------------------------------------------------------------------------------------------
40:     henley = ITWorkers('PHP', 'Henley', 'web creator', 'henley@livegate.com', 32, 700)
41:     thomas = ITWorkers('Python', 'Thomas', 'server administrator', 'thomas@livegate.com', 37, 900)
42:     gates  = ITWorkers('C', 'Gates', 'programmer', 'gates@livegate.com', 42, 1200)
43:
44:     henley.OS = 'Mac'
45:     thomas.OS = 'Linux'
46:
47:     if __name__ == '__main__':
48:
49:         henley.work(8)
50:         thomas.work(7)
51:         gates.work(10)

首先定义表示劳动者的class Workers(7--15行),接着定义它的子类ITWorkers(18--37行)。18行的ITWorkers(Workers)表示ITWorkers从Workers继承而来。ITWorkers从父类Workers继承其属性。Workers类的实例在初始化的时候,其实例变量:姓名,职业类别,e-mail地址,年龄,薪金将被存储起来。除此之外,ITWorkers会把使用语言(language)作为实例变量保存下来。出来传入language参数外,其他的参数由*av(元组)传递(当调用Workers.__init__时将其展开)。BTW,__init__是在实例构造完毕之后马上调用的专用方法(special method)(该专用方法是可选的,接近于其他OOP语言里的构造函数)。

接着,必须让IT技术人员按其薪金的多少来工作,因此定义方法work(25--33行)。work的第二个参数n表示工作时间。在这里,根据职业类型而分配其工作内容和工作时间,还有其使用的编程语言和操作系统类型。ITWorkers类里定义了类变量OS,其默认值为'WinNT'(19行)。也就是说,LiveGate公司里一般使用的操作系统是WindowNT。接着,我们定义3位IT技术人员,Henley, Thomas, Gates。Henley是Web开发者,作为一名艺术家,他使用Mac(44行)。Thomas是系统管理人员,工作上的关系,他使用Linux(44行)。编程人员Gates只要能用上编辑器(Editor)就可以了,对操作系统没什么特别要求,使用的是默认的'WinNT'。Henley, Thomas, Gates他们今天的工作时长为8, 7, 10个小时(43--45行)。

这里需要注意的是,为Henley和Thomas设定了不同的操作系统,则往他们的名字空间(namespace)里添加了OS这一项(entry)。由于Gates的名字空间里没有该条目,则往ITWorkers名字空间里搜寻。Henley和Thomas都能在自己的名字空间里找到OS,所以不用向上搜寻。同样的,因为work这一项不存在于每个IT技术人员的名字空间里,所以要往ITWorkers的名字空间里搜寻。

执行workers.py后,输出如下所示:


D:\doc\05-07\py_test>python workers.py
Henley makes web site for 8 hours, using PHP on Mac
Thomas checks the trafic for 7 hours, using Python on Linux
Gates writes programs for 10 hours, using C on WinNT

4.假如Python没有class system?

这里我们思考一下,假如Python没有class system,我们应该如何处理这种情况呢。当然,可以不使用OOP来写程序,但在这里,我们想创建属于自己的class system。

实际上,使用把函数当成数据一样来对待的编程语言(广义上指函数式语言)来创建OOP语言是非常简单的。可以使用hash表(Python里称字典)来表示各个对象的名字空间,对象的层次构造也可以根据hash表的层次结构来表示。由于Python也是把函数当成数据来对待,所以很容易实现OOP。

我们尝试用自己的class system来重新把workers.py写一遍。参考重新编写的代码,那您应该明白方法的第一个参数为什么是self了。

[code 2] (workers2.py)


代码

01:     #! /usr/bin/env python
02:
03:     """
04:     This code demostrates how easy to imprement an object orientated system on a functional programming language.
05:     It only requires a nested hash table.
06:     """
07:
08:
09:     def Cls(cls=None, **key):
10:         """ making a new class"""
11:         key['class'] = cls
12:         return key
13:
14:     def new(cls, **key):
15:         """ making an instance """
16:         key['class'] = cls
17:         return key
18:
19:
20:     def geta(obj, attr):
21:         """ getting the attribute of object """
22:         if attr in obj:
23:             return obj[attr]
24:         elif(obj['class']):
25:             return geta(obj['class'], attr)
26:         else:
27:             return None
28:
29:     def tell(obj, method, *av):
30:         """ tell object do something"""
31:         fun=geta(obj, method)
32:         if callable(fun):
33:             return fun(obj, *av)
34:
35:     if __name__=='__main__':
36:
37:         def it_work(self, n):
38:             """This funciton demonstrates how IT engineers work.
39:                Notice that arguments of thie function is identical to the method 'work' in workers.py"""
40:
41:             if geta(self, 'position') == 'web creator':
42:                 w = 'makes web site'
43:             elif geta(self, 'position') == 'server administrator':
44:                 w = 'checks the trafic'
45:             elif geta(self, 'position') == 'programmer':
46:                 w = 'writes programs'
47:
48:             print '%s %s for %d, hours using %s on %s' %
(geta(self, 'name'), w, n, geta(self, 'language'), geta(self, 'OS'))
49:
50:         workers = Cls() # dummy class
51:         it_workers = Cls(workers, OS='winNT', work=it_work) # class of IT workers
52:
53:         henley = new(it_workers, language='PHP', name='henley',
54:                      position='web creator', email='henley@livegate.com', age=32, salary=700)
55:         thomas = new(it_workers, language='Python', name='Thomas',
56:                      position='server administrator', email='thomas@livegate.com', age=37, salary=900)
57:         gates  = new(it_workers, language='C', name='Gates',
58:                      position='programmer', email='gates@livegate.com', age=42, salary=1200)
59:         henley['OS'] = 'Mac'
60:         thomas['OS'] = 'Linux'
61:
62:         tell(henley, 'work', 8)
63:         tell(thomas, 'work', 7)
64:         tell(gates, 'work', 10)

为了简化代码,workers2.py里并没有实现多重继承。

先看一下创建class的函数Cls和创建instance的函数new。实际上,两者是等同的,它们只是返回添加了表示父类'class'的hash表。

接下来看一下geta函数。这是一个搜索对象属性的函数。如果对象的hash表里没有目标属性,那么将递归地往父类的hash表里搜寻。这样一来,就能实现继承和重载(override)了。没有目标属性就向上一层搜寻,这样实现了属性的继承。假如下层的对象有定义该属性,则无视上层同名属性,这样实现了属性的再定义(override)。

函数tell告对象需要执行的方法。首先使用geta来搜索方法。然后如果找到的方法是函数(callable)的话,执行之,并返回结果。

这样便完成了定义class system的函数Cls, new, geta, tell。请注意它们都是简单定义的函数。

使用刚才创建好的class system,把workers.py重新写一次,如37行后面的代码所示。

先定义表示IT技术人员工作的函数it_work。请注意它的第一个参数是self。在函数it_work里使用geta来获取IT技术人员的属性。

接着,创建类it_workers时,把指向函数it_work的pointer赋值给其'work’属性。也就是说,hash表it_workers的'work'键(key)的值是指向it_work的pointer。只要能把函数当成数据来对待,就能够实现往hash表里填充函数。(译注:hash表里存储的是指向函数的引用)

跟workers.py一样,分别定义了3位IT技术人员。由于模拟专用发法__init__并不简单,所以这里在创建实例的时候,把IT技术人员的相关属性全都当成参数传递。接着使用函数tell使他们工作起来。输出的结果跟workers.py一样:

代码

D:\doc\05-07\py_test>python workers2.py
Henley makes web site for 8 hours, using PHP on Mac
Thomas checks the trafic for 7 hours, using Python on Linux
Gates writes programs for 10 hours, using C on winNT 

对比[code 1]与[code 2],可以看出他们相似的地方:

[code 1] [code 2]
obj.attribute geta(obj, 'attribute')
obj.method(*av) tell(obj, 'method', *av)
def work(self, n) def it_work(self, n)

这并不是偶然,Python里的class从原理上来说是这样实现的(请参考:Python reference manual 3.Data model)。实际上,Python已经为我们准备了跟函数geta一样功能的getattr函数。而且在特殊变量__dict__里定义了用于定义对象名字空间的hash表。我们可以试试在命令行里输入如下代码。粗体字是返回结果。

D:\doc\05-07\py_test>python
Python 2.4.1 (#65, Mar 30 2005, 09:13:57) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
[x]>>> import sys
[x]>>> from workers import *
[]>>> gates.__dict__
{'salary': 1200, 'name': 'Gates', 'language': 'C', 'age': 42, 'position': 'programmer',
'email': 'gates@livegate.com'}

[]>>> henley.__dict__
{'salary': 700, 'name': 'Henley', 'language': 'PHP', 'age': 32, 'position': 'web  creator',
'OS': 'Mac', 'email': 'henley@livegate.com'}

[]>>> ITWorkers.__dict__
{'__module__': 'workers', 'work': <function work at 0x00A34630>, 'OS': 'WinNT',
'__doc__': ' This is a class of IT engineers. ', '__init__': <function __init__
at 0x00A345F0>}

[]>>> ITWorkers.work(gates, 10)
Gates writes programs for 10, hours using C on WinNT
[]>>>  gates.__class__.work(gates, 10)
Gates writes programs for 10, hours using C on WinNT
[]>>> getattr(henley, 'OS')
'Mac'
[]>>> getattr(henley, 'work')
<bound method ITWorkers.work of <workers.ITWorkers instance at 0x00A33760>>
[]>>> getattr(henley, 'work')(8)
henley makes web site for 8 hours, using PHP on Mac

导入sys和workers模块,然后试试敲进上面的8行命令。Gates的名字空间里([1])有各类项目(entry),但是没有'OS'这一项。Henley的名字空间里([2])有定义'OS'。ITWorkers的名字空间里([3])除了预置的__module__, __doc__,还有我们定义的'OS',work,__init__ 。特别的,方法(<function work at 0x00A34630>)作为函数被保存在内存里。正如前面提及的一样,Henley使用自身定义的'OS',而Gates则向上搜寻,使用类ITWorkers里的'OS'('OS'不存在Gates这个名字空间里)。

因为在类ITWorkers里定义了函数work,我们可以试着像[4]那样直接调用它。其输出跟调用gates.work(10)一样。由于每个实例有一个内置属性,__class__,它指向该实例所属的类,所以我们可以像[5]那样调用方法且得到相同的结果。

最后我们试试getattr函数。像[6]那样,getattr(henley, 'OS')得到的结果跟henley.OS一样。我们把它应用到方法上看看([7])。返回如下结果:

<bound method ITWorkers.work of <workers.ITWorkers instance at 0x00A33760>>

<workers.ITWorkers instance at 0x00A33760>这是Henley在内存中的地址。这个函数不用'function'而用'bound method'表示。其实'bound method'可以像[8]那样调用。这就说明了为什么从外部调用类方法的时候,第一个参数不必是实例自身的引用。不过,真正的理由应该是那样的做法不够酷:p。'bound method'可以看做是[code 2]里tell函数的语法糖(構文糖衣)。

从上面可以看出,在函数式语言里引入class system时,作为方法而定义的函数,很自然地,需要指向实例的参数。通过def关键字,在类里面定义过程与定义普通的函数一样,只是其作用域被限定在class里面。所以定义方法的第一个参数必须是self,否则不能引用实例里的变量。

5.结语

Python基本上是函数式语言(广义的),面向对象是其使用hash表后的附属物而已。这一点与原本作为面向对象编程语言而设计的C++, Java, Ruby等相异。

Python把过程的定义合并成函数的定义(没有将函数的定义与方法的定义区分开来),但在定义方法时,第一个参数必须是实例的引用。这是约定俗成的。

函数式语言要比面向对象语言更加抽象。Python深受函数式语言Haskell的影响。实际上,像[code 2]所示的那样,函数式语言可以简单地实现面向对象编程。

本文出处:http://www.shido.info/py/python7.html

Python Class System的更多相关文章

  1. python os.system 和popen

    1.python  os.system 和popen  其中第一个只会返回0或者1,另外一个会返回执行结果 每天生成一个文件,并把磁盘的使用情况写到到这个文件中,文件名为日期格式(yyyy-mm-dd ...

  2. python os.system()返回值判断

    最近遇到os.system()执行系统命令的情况,上网搜集了一下资料,整理如下,以备不时之需,同时也希望能帮到某些人. 一.python中的 os.system(cmd)的返回值与linux命令返回值 ...

  3. Python os.system 和 os.popen的区别

    (1) os.system # 仅仅在一个子终端运行系统命令,而不能获取命令执行后的返回信息 system(command) -> exit_statusExecute the command ...

  4. python os.system()和os.popen()

    1>python调用Shell脚本,有两种方法:os.system()和os.popen(),前者返回值是脚本的退出状态码,后者的返回值是脚本执行过程中的输出内容.>>>hel ...

  5. Python os.system()调用.sh脚本

    参考: python调用shell脚本的两种方法| Jeff的妙想奇境 已解决--求教python如何调用.sh文件- 查看主题• Ubuntu中文论坛 CODE #!/usr/bin/env pyt ...

  6. python logging system

    官方教程:https://docs.python.org/2/library/logging.html 1.  用法1 import logging import logging.handlers L ...

  7. python os.system重定向stdout到变量 ,同时获取返回值

    Python执行系统命令的方法 os.system(),os.popen(),commands 最近在做那个测试框架的时候发现 Python 的另一个获得系统执行命令的返回值和输出的类. 最开始的时候 ...

  8. python os.system command_line

    command_line = ("{7} {0} -Xmx{1} -jar {2} -T Pileup -R {3} -I {4} -L {5} -o {6} " + " ...

  9. python基础之使用os.system来执行系统命令

    今天我们来尝试使用python 的os.system来执行系统命令 可以使用如下方法: import osprint os.system('ping www.baidu.com') 输出的结果是:64 ...

随机推荐

  1. VMware Mac OS中无法找到适应的分辨率的解决办法

    使用VMware安装的Mac OS中,有时在显示器的分辨率中的选择项里面,没有对应显示的分辨率可供选择的时候(无法自适应),可以在虚拟机设置里,显示器中修改强制分辨率.修改过后重启虚拟机,就可以有对应 ...

  2. Linux配置LNMP环境(一)配置Nginx

    注意:配置版本nginx-1.12.0,使用虚拟机安装linux.教程中的下载地址可能有变化.注意(在您看本教程之前首先需要对linux的基本操作熟悉) 1.  先cd到/usr/local/src ...

  3. 消耗CPU的程序

    昨天领导交代客户需要一个可以测试CPU性能的脚本,问题简化下就是说要做一个可以手动设置对CPU产生消耗的程序.心想哪有这种脚本,或许性能测试工具还差不多.琢磨了下,或许用死循环可以达到差不多的效果,但 ...

  4. gulp总结

    安装 1.安装node 检测是否安装成功的方法,在命令行输入: node -v npm -v 显示版本号则安装成功 2.装cnmp一定要网络好一点 npm install cnpm -g --regi ...

  5. PHP连接数据库、创建数据库、创建表的三种方式

    这篇博客主要介绍了三种方式来连接MySQL数据库以及创建数据库.创建表.(代码是我在原来的基础上改的) MySQLi - 面向对象 MySQLi - 面向过程 PDO MySQLi 面向对象 < ...

  6. 英文版windows7中文软件显示乱码的解决办法

    一.打开控制面板,修改语言的归属地为China 修改完成之后重启,一般能解决大部分问题,如果依然有部分显示乱码,就需要去修改注册表

  7. Unreal Engine 4(虚幻UE4)GameplayAbilities 插件入门教程(三)技能标签(Ability Tags)

    本教程参考了https://wiki.unrealengine.com/GameplayAbilities_and_You,如果没有学习前两篇教程,请前往学习. GameplayAbilities插件 ...

  8. 2.1 insertion sort 《算法导论》答案

    2.1 insertion sort <算法导论>答案 答案索引帖 2.1-1 Using Figure 2.2 as a model, illustrate the operation ...

  9. Java设计模式之模板方法设计模式(银行计息案例)

    不知道为什么,这几天对Java中的设计模式非常感兴趣,恰巧呢这几天公司的开发任务还不算太多,趁着有时间昨天又把模板方法模式深入学习了一下,做了一个客户在不同银行计息的小案例,感触颇深,今天给各位分享一 ...

  10. (转)Linux服务器安装配置tomcat

    这里首先说明下,Linux下的Tomcat没有32位和64位的区分. tomcat是分windows版本与linux版本的,windows版本一般以exe或者zip格式:linux版本一般以tar.g ...