模块
在Python中,一个.py文件就称之为一个模块(Module)。
为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。如下mycompany包
mycompany
├─ init.py
├─ abc.py
└─ xyz.py
请注意,每一个包目录下面都会有一个init.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。init.py可以是空文件,也可以有Python代码,因为init.py本身就是一个模块,而它的模块名就是mycompany。
模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。
创建自己的模块时,要注意:
模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。
1.使用模块
#!/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])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用author变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
2.作用域
在一个模块中(也就是一个.py文件)
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
类似xxx这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的author,name就是特殊变量,hello模块定义的文档注释也可以用特殊变量doc访问,我们自己的变量一般不要用这种变量名;
类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
3.安装第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。
一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是:
pip install Pillow
面向对象编程
我们以一个例子来说明面向过程和面向对象在程序流程上的不同之处。
假设我们要处理学生的成绩表,为了表示一个学生的成绩.
#面向过程:
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }
#而处理学生成绩可以通过函数实现,比如打印学生的成绩:
def print_score(std):
print('%s: %s' % (std['name'], std['score']))
#面向对象
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))
#给对象发消息实际上就是调用对象对应的关联函数,
#我们称之为对象的方法(Method)。的程序写出来就像这样:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
1.类和实例
#定义类是通过class关键字:
#Student是类名 通常是大写开头的单词
#object 表示该类是从哪个类继承下来的 如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
class Student(object):
def __init__(self, name, score): #第一个参数永远是self,表示创建的实例本身
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
bart = Student('Bart Simpson', 59) #使用
bart.name = 'Bart Simpson' #可以自由地给一个实例变量绑定属性
2.访问限制
在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
需要注意的是,在Python中,变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用name、score这样的变量名。
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
3.继承和多态
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承有什么好处?最大的好处是子类获得了父类的全部功能。
当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。
判断一个变量是否是某个类型可以用isinstance()判断:
isinstance(a, list)
#True
4.静态语言 vs 动态语言
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
1.使用type()
a=123
print(type(a)) #
print(type('str')) #
print(type(None)) #
print(type(abs)) #
print(type(123)==type(456)) #True
print(type('abc')==str) #True
print(type('abc')==type(123)) #False
import types
def fn():
pass
print(type(fn)==types.FunctionType) #True
print(type(abs)==types.BuiltinFunctionType) #True
print(type(lambda x: x)==types.LambdaType) #True
print(type((x for x in range(10)))==types.GeneratorType) #True
2.使用isinstance()
能用type()判断的基本类型也可以用isinstance()判断:
isinstance(d, Dog) and isinstance(d, Animal) #True
并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
isinstance([1, 2, 3], (list, tuple)) # True
isinstance((1, 2, 3), (list, tuple)) #True
3.使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
dir('ABC')
#['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似xxx的属性和方法在Python中都是有特殊用途的,比如len方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的len()方法,所以,下面的代码是等价的:
len('ABC')
== 'ABC'.__len__()
我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法:
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
hasattr(obj, 'x') # 有属性'x'吗? True
hasattr(obj, 'y') # 有属性'y'吗? False
setattr(obj, 'y', 19) # 设置一个属性'y'
hasattr(obj, 'y') # 有属性'y'吗? True
getattr(obj, 'y') # 获取属性'y'
getattr(obj, 'z') # 获取属性'z' 如果试图获取不存在的属性,会抛出AttributeError的错误:
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
hasattr(obj, 'power') # 有属性'power'吗? True
print(getattr(obj, 'power')) # >
实例属性和类属性
class Student(object):
count = 0 #类属性
def __init__(self, name):
Student.count+=1
self.name = name #实例属性
面向对象高级编程
1.使用slots
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性
class Student(object):
pass
s = Student()
s.name = 'Michael' # 动态给实例绑定一个属性
def set_age(self, age): # 定义一个函数作为实例方法
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
s.set_age(25) # 调用实例方法
#但是,给一个实例绑定的方法,对另一个实例是不起作用的:
#为了给所有实例都绑定方法,可以给class绑定方法:
def set_score(self, score):
self.score = score
Student.set_score = set_score
s.set_score(22)
print(s.score)
但是,如果我们想要限制实例的属性怎么办?
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身的slots加上父类的slots。
2.使用@property
Python内置的@property装饰器就是负责把一个方法变成属性调用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter #注意这个
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
3.多重继承
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系
4.定制类
__author__ = 'huangxianchang'
class Student(object):
age=12 #类属性
def __init__(self):
self.name = "Student——init"
# 实例()时调用
def __call__(self, *args, **kwargs):
print("call")
return "__call__"
# print(实例)时调用 __repr__()是为调试服务的
def __repr__(self):
return "__repr__"
# print(实例)时调用 __str__()是为用户服务的
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
#被用于for ... in循环,类似list或tuple那样
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
#要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:
# 但是list有个神奇的切片方法: list(range(100))[5:10]
#原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
#与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。
# 最后,还有一个__delitem__()方法,用于删除某个元素。
#当调用不存在的属性时,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值:
def __getattr__(self, attr):
if attr=='score':
return 99
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
#默认返回就是None
#注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。
# 类方法,只能访问类变量,不能访问实例变量
@classmethod
def talk(cls):
print(cls) # 不能访问self.name
# 静态方法不访问实例变量和类变量,实例.静态方法()时,不会自动传入的id。一个方法不要访问了类和实例变量,但类
# 又要用这个方法时可以定义为静态方法。
@staticmethod
def walk(cmd):
print("the cmd is :%s"% cmd) # 不访问self.name和类.age,
# 将方法转为属性,方法他时,不带括号。实例.方法。只有输出,但不接收输入时可以使用。
@property
def shout(self):
print("shout:%s"%self.name)
return 18
# 这个property.setter装饰的方法必须是被property装饰过的方法。
# 否则报错:TypeError: descriptor 'setter' requires a 'property' object but received a 'function'
@shout.setter
def shout(self,arg):
print("shout:%s, %s"%(self.name,arg))
return 18
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。
callable(Student())
4.使用枚举类
#枚举
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
#这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
#默认从1开始计数
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
#@unique装饰器可以帮助我们检查保证没有重复值。
print(Weekday(1)) #Weekday.Mon
#既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量
5.使用元类
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。
print(type(Hello))
type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
h = Hello()
h.hello()
print(type(Hello)) #
print(type(h)) #
要创建一个class对象,type()函数依次传入3个参数:
class的名称;
继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。
6.metaclass
metaclass,直译为元类,简单的解释就是:可以把类看成是metaclass创建出来的“实例”。
我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:
定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:
# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:
class MyList(list, metaclass=ListMetaclass):
pass
当我们传入关键字参数metaclass时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。