模块
Python中,一个.py文件就称之为一个模块(Module)。
为了避免模块名冲突,Python引入了按目录来组织模块的方法,称为包(Package)。
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。如下:abc.py模块的名字就变成了my.abc,类似的,xyz.py的模块名变成了ur.abc。
my
├─ init.py
└─ abc.py
ur
├─ init.py
└─ abc.py
每一个包目录下面必须有一个__init__.py的文件,否则,Python就把这个目录当成普通目录,而不是一个包。
init.py可以是空文件,也可以有Python代码,init.py本身就是一个模块,它的模块名就是my。
可以有多级目录,组成多级层次的包结构。比如如下的目录结构:
my
├─ web
│ ├─ init.py
│ ├─ utils.py
│ └─ www.py
├─ init.py
├─ abc.py
└─ utils.py
文件www.py的模块名为my.web.www,两个utils.py文件的模块名分别为my.utils和my.web.utils。
自己创建模块时要注意命名,不能和Python自带的模块名称冲突。
使用模块
Python模块的标准文件模板:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
if __name__=='__main__':
test()
- 第1行和第2行是标准注释
- 第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行
- 第2行注释表示.py文件本身使用标准UTF-8编码
- 第4行是一个字符串,表示模块的文档注释,模块代码的第一个字符串会被视为模块的文档注释
- 第六行使用__author__变量声明模块的作者
使用模块:
- 导入模块:
import sys
- 通过模块名访问模块的方法和属性:
sys.argv
当运行模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该模块时,以下if判断将失败,因此,该if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
if __name__=='__main__':
test()
变量命名习惯:
- 正常变量:公开的,可以被直接引用,如:abc,x123,PI等
- 特殊变量:可以被直接引用,但是有特殊用途,形如
__xxx__
,如:__author__
,__name__
- 私有变量:非公开,不应该被直接引用,形如
_xxx
和__xxx
,如_abc
,__abc
等;
安装第三方模块
使用pip安装:pip install numpy
更加好用的方法:使用Anaconda
面向对象编程(OOP)
在Python中,所有数据类型都可以视为对象,每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
自定义的对象数据类型就是面向对象中的类(Class)的概念,类是对象的模板,定义类:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
使用类:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
代码中的bart和lisa称为实例(Instance),实例是类的具体。
对象拥有的函数称为对象的方法(Method)。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。
类和实例
OOP中最重要的概念就是类和实例
定义类使用class
关键字:class Student(Object): pass
:
- Student为类名
- (Object)表示该Student类继承自Object类
创建实例使用类名+():bart = Student()
Python可以自由的给实例添加属性,如给bart添加name属性:bart.name = 'Bart Simpson'
类
类作为对象的模板,需要设置一些对象的必备属性,通过类的__init__
方法实现:
class Student(Object):
def __init__(self, name, score):
self.name = name
self.score = score
__init__
方法的第一个参数永远是self,表示创建的实例本身。
有了__init__
方法,在创建对象时必须提供模板设置的属性(除了self,Python解释器会自己传入self):bart = Student('Bart Simpson', 65)
数据封装
在Student类中,每个实例拥有各自的name和score数据。
可以在类内部提供方法来访问这些属性,成为封装:
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart.print_score() # Bart Simpson: 65
定义一个方法,除了第一个参数是self外,其他和普通函数一样。
调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入。
封装还有利于给类添加新的方法。
访问限制
类向外部提供方法访问内部属性,可以隐藏内部的复杂逻辑。
但是上文的Student类的属性仍然可以在外部直接访问或修改。
为了限制内部属性的访问,Python规定类中以__
开头的属性名为私有变量,只能在内部被访问。
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
当需要获取或修改内部的属性时,可以在类中提供对应的方法:
class Student(object):
...
def get_name(self):
return self.__name
def set_score(self, score):
self.__score = score
继承和多态
继承
在OOP中,当创建新类时,可以选择继承自已有的类,继承可以简化类的实现。新类称为子类,被继承的类为父类。
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
上述代码中,定义类Animal
类,并提供了run()
方法,所以定义Dog
类时,只需要继承Animal
类,就拥有了Animal
的run()
方法,无需二次定义。
判断一个变量是否是某个类型可以用isinstance()判断:
isinstance(a, Animal) # True
isinstance(d, Dog) # True
isinstance(d, Animal) # True
因为Dog是从Animal的子类,当我们创建了一个Dog的实例d时,d的数据类型是Dog,但d同时也是Animal。
所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
多态
当在子类中重新定义run()
方法时,会覆盖父类中的方法,在运行时,会调用子类的run()
方法,称为OOP的多态特性。
class Cat(Animal):
def run(self):
print('Cat is running...')
可以定义一个函数,函数接收一个对象,执行该对象的run()
方法:
def go(animal):
animal.run()
对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用Animal或其子类对象上,由运行时该对象的确切类型决定,这就是多态。
并且,新增的任何Animal子类都可以直接传入go()
函数,不用对go()
方法做任何修改,这就是开闭
原则:
- 对扩展开放:允许新增Animal子类
- 对修改封闭:不需要修改依赖Animal类型的go()等函数。
静态语言 VS 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法传入go()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal或其子类。只需要保证传入的对象有一个run()
方法即可。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
获取对象信息
type()
Python提供了type()
函数用于获取对象的类型。
type()
函数返回对应的Class类型。
如果在if语句中判断,需要比较两个变量的type类型:type('123') == type(123) # False
判断基本数据类型可以直接写int,str等:type(123) == int # True
如果要判断一个函数对象,可以使用types模块中定义的常量:
import types
def fn():
pass
type(fn)==types.FunctionType # True
type(abs)==types.BuiltinFunctionType # True
type(lambda x: x)==types.LambdaType # True
type((x for x in range(10)))==types.GeneratorType # True
isinstance()
对于继承关系的class类型来说,可以使用isinstance()
函数。
isinstance()
判断的是一个对象是否是该类型本身,或者是该类型的子类实例。
比如继承关系:
# Object -> Animal -> Dog
a = Animal
d = Dog()
isinstance(a, Animal) # True
isinstance(d, Animal) # True
isinstance(d, Object) # True
isinstance(a, Dog) # False
对于基本类型,也可以使用isinstance()
判断类型:
isinstance('a', str) # True
isinstance(123, int) # True
isinstance(b'a', bytes) # True
isinstance()
还可以判断一个变量是否是某些类型中的一种:
isinstance([1, 2, 3], (list, tuple)) # True
isinstance((1, 2, 3), (list, tuple)) # True
总是优先使用isinstance()判断类型,因为可以同时判断指定类型及其子类
dir()
使用dir()函数会返回一个包含字符串的list,包含对象的所有属性和方法
>>> dir('qwe')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', ...,
'upper', 'zfill']
getattr()、setattr()及hasattr()
- hasattr():用于判断对象是否有指定的方法或属性
- setattr():用于为对象设置或添加属性
- getattr():用于获取对象的指定方法或属性,可以传入default参数,若属性不存在,则返回默认值
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
hasattr(obj, 'x') # True
hasattr(obj, 'y') # False
setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(obj, 'y') # True
getattr(obj, 'y') # 19
getattr(obj, 'z', 404) # 404
hasattr(obj, 'power') # True
getattr(obj, 'power') # <bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
fn = getattr(obj, 'power')
fn() # 81
实例属性和类属性
Python是动态语言,通过类创建的实例可以动态绑定属性。
给实例添加属性:
- 通过实例变量:
s.name = 'Bert'
- 在类初始化中通过self变量:
def __init__(self, name): self.name = name
给类自身绑定属性:
- 直接在类中定义属性:
class Student(object): name = 'Student'
在类中访问属性:
- 通过
self.name
访问实例属性 - 通过
Student.name
访问类属性