Python学习笔记 —— 对象
来源:互联网 发布:2017双十一交易数据 编辑:程序博客网 时间:2024/06/06 06:33
本文档根据《Python3 面向对象编程》第二章内容整理总结,侵权请联系
通过学习本节的内容,我们将学到:
- 在Python中如何创建类并实例化对象
- 如何给Python对象添加属性和行为
- 如何把类组织成包和模块
- 如何建议别人不要错误地使用我们的数据
本节目录:
- 创建Python类
- 添加属性
- 让类实现一些功能
- 对象的初始化
- 解释你自己
- 模块和包
- 组织模块
- 谁可以访问我的数据
- 创建Python类
创建Python类
我们知道Python是一门非常简洁的语言,在Python3中一个最简单的类是这样实现的
class MyFirstClass: pass
如果我们调用print打印一个类的对象,它会告诉我们它是哪个类,以及在内存中的存放位置(尽管在Python中不常使用内存地址)
>>> a = MyFirstClass()>>> print(a)<__main__.MyFirstClass object at 0xb7b7faec>
添加属性
我们现在有一个基本的类,但是它毫无用处。它不包含任何数据,并且也不能做任何事情。在类的定义中,我们不必要做任何特殊的操作。我们可以通过点记法给一个实例化的对象赋予任意属性。
class Point: passp1 = Point()p2 = Point()p1.x = 5p1.y = 4p2.x = 3p2.y = 6
上边这段代码创建了一个没有任何属性和行为的空类,然后创建了两个类的实例,并给每个实例赋予了一个x坐标和y坐标来标识一个二位空间的点。我们需要做的只是通过 <object>.<attribute>=<value>
这个语法来为属性赋值。这种方式有时候被称为点记法。
让类实现一些功能
我们从一个reset的方法开始。
class Point(): def reset(self): self.x = 0 self.y = 0p = Point()p.reset()
方法和普通的函数有一点不同,就是所有的方法都有一个必须的参数,通常被称为 self 。这个命名是约定俗称的,即使如此,我们还可以称它为 this 或者 Martha 都可以。
我们调用方法的时候,并不需要给它传入 self 参数。Python 自动帮我们完成了,它知道我们正在调用对象的一个方法,所以它自动把对象传给了这个方法。如果我们在定义类的方法的时候忘记包含 self 参数,Python 会返回错误消息 takes no arguments。当程序提示你缺少参数的时候,第一件事情就是去检查在方法定义时你是否忘了带self函数。给方法传递多个参数:
class Point: def move(self, x, y): self.x = x self.y = y def reset(self): self.move(0, 0)
对象的初始化
大部分面向对象的编程语言都有一个叫构造函数的特殊方法,当对象被创建的时候会自动调用构造函数。在这点上Python和它们有一点不同,Python有一个构造函数和一个初始化函数。正常情况下,构造函数很少能被用到,除非你想做一些特别另类的操作,下面我们讨论初始化的方法。
除了有一个特殊的名字__init__
以外,Python的初始方法和其他方法没有什么不同。开始和结尾的双下划线的意思是:这是一个特殊的方法,Python的解释器会特殊对待他。
class Point: def __init__(self, x, y): self.move(x, y) def move(self, x, y): self.x = x self.y = y def reset(self): self.move(0, 0)point = Point(3, 5)
如果我们构造一个point对象的时候没有包含合适的初始化参数,程序回报一个“没有足够参数”的错误,与之前我们收到的那个错误类似。如果不想让这两个参数是必须的,我们怎么办?
我们看可以通过不改变Python函数的语法,而通过提供函数默认值来实现,语法就是在每一个变量后边通过等号赋予参数默认值。如果创建对象时没有提供参数,那么就会使用默认参数值。
class Point: def __init__(self, x=0, y=0): self.move(x, y)
大多数情况下,我们会把初始化的语句放到 __init__
函数里。但是,就像之前提到的一样,除了有初始化函数之后,Python还有一个构造函数 __new__
,并且它只接收一个参数,就是这个将要构造的类本身,同时返回刚被创建的对象。它会在对象被构造之前调用,所以这里没有self参数。当设计复杂的元编程技术时,可能会用到。
解释你自己
Python是一门非常简单易读的编程语言,有些人可能认为它是self-documenting。当我们进行面向对象的编程时,清晰地总结每一个方法是做什么的,并把这些内容写成API文档是很重要的。最好的方法就是把文档写到代码里。
Python的 docstring
文档字符串提供了对这种文档方式的支持。在每一个类,函数,方法的开头,紧接着他们的定义(以冒号结尾的那行)可以有一行Python的标准字符串,这行字符串也要和下面的代码一样有缩进。docstring
使用单引号或者双引号标注的Python字符串即 ' '
和 " "
。
文档字符串的惯例是一个多行字符串,它的首行以大写字母开始,句号结尾。第二行是空行,从第三行开始是详细的描述。强烈建议在你的函数中使用文档字符串时遵循这个惯例。
import mathclass Point: 'Represents a point in two-dimensional geometric coordinates' def __init(self, x=0, y=0): '''Initialize the position of a new point. The x and y coordinates can be specified. If they are not, the point defaults to the origin.''' self.move(x, y) def move(self, x, y): "Move point to a new location in two-dimensional sapce." self.x = x self.y = y def reset(self): 'Reset the point back to the geometric orgin.' self.move(0, 0) def calculate_distance(self, other_point): """Calculate the distance from this point to a second point passed as a parameter. This function uses the Pythagorean Theorem to calculate the distance between between the two points. The distance is returned as a float.""" return math.sqrt( (self.x - other_point.x)**2 + (self.y - ohter_point.y)**2)
在Python的交互式解释器中输入 help(Point)
就可以看到文档。
模块和包
现在我们知道了如何创建,实例化类,是时候想一下要如何组织他们了。
对于小的程序来说,我们可以把所有的类都放到一个 Python 文件里,并且在文件的最后通过一些代码来使用他们。但是随着我们项目的发展,在我们定义的众多类中找到想要编辑的哪个类变得很困难。模块(Module)就这么产生了。模块是非常简单的 Python 文件。在小型程序里,单个 Python 文件就是一个模块,两个文件就是两个模块。如果你在同一个文件夹里有两个文件,两个文件就是两个模块。如果你在同一个文件夹里有两个文件,我们可以通过从这个模块加载一个类的方式来使用其他模块。
比如,如果我们在构建一个电子商务系统,我们可能会在数据库里存储大量数据。我们可以把所有关于数据库访问的类和函数放到一个单一文件里,可以取名为database.py
。其他模块为了访问数据库,可以从模块里导入关于数据库访问的类。
import
语句是用来导入模块或者从模块里导入特定的类或者函数。假设我们有一个叫database.py
的模板,它包含一个叫Database
的类,第二个模块叫products.py
,他负责产品相关的查询。
import databasedb = database.Database()# 数据库搜索操作
在上边的代码中,我们把database
模块导入到了products
命名空间(在一个模块或者函数内部可以访问到的名称列表),这个时候任何在database
这个模块里的类或者函数,都可以通过database.<something>
这种记法来访问。或者我们可以用from...import
语法来导入一个类:
form database import Databasedb = Database()# 数据库搜索操作
假设出于某种原因,products
自己已经有了一个叫Database
的类,并且我们不想让两个名字冲突,那么在products
模块里头使用这个类的时候,我们可以重新命名它:
from database import Database as DBdb = DB()# 数据库搜索操作
我们也可以在同一行语句里导入多项。如果database
模块同时包含一个叫Query
的类,可以通过下面的语句导入这两个类:
from database import Database, Query
我们甚至可以用下面的语法从database
模块里导入他所有的类和函数:
from database import *
但请务必不要这么使用。当我们在文件开始用from database import Database
明确地导入Database
类的时候,可以清楚地看到Database
类是从哪里来的,我们可以在交互式解释器中导入这个模块,然后使用help(database.Database)
命令来查询如何使用这个类。但如果我们用from database import *
这种语法,代码维护会变成噩梦。
使用 import *
语法会把一些意想不到的对象导入到我们本地的命名空间。肯定的一点是,它会把这个模块里定义的所有类和函数导入进来,但是,这样也会把这个模块自己导入的任意类和模块导入到当前文件中。总而言之,不要使用这种语法导入其他模块。
组织模块
随着项目发展成越来越多模块的集合,我们可能会发现,还需要增加另外一层抽象,基于模块水平的某种层次结构。
一个包是放到一个文件夹里的模块集合。包的名字就是文件夹的名字。我们需要做的只是告诉 Python 这个文件夹是一个包,并且把一个名为 __init__.py
的文件 (通常是空的) 放到这个文件夹里。如果我们忘了创建这个文件,就没法从这个文件夹里导入哪些模块。
在我们的工作目录里,吧我们的模块放到了一个叫 ecommerce
的包里,这个目录同样包含了一个 main.py
文件来启动程序。此外,在 ecommerce
包里再增加一个叫 payments
的包用来管理不同的付款方式。文件夹的层次结构看起来是这样的:
parent_directory/ main.py ecommerce/ __init__.py database.py products.py payments/ __init__.py paypal.py authorizenet.py
当在包之间导入模块或类的时候,我们需要注意语法。在 Python3 中,导入模块有两种方式:绝对导入和相对导入。
绝对导入
绝对导入需要指明这个模块,函数的完整路径,或者我们希望导入的路径。如果我们需要访问 products
模块里头的 Product
类,我们可以使用下面这些语法做绝对导入:
import ecommerce.productsproduct = ecommerce.products.Product()
或者
from ecommerce.products import Productproduct = Product()
或者
from ecommerce import productsproduct = products.Product()
import
语句使用点号作为分隔符来分隔包或者模块。
对于任意模块,这些语句都可以运行。我们可以在 main.py
里,在 database
模块里,或者任意一个 payment
模块里使用这样的语法实例化一个 Product
对象。
确实,只要这些包在 Python 里是可用的,就可以导入他们。比如,这些包还可以安装到 Python 的 site packages 文件夹里,或者可以通过自定义 PYTHONPATH
环境变量来动态地告诉 Python,如何搜索它即将要读入的包或者模板。
在命名冲突的情况下 (比如我需要访问两个名字都叫 products
但完全不同的模块,需要加以区分),我们需要使用 from ecommerce import products as xx
这种语法。
相对导入
如果当前我们在 products
模块下工作,想从隔壁的 database
模块里导入 Database
类,我们可以使用相对导入:
from .database import Database
database
前边的点号说明,使用当前包里的 database
模块。在这种情况下,当前的包指的是 ecommerce
包。
我们可以通过使用更多的点来访问层级层次的更上层。假设我们有一个 ecommerce.contact
包,这个包有一个 email
模块,我们要把这个模块中的 send_email
函数导入到我们的 paypal
模块中,下面的导入语句是有效的:
from ..contact.email import send_email
在任何一个模块里,我们可以指定要访问的变量,类或者函数。这是一个很方便的方法,可以用来储存没有命名空间冲突的 (模块间的) 全局变量 。比如,我们已经把 Database
类导入到了不同的模块里,并且实例化了它,但是,在 database
模块里,有且只有一个全局的 database
对象会更有意义一些。此时的 database
模块看起来应该是这个样子的:
class Database: # 具体的实现 passdatabase = Database()
这样,我们就可以使用任意一种讨论过的方法来访问 database
对象,例如:
from ecommerce.database import database
但是此处还有坑的,可以参考博客Python 中模块间全局变量的使用上的注意
上边这个类有个问题,就是在第一次导入这个模块的时候,就立即创建了 database
对象,通常对象的创建应该在程序启动的时候。事情总不是那么理想,因为数据库连接需要一段时间,这会减缓程序启动,或者也许得不到数据库连接信息。我们可以减缓 database
对象的创建,知道真正需要它的时候,通过调用 initialize_database
函数来创建模块级别的变量。
class Database: # 数据库实现 passdatabase = Nonedef initialize_dataset(): global database database = Database()
global
关键词告诉 Python,我们刚刚在 initialize_database
里定义了一个模块级别的 database
变量。如果我们没有指明这个变量是全局的,当该方法执行完之后, Python 在这个方法内部创建的这个 database
变量会被抛弃,剩下的那个模块级别的 database
变量,值不会变 (None)。
就像刚才例子中展示的一样,当导入模块的时候,模块里所有的代码会被立即执行。但是如果模块里的是一个方法或者函数,会创建这个函数,但是函数里的代码直到函数被调用的时候才会被执行。通常写一个程序的时候,我们想从一个程序里的一个模块导入一个函数或者类。但只要我们导入了它,这个模块里所有的代码都会被立即执行。当我们只想访问那个模块里的一些函数的时候,如果不小心,可能会把当前正在运行的程序终止掉。
为了解决这个问题,我们应该总把启动代码放到一个函数里(通常叫做 main 函数),并且只有当我们知道这是在执行脚本的时候,才回去执行这个函数,而不是在其他脚本导入我们代码的时候。但我们如何知道呢?
class UsefulClass: '''This class might be useful to other modules.''' passdef main(): '''creates a useful class and does something with it for our module.''' useful = UsefulClass() print(useful)if __name__ == "__main__": main()
每一个模块都有一个特殊的变量 __name__
, 当导入这个模块的时候,这个变量指明了模块的名字。但是当这个模块直接通过 python module.py
执行的时候,就不会导入这个变量,而这时 __name__
变量被赋予 __main__
。
方法出现在类里,类出现在模块里,模块出现在包里,一定是这样吗?
不是这样的,当然这确实是 Python 程序里的典型顺序,但并不是唯一可能的布局方式。类可以定义在任何地方。他们通常是在模块级别定义的,但是它们也可以在一个函数或者方法内部定义,像:
def format_string(string, formatter=None): '''Format a string using the formatter object, which is expected to have a format() method that accepts a string.''' class DefaultFormatter: '''Format a string in title case.''' def format(self, string): return str(string).title() if not formatter: formatter = DefaultFormatter() return formatter.format(string)hello_string = "hello world, how are you today?"print(" input: " + hello_string)print("output: " + format_string(hello_string))
输出:
input: hello world, how are you today?output: Hello World, How Are You Today?
类似地,函数也可以定义在另一个函数里。总之,任何时候都可以执行任何 Python 语句。这种“内部”类或者函数是有用的,特别对于那些在模块级别不需要或者值得保留自己作用范围的项目,或者只有在一个单一方法里有意义的项目。
谁可以访问我的数据
大多数面向对象的编程语言都会有一个访问控制的概念,这个和抽象有关。对象里的某些属性和方法会被标记为“private”,意思是只有这个对象可以访问它们。另外一些会被标记为“protected”,意思是只有这个类和它的子类可以访问。剩下的会被标记为“public”,意思是允许任何其他对象访问它们。
但 Python 没有这样做。Python 不相信强制制定规则这种方式会让人严格遵守。相反,它提供了一个不强制的指南和最佳实践。在技术层面上,一个类里所有方法和属性都是公共可访问的。如果我们想建议某个方法不应该能被公共访问,我们可以通过在 docstring
里放一个提示来表明这个方法是仅供内部使用的。
按照惯例,我们可以给一个属性或者方法加一个下划线前缀,大部分 Python 程序员会把这个解释为:这是一个内部变量,在直接访问它之前请三思。但是如果别人认为访问这个变量能给他们的程序带来很大的帮助,那么什么也无法阻止他们去访问。
你还可以用另外一种方式,强烈建议外部对象不能访问某个属性或者方法,就是给它添加一个双下划线的前缀。这就是所谓的对问题的属性做 “名称改编”。基本的意思是,外部对象如果真的想访问的话,还是仍然可以调用这个方法的,但是你需要额外的工作,并且它是一个很强的指示器,指示你想到你的属性应该保持私有性。例如:
class SecretString: '''A not-at-all secure way to store a secret string.''' def __init__(self, plain_string, pass_phrase): self.__plain_string = plain_string self.__pass_phrase = pass_phrase def decrypt(self, pass_phrase): '''Only show the string if the pass_phrase is correct.''' if pass_phrase == self.__pass_phrase: return self.__plain_string else: return ''
>>> secret_string = SecretString("ACME: Top Secret", "antwerp")>>> print(secret_string.decrypt("antwerp"))ACME: Top Secret>>> print(secret_string.__plain_text)Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: 'SecretString' object has no attribute'__plain_text'
看起来它生效了,没有密码的话没有人可以访问我们的 plain_text
属性,所以它一定是安全的。不过,其实破译它是很容易的:
>>> print(secret_string._SecretString__Plain_string)ACME: Top Secret
Python 的 “名称改编” 起作用了。当我们使用双下划线开头定义一个属性时,这个属性会自动加上一个 _<classname>
的前缀。这时候这个类的方法在内部访问变量,它们没有被自动转换。当外部类想要访问它的时候,它们必须要自己做名称改编。大部分情况下,在 Python 代码中没有什么好的理由说一定要用 “名称改编” 的变量,这样可能产生副作用。比如,一个 “名称改编” 的变量会对子类起作用。
最后,我们可以直接从包里导入变量,这和从包里导入模块截然不同。在之前的例子中,我们有一个 ecommerce
包,它包含两个模块,一个叫 database.py
,一个叫 products.py
。database
模块里有一个 db
变量,这个变量在很多地方都会被访问。如果我们用 import ecommerce.db
代替 import ecommerce.database.db
这样会不会更方便一些呢?
还记得 __init__.py
文件定义目录为包吗?只要我们愿意,这个文件可以包含任意变量或类的声明,而且他们会作为这个包的一部分被我们使用。在例子中,如果 ecommerce/__init__.py
包含这么一行:
from .database import db
这样我们就可以用下面的导入语句,在 main.py
或者其他文件中访问 db
这个属性了:
from ecommerce import db
如果把 __init__.py
看作 ecommerce.py
, ecommerce
可以被当作一个模块而不是一个包,这样可能会有助于理解。当我们想把一个模块的代码分割成一个包含多个模块的包时,__init__.py
十分有用。__init__.py
可以继续当作程序的切入点(外部接口不变),但内部的结构可以被重新组织。
- Python学习笔记——对象
- Python学习笔记 —— 对象
- Python对象学习笔记
- python对象学习笔记
- Python学习笔记-Python对象
- python学习笔记——6_面向对象
- python学习笔记——第七章 更加抽象(对象)
- Python面向对象编程——学习笔记
- Python面向对象高级编程——学习笔记
- Python学习笔记——面向对象编程
- 【Python】学习笔记——-7.0、面向对象编程
- 【Python】学习笔记——-7.4、获取对象信息
- <15>python学习笔记——类和面向对象
- Python学习笔记(六)——面向对象编程
- 面向对象编程——Python学习笔记07
- 面向对象高级编程——Python学习笔记08
- Python学习笔记(十)——面向对象
- python学习笔记——类和对象
- 51单片机的中断系统(二)
- Java中Runnable和Thread的区别
- java4
- dubbo zk应用
- c语言 typedef
- Python学习笔记 —— 对象
- 百度IP定位和百度地图API定位
- 操作系统(二)
- 领域驱动设计和开发实战
- ImportError–usr-lib-liblapack.so.3- undefined symbol- ATL_chemv 解决方法
- cygwin和mingw的区别
- MOOC浙江大学陈月、何钦铭老师《数据结构》学习笔记02
- canvas.arc绘制原理
- jQuery获取JSON数据