Python学习笔记(五)
1.模板
使用模板的好处?
- 提高代码的可维护性
- 被其他地方引用,加快开发进度
- 避免函数名和变量名冲突
模板的导入
一个.py
文件就是一个模板,Python还引入了包的概念,防止命名冲突。
请注意,每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件,也可以有Python代码。
Python使用import
语句导入一个模块。例如,导入系统自带的模块 math
:
import math
我们可以认为math就是指向模板的变量,通过math可以访问模板中所有公开的变量、函数和类。
如果我们希望只导入模板中的某几个函数,而不是全部,则可以用一下的语句:
from math import sin cos log
这样就导入了math模板里面的sin
、cos
、log
三个函数,其他函数都没有导入进来。
注意:
import xx
:导入模块对于模块中的函数,每次调用需要“模块.函数”来用。
from xx import fun
: 直接导入模块中某函数,直接fun()就可用。
当使用 from...import
导入函数引起了冲突。这时,可以给函数起个“别名”来避免冲突:
from math import log
from logging import log as logger # logging的log现在变成了logger
安装第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。
pip install 模板名
默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中,如果我们要添加自己的搜索目录,有两种方法:
直接修改sys.path,添加要搜索的目录:
sys.path.append('/Users/python_code/my_py_scripts')
这种方法是在运行时修改,运行结束后失效。
- 第二种方法是设置环境变量
PYTHONPATH
,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。
2.面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数
类和实例
在Python中,定义类是通过class
关键字:
class Student(object)
pass
创建实例是通过类名+()
实现的:
s = Student()
可以自由的为实例对象添加属性,不过一般都是通过一个特殊方法__init__()
方法强制指定对象属性,相当于java中类的构造函数:
class Student(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
s = Student('zhangsan','man')
print(s.name)
print(s.gender)
运行结果:
zhangsan
man
__init__()
方法的第一个参数永远是self
,它代表类实例本身。在创建对象的时候self
参数不需要传入。
为类定义访问数据的方法:
class Student(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
def get_name(self):
return self.name
def get_gender(self):
return self.gender
s = Student('zhangsan','man')
print(s.get_name())
print(s.get_gender())
运行结果:
zhangsan
man
定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入。
访问限制
为了让类的属性不能被外部随意访问,可以将类属性设为私有。在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
class Student(object):
def __init__(self,name,gender):
self.__name = name
self.__gender = gender
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
同时我们需要通过方法来访问内部私有属性:
def set_name(self,name):
self.__name = name
def set_gender(self,gender):
self.__gender = gender
def get_name(self):
return self.__name
def get_gender(self):
return self.__gender
注意:在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。
私有变量也不是一定不能从外部直接访问,是Python解释器自动改了变量名:
class Student(object):
def __init__(self,name,gender):
self.__name = name
self.__gender = gender
def set_name(self,name):
self.__name = name
def set_gender(self,gender):
self.__gender = gender
def get_name(self):
return self.__name
def get_gender(self):
return self.__gender
s = Student('zhangsan','man')
print(s._Student__name)
print(s._Student__gender)
运行结果:
zhangsan
man
不同的Python解析器可能会把__name
改成不同的变量名,因此直接从外部修改私有变量的值不会成功,也不建议直接在外部访问私有变量。
以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
继承和多态
继承最大的好处是子类获得了父类的全部功能,子类只需要添加自己特有的功能。
class Animal(object):
def run(self):
print("Animal is runing")
class Dog(Animal):
pass
class Cat(Animal):
pass
d = Dog()
c =Cat()
d.run()
c.run()
运行结果:
Animal is runing
Animal is runing
在子类初始化的时候必须先通过super
关键字调用父类的初始化方法:
class Person(object):
def __init__(self,name):
self.name = name
class Student(Person):
def __init__(self,name,score):
super(Student,self).__init__(name)
self.score = score
s = Student('zhangsan',88)
print(s.name)
print(s.score)
运行结果:
zhangsan
88
函数super(Student, self)
将返回当前类继承的父类,然后调用__init__
方法来初始化父类,否则继承自Person的Student类将没有name这一属性。
当子类中覆盖了父类中的方法,会调用子类自己的方法,而且子类实例既可以是子类类型,又可以是父类类型。
class Animal(object):
def run(Animal):
print("Animal is runing")
class Dog(Animal):
def run(self):
print("Dog is runing")
class Cat(Animal):
def run(self):
print("Cat is runing")
a = Animal()
d = Dog()
c = Cat()
a.run()
d.run()
c.run()
print(isinstance(a,Animal))
print(isinstance(a,Dog))
print(isinstance(a,Cat))
print(isinstance(d,Animal))
print(isinstance(d,Dog))
print(isinstance(c,Animal))
print(isinstance(c,Cat))
运行结果:
Animal is runing
Dog is runing
Cat is runing
True
False
False
True
True
True
True
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行。
多态的好处:在一个方法中,当我们传入子类型时,我们只需要接收父类型,然后会自动调用实际类型对应的方法。
class Animal(object):
def run(self):
print("Animal is runing")
class Dog(Animal):
def run(self):
print("Dog is runing")
class Cat(Animal):
def run(self):
print("Cat is runing")
def to_run(animal):
animal.run()
a = Animal()
d = Dog()
c = Cat()
to_run(a)
to_run(d)
to_run(c)
运行结果:
Animal is runing
Dog is runing
Cat is runing
获取对象信息
type()
type()
函数用来判断对象类型,基本类型都可以用type()
判断:
print(type(123))
print(type('123'))
print(type([1,2,3]))
print(type((1,2,3)))
运行结果:
<class 'int'>
<class 'str'>
<class 'list'>
<class 'tuple'>
如果一个变量指向函数或者类,也可以用type()
判断:
print(type(abs))
运行结果:
<class 'builtin_function_or_method'>
type()
返回的是Class类型。如果要判断一个对象是否是函数,可以使用types
模块中定义的常量:
import types
def fn():
pass
print(type(fn) == types.FunctionType)
print(type(abs) == types.BuiltinFunctionType)
print(type(lambda x : x) == types.LambdaType)
运行结果:
True
True
True
instance()
对于类的继承来说,使用instance()
更方便
class A(object):
pass
class B(A):
pass
class C(A):
pass
a = A()
b = B()
c = C()
print(isinstance(a,A))
print(isinstance(b,A))
print(isinstance(c,A))
运行结果:
True
True
True
还可以判断一个变量是否是某些类型中的一种:
print(isinstance(['1','2','3'],(list,tuple)))
运行结果:
True
dir()
如果要获得一个对象的所有属性和方法,可以使用dir()
函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:
print(dir('ABC'))
运行结果:
['__add__', '__class__', '__contains__', '__delattr__',
'__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__getitem__', '__getnewargs__',
'__gt__', '__hash__', '__init__', '__iter__',
'__le__', '__len__', '__lt__', '__mod__',
'__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__rmod__', '__rmul__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'capitalize', 'casefold', 'center', 'count',
'encode', 'endswith', 'expandtabs', 'find',
'format', 'format_map', 'index', 'isalnum',
'isalpha', 'isdecimal', 'isdigit', 'isidentifier',
'islower', 'isnumeric', 'isprintable', 'isspace',
'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip',
'maketrans', 'partition', 'replace', 'rfind',
'rindex', 'rjust', 'rpartition', 'rsplit',
'rstrip', 'split', 'splitlines', 'startswith', 'strip',
'swapcase', 'title', 'translate', 'upper', 'zfill']
__xx__
的属性和方法在Python中有特殊用途,比如len方法返回长度。当我们需要对象返回长度写,复写这个方法就可以了。
还可以通过getattr()
、setattr()
以及hasattr()
来直接操作一个对象的状态:
class Test(object):
def __init__(self):
self.x = 9
def power(self):
return self.x *self.x
t =Test()
print(hasattr(t,'x'))
print(hasattr(t,'power'))
setattr(t,"x",10)
print(getattr(t,"x"))
print(getattr(t,"power")())
运行结果:
True
True
10
100
如果试图获取不存在的属性,会抛出AttributeErro
的错误。可以传入一个default参数,如果属性不存在,就返回默认值:
print(getattr(t,'y',2))
运行结果:
2
注意:只有在不知道对象信息的时候,我们才会去获取对象信息
实例属性和类属性
直接在class
中定义的属性,这种属性是类属性,归类本身所有。而实例属性是通过实例绑定或者self
绑定的。实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。
类属性直接与类绑定,因此访问的类属性可以不需要创建类的实例,当然也可以通过实例访问:
class Person(object):
name = 'zhangsan'
p = Person()
print(p.name)
print(Person.name)
运行结果:
zhangsan
zhangsan
当修改类属性时,所有实例都会受到影响,在实例变量上修改类属性,只是相当于为该实例添加了一个实例属性而已:
class Person(object):
name = 'zhangsan'
p1 = Person()
p2 = Person()
p2.name = 'lisi'
print(p1.name)
print(p2.name)
print(Person.name)
运行结果:
zhangsan
lisi
zhangsan
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
面向对象的高级编程
使用slots
顾名思义,__slots__
是指一个类允许的属性列表,来限制该class实例能添加的属性。 为对象绑定不在属性列表里面的属性,程序将会得到AttributeError
错误:
class Person(object):
__slots__ = ('name','gender')
p = Person()
p.score = 6
运行结果:
Traceback (most recent call last):
File "D:\python_code\pythonTest\hello.py", line 5, in <module>
p.score = 60
AttributeError: 'Person' object has no attribute 'score'
注意:__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。
class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
__slots__ = ('score')
def __init__(self,name,gender,score):
super(Student,self).__init__(name,gender)
self.score = score
s = Student('Bob', 'male', 59)
s.name = 'Tim'
s.score = 99
print (s.score)
运行结果:
99
使用@property
@property
是一个装饰器。目的是把属性的get/set 方法“装饰”成属性调用:
class Student(Person):
def __init__(self,name,gender,score):
super(Student,self).__init__(name,gender)
self._score = score
@property
def score(self):
print("score(self) be called")
return self._score
@score.setter
def score(self,value):
print("score(self,value) be called")
if not isinstance(value,int):
raise ValueError('score must be a integer')
if value < 0 or value > 100:
raise ValueError('value must between 0~100')
self._score = value
s = Student('Bob', 'male', 59)
s.score = 5
print (s.score)
运行结果:
score(self,value) be called
score(self) be called
5
从上面的打印结果可以看出,s.score = 5
相当于在在执行set
方法,s.score
相当于在执行get
方法。
多重继承
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
class A(object):
def __init__(self, a):
print ('init A...')
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print ('init B...')
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print ('init C...')
class D(B, C):
def __init__(self, a):
super(D, self).__init__(a)
print ('init D...')
定制类
通过复写某些特殊意义的方法来自定义类。
__str__()
__str__()
方法类似java中的toString()
方法:
class Person(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
def __str__(self):
return 'Person(%s,%s)' %(self.name,self.gender)
p =Person('lisi','man')
print(p)
运行结果:
Person(lisi,man)
因为 Python 定义了__str__()
和__repr__()
两种方法,__str__()
用于显示给用户,而__repr__()
用于显示给开发人员。一种偷懒的方式:
__repr__ = __str__
__cmp__()
Python的 sorted()
按照默认的比较函数cmp
排序,但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 __cmp__()
:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
elif self.name > s.name:
return 1
else:
return 0
__len__()
要让 len()
函数工作正常,类必须提供一个特殊方法__len__()
,它返回元素的个数。
class Student(object):
def __init__(self,*args):
self.names = args
def __len__(self):
return len(self.names)
s = Student('Mike','Jane','molly')
print(len(s))
运行结果:
3
__iter__()
如果一个类想被用于for...in
,就必须实现一个__iter__()
。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
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 # 返回下一个值
__getitem__()
按照下标取出元素,需要实现getitem()方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
__getattr__()
只有在没有找到属性的情况下,才调用__getattr__
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
__call__()
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s' % self.name
print 'My friend is %s' % friend
p = Person('Bob','man')
p('Tim')
运行结果:
My name is Bob
My friend is Tim
使用枚举类
Enum
可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较
from enum import Enum
Month = Enum('Month',('Jan','Feb','Mar','Apr','May','Jun','Jul','Agu','Sep','Oct','Nov','Dec'))
for name,member in Month.__members__.items():
print('name = %s,member = %s value = %s' % (name,member,member.value))
运行结果:
name = Jan,member = Month.Jan value = 1
name = Feb,member = Month.Feb value = 2
name = Mar,member = Month.Mar value = 3
name = Apr,member = Month.Apr value = 4
name = May,member = Month.May value = 5
name = Jun,member = Month.Jun value = 6
name = Jul,member = Month.Jul value = 7
name = Agu,member = Month.Agu value = 8
name = Sep,member = Month.Sep value = 9
name = Oct,member = Month.Oct value = 10
name = Nov,member = Month.Nov value = 11
name = Dec,member = Month.Dec value = 12
value
属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类,@unique
装饰器可以帮助我们检查保证没有重复值:
from enum import Enum,unique
@unique
class Weekday(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
day1 = Weekday.Sun
print(day1)
print(Weekday.Mon)
print(Weekday['Tue'])
print(Weekday.Wed.value)
print(Weekday(4))
运行结果:
Weekday.Sun
Weekday.Mon
Weekday.Tue
3
Weekday.Thu
参考资料:廖雪峰的官方网址