目录
1.1.2 将自己写的多个.py文件规范化成外部类,并创建__init__.py
1. 模块Module
1.1 Module concept
python Module 模块
模块的作用在于提高代码的可维护性,避免函数名和变量名冲突。
一个abc.py文件就是一个abc模块
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是标准注释,第1行注释可以让这个
hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用
__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;以上就是Python模块的标准文件模板
模块中的函数function
在一个模块中,我们会定义许多函数和变量,但有的变量和函数我们希望给别人使用,有的函数我们希望仅仅在函数内部使用。在python中,这是通过下划线前缀(_)来实现的。
- 正常的函数和变量名是公开的(public),可以直接引用,e.g. abc, x123,
- 类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__, __name__就是特殊变量。
- 类似_xxx和__xxx这样的变量就是非公开的(private),不应该被直接引用,e.g. _abc, __abc。private函数和变量不应该被直接引用,而不是不能被直接引用,只是不应该被别人引用。
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
这个模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了。这样,调用greeting()函数不用关心内部private函数细节,这也是一种非常有用的代码封装和抽象方法。
python package 包
为了避免abc模块名与其他模块名冲突,引入一个顶层包名package,比如:mycompany,并按照如下目录存放。
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
引入了包以后,只要顶层包名不与其他包冲突,那所有模块都不会和别人冲突。比如,引入包后,abc.py模块名字变成了mycompany.abc。
Notice:每一个包目录下面都会有一个__init__.py文件,这个文件是必须存在的。否则,python就把这个目录当成了普通目录,而不是一个package。__init__.py可以是空文件,也可以有python代码,__init__.py本身就是一个模块,而它的模块名就是mycompany。
类似的,可以有多级目录,组成多级层次的包结构,比如:
mycompany
├─ web
│ ├─ __init__.py
│ ├─ utils.py
│ └─ www.py
├─ __init__.py
├─ abc.py
└─ utils.py
mycompany.utils和mycompany.web.utils两个模块名。
1.1.1 简单地导入自己写的.py文件
将a.py与b.py放在同一项目路径下,然后在另一个b.py文件中执行import a.py,然后我们就可以在b.py中调用a.py中的函数了
import a
a.py()
参考:https://jingyan.baidu.com/article/08b6a591810daf14a8092204.html
1.1.2 将自己写的多个.py文件规范化成外部类,并创建__init__.py
参考:python_baike_spider/baike_spider at master · fifths/python_baike_spider · GitHub
1.1.3 将自己的程序封装成外部包
参考:
Python实现封装打包自己写的代码,被python import_Python_脚本之家; python如何将自己写的代码打包供他人使用 - smileyes - 博客园; Python将自己写的模块进行打包 - 学一点也是好 - 博客园
2. 面向对象
面向对象,即java的编程思想,与原有的面向过程编程思想不同。一个数据类型抽象化创建为对象(class),这个对象拥有name和property两种属性,面向对象思想编程有三大特点:
- 数据封装。在于复用
- 继承
- 多态
2.1 类和实例
面向对象的设计思想是从自然界来的,因为在自然界中,类(class)和实例(instance)的概念是很自然的。比如:鸟class,可以实例化一个对象instance,这个实例有fly()方法属性、size和color颜色属性。
所以,面向对象的设计思想是抽象出class,根据class创建instance。
- 面向对象的抽象程度比函数要高,因为一个class既包含数据,又包含操作数据的方法。
- 每个由class创建的具体实例instance,都拥有相同的方法,但各自的数据可能不同。
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))
2.1.1 定义类名
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧着着是(object),表示该类从哪个类继承下来。如果没有合适的继承类,就使用object,这是所有类最终都会继承的类。
定义好了Student类,就可以根据Student类创建出Student实例,创建instance是通过类名+()实现的。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__(分别有两根下划线)方法,在创建实例的时候,就把name, score等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
2.1.2 定义__init__函数
Notice:这里的__init__是初始化函数,不同于上面package __init__初始化模块!!!
注意到__init__方法的第一个参数永远是self,表示创建的实例本身。因此,在__init__方法内部,就可以把各种属性绑定到self,因此self就指向创建的实例本身。
__init__()初始化函数,用于声明初始化变量、函数functions,深度学习子层layer和模型model。
有了__init__方法,在创建实例的时候,就不能传入空的参数了!!必须传入与__init__方法匹配的参数,但self不需要传,python interpreter自己会把实例变量传进去:
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
2.2 数据封装
面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例都拥有各自的name和score数据。
虽然,我们可以定义一个函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样就把“数据”给封装起来了
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))
2.2.1 class定义一个方法
要定义一个方法,除了第一个参数是self外,其他和普通函数一样。
要调用这个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入。
>>> bart.print_score()
Bart Simpson: 59
这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出name和score,而如何打印,都是在Student类的内部定义的,这些数据和逻辑都被“封装”起来了,调用很容易,但却不用知道内部实现的细节
2.2.3 class summary
类是创建实例的模板,而实例是一个一个具体的对象,各个实例拥有的数据都相互独立,互不影响。
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据!
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无序知道方法内部实现的细节。
和静态语言不同,python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
2.2.4 访问限制
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
但是,从前面的Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性。
>>> bart = Student('Bart Simpson', 59)
>>> bart.score
59
>>> bart.score = 99
>>> bart.score
99
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,就实例instance变成一个私有变量(private),只有内部可以访问,外部不能访问。
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)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法。
class Student(object):
...
def set_score(self, score):
self.__score = score
通过函数修改,而不是修改__init__初始化变量的做法,其好处是可以对参数做检查,避免传入无效参数。
2.3 继承
在面向对象的程序设计中,当我们定义一个class时,可以从某个现有的class继承,新的class称为子类(Subclass),被继承的class称为基类、父类(Base class、Super class).
继承最大的好处就是子类获得了父类的全部功能。
class Animal(object):
def run(self):
print('Animal is running...')
dog = Dog()
dog.run()
cat = Cat()
cat.run()
'''
Animal is running...
Animal is running...
'''
2.3.1 子类可以增加一些方法
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
2.3.2 子类可以重写父类方法
当子类和父类都存在相同的run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
2.4 多态
多态是继承的另一种表现形式
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型
>>> isinstance(c, Animal)
True
c不仅仅是Dog,还是Animal。
def run_twice(animal):
animal.run()
animal.run()
>>> run_twice(Animal())
Animal is running...
Animal is running...
>>> run_twice(Dog())
Dog is running...
Dog is running...
允许新增animal子类,不必对run_twice()做任何修改。实际上,任何依赖animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
多态好处即在于,只要是父类的子类,就是可以按照父类进行操作,自动调用实际类型的run()方法。
2.5 获取对象信息、属性
2.5.1 type()判断对象类型
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
# 甚至可以判断一个变量指向函数或者class
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>
2.5.2 判断两个变量的type类型是否相同
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
2.5.3 使用isinstance()判断class类型
我们回顾上次的例子,如果继承关系是:object->Animal->Dog->Husky
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
isinstance()函数
判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
2.5.4 dir()函数获取对象所有属性和方法
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似__xxx__的属性和方法在python中都是有特殊用途的,比如__len__方法返回长度。实际上,在len()函数内部,它自动去调用该对象的__len__()方法。
仅仅把属性和方法列出来是不够的,配合getattr()、setattr()、hasattr()方法,我们可以直接操作一个对象的状态。
>>> obj = MyObject()
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
2.5.5 实例属性和类属性
- 实例属性
由于python是动态语言
根据类创建的实例可以任意绑定属性,给实例绑定属性的方法是通过实例变量,或者通过self变量。
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
- 类属性
给class绑定一个属性,可以直接在class中定义属性,这种属性就是类属性,归Student类所有。
class Student(object):
name = 'Student'
- attributes summary
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
3. 面向对象高级特性
3.1 __slots__属性
正常情况下,当我们定义了一个类class,创建了一个class的实例后,我么可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
class Student(object):
pass
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael
但是,如果我们想限制实例的属性怎么办?比如,只允许Student实例添加name和age属性。
为了达到限制的目的,python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性。
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
注意:__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
3.2 使用@property
装饰器(decorator)可以给函数动态加上功能。
对于类的方法,python内置的@property装饰器就是负责把一个方法变成属性调用的。
3.3 多重继承
如果需要混入额外的功能,通过多重继承就可以实现,这种设计称为MixIn。
把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。类似的,还可以定义出肉食动物(CarnivorousMixIn)和植食动物(HerbiboresMixIn),让某个动物同时同游好几个MinIn。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
3.4 定制类
类似__slots__这种形如__xxx__的变量或者函数名,这些在python中是有特殊用途的.
python的class中还有很多有特殊用途的函数,可以帮我们定制类,比如
- __str__
- __iter__
- __getitem__
- __getattr__
- __call__
3.5 枚举类
为枚举类型定义一个class类,然后,每个常量都是class的一个唯一实例。
python提供了Enum类来实现这个功能。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员:
3.6 使用元类
- type(),既可以返回一个对象的类型,又可以创建出新的类型。
- metaclass()元类,先定义metaclass,然后创建类 --> 控制类的创建行为。