【二】Python对象
来源:互联网 发布:如何管理淘宝网店 编辑:程序博客网 时间:2024/06/06 18:43
创建Python对象
在Python3中创建一个类通常是这样的:
class MyClass: pass
类的定义以关键字class开头,之后跟着类的名字,以冒号结尾。
Python代码风格指南“PEP8”建议类的名字应使用驼峰记法,即每个单词的首字母大写。
紧跟着是类的定义,换行缩进4个空格。这里写了pass是因为目前这个类不需要执行任何东西,但我们依然可以实例化这个类,在idle中执行这样的操作:
>>> a=MyClass()>>> b=MyClass()>>> a<__main__.MyClass object at 0x7fb69a076a20>>>> b<__main__.MyClass object at 0x7fb69a0769e8>
这里打印的内容告我们:a.b分别是MyClass类的对象,内存地是多少。
添加属性
我们创建了一个基本的类,但它没有任何行为,也没有数据。要添加属性可以通过点记法个一个实例化的对象赋予任意属性:
class Ponit: passp1=Point()p2=Point()p1.x=5p2.x=4p1.y=3p2.y=6print(p1.x,p1.y)print(p2.x,p2.y)
run
5 43 6
这段代码创建了一个没有任何数据和行为的空Point类,然后实例化了p1,p2,给两个实例对象赋x,y的坐标值,并打印。
点记法的格式是这样的:object.attribute=value,用这个规则来为对象赋值。value可以是Python内置的数据类型、其他对象、一个函数、一个类。
让类做一些事情
给Point类添加一些内容,让它有点“意思”。可以创建一个叫reset的方法,这个方法用来把点移动到原点,并且这个方法不用任何参数(实际上是没有多余的参数,后面会讲到)。
class Point: def reset(self): self.x=0 self.y=0p=Point()p.reset()print(p.x,p.y)
结果不用说
0 0
方法和函数的相同点
Python中方法的定义格式和函数相同:以关键字def开头,紧跟着一个空格和名称,名称后紧跟一对小括号,括号内填写这个函数的参数,然后以冒号结尾;下行缩进后写出这个函数的实现语句,它可以操作对象本身和任意传入的参数,前提是这个方法人为这个参数合法。
方法和函数的不同点
所有的方法都要有一个self参数(其实你可以命名为this,me,都可以,self是约定俗成的),这是对调用这个方法的对象的引用。
方法其实就是个函数,在类定义中的函数
你会注意到,p.reset()这个语句并没有写参数,Python知道我们要引用的self是p,即点记法的对象,所以程序自动把p传给了self。
这并不绝对。
还有一种方法可以带参数调用:
p=Point()Point.reset(p)print(p.x,p.y)
如何给方法传递多个参数?让我们添加一个新方法,这个方法允许我们把点移动到任意位置,不仅仅是移动到原点。
class Point: def move(self,x,y): self.x=x self.y=y def reset(self): self.move(0,0)p1=Point()p2=Point()p1.reset()p2.move(5,0)print(p1.x,p1.y)print(p2.x,p2.y)
结果是
0 0 5 0
在这个例子中,move方法就有多个参数被传入,然后reset实现是调用move,相当于move的特殊情况。
对象初始化
如果不去清晰给出参数,我们将不会得到一个有用的点:
>>> point=Point()>>> point.x=10>>> #不给它y坐标>>> print(point.x,point.y)Traceback (most recent call last): File "<pyshell#34>", line 1, in <module> print(point.x,point.y)AttributeError: 'Point' object has no attribute 'y'
报错也告诉我们了,没有y属性。每次都要输入x,y,很不方便。所以,我们要使用初始化方法,来简化操作。
除了名字比较特殊__init__
外,和普通的方法没有什么区别。双下划綫意味着“这是一个特殊方法,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(2,3)>>> print(point.x,point.y)2 3
现在就可以方便的指定坐标值了。但是我处于某些需要,创建的点都在原点上,该怎样简单一些呢?在初始化方法中设置默认参数就行了。
class Point: def __init__(self,x=0,y=0): self.move(x,y)
现在所有点,不带参数时将会产生在原点。
解释你的作品
当你文思泉涌写了一个精美绝伦的类时,你的小伙伴居然没看懂,这就尴尬了。为了方便其他人参考阅读你的精彩作品,你可以在代码阶段写入一些文字,解释每个对象是什么,每个方法是干什么的。
Python的docstring提供了对这种文档的支持,在每个类、函数、方法的定义阶段,可以添加一行多行注释文字,用来解释你这个东东是个什么玩意儿(注意要保持缩进状态)。
docstring应清晰准确地总结出它所描述的类或对象的用途,解释任何用法不那么明显的参数,并且也包含如何使用这些api的简单例子。任何一个api使用者应该知道的注意事项都应写在此处。
把上面的例子加入docstring:
class Point: '''Creating some points with arguments or default methods''' def __init__(self,x=0,y=0): '''initialization of the point, by default x=0,y=0''' self.move(x,y) def move(self,x,y): '''move method''' self.x=x self.y=y def reset(self): '''reset method''' self.move(0,0)>>>#类定义完毕>>> help(Point)Help on class Point in module __main__:class Point(builtins.object) | Creating some points with arguments or default methods | | Methods defined here: | | __init__(self, x=0, y=0) | initialization of the point, by default x=0,y=0 | | move(self, x, y) | move method | | reset(self) | reset method
看,高大上的说明就有了。这就是docstring的用处。
模块和包
模块module是为了方便大型项目的类管理。在小程序里,一个文件就是一个模块,两个文件就是两个模块。
假设建立一个电商平台,把所有关于数据库查询的类和函数放在一个模块中(database.py)其他模块(客户、产品清单等)为了访问数据库,可以从这个模块里调用相应的类和函数。
import语句是用来导入模块或者从模块导入特定的类、函数之用:
>>>import math>>>math.log2(2)>>>1.0
继续电商的例子,有一个叫database.py的模块,包含一个Database的类,另一个模块叫做product.py模块,负责产品相关查询,此时不用想太多,简化一下:product.py需要在database.py里实例化一个Database类,然后就可以在数据中进行产品相关查询。有两种方法来实现:
import databasedb=database.Database()
这里,把database模块导入了product命名空间(一个模块或者函数内部可以访问到的名称列表),这个时候任何在database模块里的类和函数都可以通过database.something来访问。下面的方法也可以调用模块:
from database import Databasedb=Database()
两种方法都可以方便的调用模块。
假如product模块中自己已经有一个叫Database()的类,但是我们必须要调用database模块中的Database()类,重名了,怎么办?那么在导入模块的时候可以给他一个小名:
from database import Database as DBdb=DB()
假如我们要导入多个类,可以写在一起:
from database import Database, Query
注意import database 和 from database import *(慎用)
很明显,后者把database 的所有自带的和自己定义的类和函数都导入了命名空间;而前者看似导入了整个模块,其实只是提供了一个访问模块的接口,在实际使用database.somethin之前,对命名空间影响不大。
组织模块
随着项目发展为越来越多的模块的集合,我们会发现,需要增加另外一层抽象,基于模块水品的某种层次模块。但是,一个文件就是一个模块,只能是一个模块。
但是,文件可以放在文件夹里,模块也是。一个包(package)就是放到一个文件夹里所有文件的集合。包的名字就是文件夹的名字,我们要做的是把一个__init__.py
的文件放到文件夹里。
在上面的例子中,我们把模块放到了一个叫ecommerce(电子商务)的包里,这个目录也包含一个main.py的文件来启动程序。此外,在ecommerce文件夹里新建一个payments的包用来管理不同的付款方式。文件层次看起来是这样的:
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的site packages文件夹里,或者可以通过自定义PYTHONPATH环境变量来动态地高速Python,如何搜索它即将要导入的包或者模块。
那么这么多的方法,我们该怎么选择呢?这取决于你的爱好和手头的应用程序:如果我想用的这个products模块里有几十个类和函数,一般是使用from ecommerce import droducts
导入模块的名字,然后通过products.Product来访问单一的类。如果仅需要products模块中的某几个类,就推荐使用from ecommerce.products import Product
来导入这几个类。记住你可以做任何事来让你的代码更加优雅简洁。
相对导入
其实,玩过Unix-like系统的都明白相对路径和绝对路径的用法,Python也是一样的用法。比如:当前我们在products模块(其实上就是一个文件)下工作,想导入“隔壁”的database模块里的Database类,使用相对导入语法:from .database import Product
,看到了吧,和系统命令一样的,点代表当前目录下,.database
就是当前目录下的database.py文件。
假设现在我正在编辑paypal.py文件,想导入products模块里的Product类,那么就这么操作:from ..products import Product
看,两个点,和系统的命令操作一模一样的。
模块导入二三事
在任何一个模块里,我们可以指定要访问的变量、函数、或者类。这是一个很方便的方法,用来存储没有命名空间冲突的全局状态。
比如,我们已经把Database类导入了不同的模块里,并且实例化了它,但是在database模块里,有且只有一个全局对象database会变得更有意义。此时database模块看起来是这样的:
class Database: #数据库实现 passdatabase = Database
这样我们就可以使用任意一种方式来访问database对象:
from ecommerce.database import database
上面的方法有个问题,第一次导入这个模块的时候,就立即创建了database对象,通常这会减缓程序启动时间,所以我们应该用间接的方式创建这个对象,真正需要它的时候,通过调用initialize_datebase 函数来创建模块级别的变量:
class Database: #数据库实现 passdatabase = Nonedef initialize_database(): global database database =Database()
global关键字告诉Python,我们刚刚在initialize_database里定义了一个模块级别的database变量。如果没有指明这个变量是全局(global),当initialize_database 方法执行完返回的时候,Python在这个方法内部创建的这个database变量会被抛弃,剩下那个模块级别的database变量,值不会变(None)。
这两个例子说明了一个东西:
- 当导入模块时,模块里的所有代码都会被执行。但是模块里的方法或者函数只会创建其本身,其内的代码直到函数(方法)被调用才会执行。
but,有时候我们只想要某个模块内部的某个函数,可是导入这个模块时,模块内所有的外层(相对于方法、函数的定义语句为内层)代码都执行了一遍,如果不小心,我们正在运行的程序会被终止掉。为了解决这个问题,我们应该把启动代码放到一个一个函数里(通常叫做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__
,当导入了这个模块的时候,这个变量指明了模块的名字,if判断失效;但是在命令行执行这个模块(文件)的时候,__name__
变量就被赋值为__main__
,此时if判断生效,即可执行被这个判断管理的函数。这样做的好处是:
- 防止你在文本中导入模块的时候牵扯到多余的代码;
- 可以让模块运行额外的代码,可以起到模块测试之用。
方法出现在类里,类出现在模块里,模块出现在包里,所有的代码都是这样的吗?
非也。请看例子:
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?
format_string函数接收一个字符串和一个可选的格式化对象作为参数,然后执行格式化字符串操作。如果没有提供格式和方法,函数会自己创建一个格式化方法,作为一个本地的类并实例化它。既然是在函数内部创建的,这个类就不能放访问这个函数外面的任何地方。类似的,函数也可以定义在另一个函数里面。总之,任何时候都可以执行任何Python语句。这种“内部”类或函数都是有用的,特别对于那些在模块级别不需要或值得保留自己作用范围的“一次性”项目,或者只有在一个单一方法里有意义的项目。
访问控制
大多数面向对象编程语言都有一个“访问控制”的概念。这个和抽象有关。
主要有三个类型:
1. 私有(private):只有这个对象可以访问。
2. 受保护的(protected):只有这个类和它的子类可以访问。
3. 公共的(public):允许任何其他对象访问。
Python不会直接限制访问,但是有其独特的表示方法。在技术层面上,一个类所有的方法和属性都是可以公共访问的。如果我们建议某个方法不应该被公共访问,可以通过在docstring里放一个提示来说明这个方法是否是内部使用的,或者解释面向公共的API如何工作。
我们也可以给一个属性或者方法加一个下划线前缀,表明“这是一个内部变量,在直接访问他们前请三思”
例如:
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("top secret","123")>>> print(secret_string.decrypt("123"))top secret>>> print(secret_string.__plain_string)Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> print(secret_string.__plain_string)AttributeError: 'SecretString' object has no attribute '__plain_string'
看起来在没有密码的情况下,是不能访问plain_string属性的,但是它真的不能访问吗?
>>> print(secret_string._SecretString__plain_string)top secret
这样,我们的密码就被破解了。当我们使用双下划线开头定义一个属性时,这个属性会自动加上一个_<calssname>
的前缀。这是这个类的方法在内部访问,就不用完整输入_<calssname>
,不会被自动转换;当外部类想要访问的时候,就要完整输入_<calssname>
。所以名称改编不能保障隐私,只能强烈建议要保护隐私。这是一个约定俗成的规矩。
__init__.py
的利用
前面就提到了__init__.py
这个文件可以是空的,作用是放在某个文件夹下使这个文件夹成为包可以导入调用,那么它一定要空吗?
当然不是!我们前面所说的都是从包里导入模块,从模块里导入变量。在前面的例子中我们有一个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
导致ecommerce.py这个文件是一个模块而不是一个包,就是__init__.py
在起作用,如果你想把所有代码都放在一个单独的模块里,过后又决定把它拆分为一个包里的多个包,__init__.py
也可以帮助到你。其他模块想要访问这个新包,__init__.py
仍然是一个切入点,但是在内部,代码仍然可以被组织成不同的模块或者子包。
总结
本章主要学习了:
- 类的语法
- 属性和方法
- 初始化函数和构造函数
- 模块和包
- 相对导入和绝对导入
- 访问控制以及它的局限性
- 【二】Python对象
- 面向对象的python(二)
- Python-面向对象 (二 继承)
- Python面向对象(二)
- Python进阶(二)Python对象类型
- python面向对象(二) 内置类方法
- Python面向对象编程(二)
- Python入门教程--类和对象(二)
- Python面向对象编程(二)
- python系列之 - 面向对象(二)
- Python面向对象编程(二)
- Python面向对象编程(二)
- Python自学笔记二、面向对象编程
- python系列二(面向对象)
- Python 面向对象编程(二)
- python面向对象(二)之封装
- PYTHON的内置对象 (二)字符串对象
- Python 面向对象(二)—— 获取对象信息
- R-FCN算法的Caffe实现
- MVC2.0中的HtmlHelper大全
- java多态(接口与实现)之静态代理
- 学习Struts2
- 如何使用Android Studio把自己的Android library分发到jCenter和Maven Central
- 【二】Python对象
- 移动端h5页面不同尺寸屏幕适配兼容方法
- JQuery(2)
- 版本控制
- 类文件结构
- 配置nginx的反向代理及负载均衡
- jQuery事件(补6月2日)
- 汇编语言编程实例---串行控制七段数码管
- 委托中的匿名方法和lambda表达式