类
namespaces
namespaces 具有不同的生命周期,并且创建时间也各不相同。内置的namespace属于builtins模块,是解释器启动的时候就创建的,并且永生。
The global namespace保存所有模块的全局变量,当模块被载入时才为该模块创建全球变量,直到退出解释器才消亡。
The local namespace保存所有function的本地变量,当function被调起时创建,当function结束时消亡。
变量的命名空间(作用域)在它被赋值时所在的代码位置决定。变量被赋值时才创建。
关键字global nonlocal
def scope_test():
def do_local():
spam = "local spam" # spam被赋值时处于do_local()中,所以属于do_local()的local namespace变量
def do_nonlocal():
nonlocal spam #nonlocal声明此spam不是do_nonlocal()的local namespace变量
spam = "nonlocal spam" #这里不会创建变量,而是使用最近一层嵌套方法scope_test()的local spam变量,使用nonlocal的变量必须在某个上层外套方法中有定义,不能是global的。(注意,是**方法**里面有定义)
def do_global():
global spam #global声明该spam是global namespace的,即模块层的。这里只是声明变量是哪个层面的。
spam = "global spam" #接下来这里才是创建一个global级别的变量spam。
spam = "test spam" # scope_test()的的local namespace变量
do_local() #函数被调起时创建函数本身local namespaces spam = "local spam"
print("After local assignment:", spam) #此print被scope_test()直接包围,所以找到的spam属于scope_test()
do_nonlocal() #因为使用了nonlocal,所以没有创建do_nonlocal内部变量spam,而是直接使用了scope_test()的
print("After nonlocal assignment:", spam) # do_nonlocal()改变了scope_test()的spam
do_global() #创建global层面的spam并赋值
print("After global assignment:", spam) #但是打印的却还是scope_test()的local变量
#以上函数定义完毕,以下开始测试:
scope_test() #函数被调起时创建函数本身内部变量spam = "test spam"
print("In global scope:", spam) #包含此print的已经是最外层了,而最外层并没有定义spam,最终就找到do_global()定义的global。
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
类的定义
class ClassName:
<statement-1>
.
.
.
<statement-N>
以下创建类的实例,但是这种无参数的创建,总是建立一模一样的实例,更多时候人们想创建不同的实例。
x = MyClass()
于是就有def __init__()关键字方法,每次创建实例都会自动调该方法以创建不同的实例。self不是关键字,只是类的方法的第一个参数,self是约定成俗的名称,self指的是实例本身。类的方法与普通函数方法 的区别似乎就这第一个参数的不同。
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5) #不同参数,创建不一样的实例
>>> x.r, x.i
(3.0, -4.5)
类的属性并不用预先在类中定义,而是与变量一样,用到的时候再绑定就行:
x.counter = 1 #实例绑定一个属性
print(x.counter) #将打印1,而不会出错
del x.counter #删除之前绑定的属性
print(x.counter) #再打印将报错,已经没有这个属性了
类的方法,与普通函数方法不同的是,第一个参数一定是实例本身self。
调用类的方法的方式之一:
MyClass.f(x) #x即是实例,做为第一个参数去调类名.方法名(实例)
调用类的方法的方式之二:
x.f() #这是常用方式,直接由实例.方法名(),无需第一个self参数
class MyClass:
"""A simple example class"""
i = 12345
def f(self): #self是约定成俗的名称,并不是关键字
return 'hello world'
与x.i属性对象类似,x.f则是方法对象。x.f()有括号的才是对方法的调用。
xf = x.f #对象可以存起来,方便以后调用
while True:
print(xf()) #加上()调用方法。
类的namaspace
在class中,而且又在def方法之外定义的变量是类的一个属性,该属性只有一份,所有实例共享的,但具体的实例对象可以绑定一个同名的属性覆盖它,这时该对象实例的这个属性是独有的。其他没覆盖的实例对象仍然共享那个类的属性。
__init__ ()配合self定义的变量(这个变量也是类的属性)是每个实例各自一份的,不共享。在其他普通方法中引用累的属性(不管是共享或非共享的)都需要配合self,如self.bianliang。同样在方法中未配合self定义的变量就仅是普通变量,不是累的属性。
class Dog:
kind = 'canine' #所有实例共享的类属性,但是如果kind改成name却也不会出错,
#此时 instance.name引用的是self.name(实例自已覆盖后的属性)
#而Dog.name=canine。(类的共享属性)
def __init__(self, name):
self.name = name # 在构造方法配合self定义的变量也是类的属性,每个实例一份。
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind #实例可以引用类定义的类变量也可以引用实例(self)定义的实例变量。
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
#如下是错误的用法:
class Dog:
tricks = [] # 所有实例共享
def __init__(self, name):
self.name = name #每个实例各自一份name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
>>> e.tricks
['roll over', 'play dead']
#正确用法应该是:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
下例f g h都是类的属性,这些属性返回(指向)函数对象。于是乎类的方法可以定义在类之外。
# Function defined outside the class
def f1(self, x, y): #注意,有self作为第一个参数。
return min(x, x+y)
class C:
f = f1 #需要在类定义里面赋值,
#如果在类定义之外赋值虽然不出错,但是需要这样x.f(x,1,2)调用,似乎不是标准用法。
def g(self):
return 'hello world'
h = g
继承
class DerivedClassName(BaseClassName): #在同一模块时
<statement-1>
.
.
.
<statement-N>
class DerivedClassName(modname.BaseClassName): #不在同一模块时
子类复写父类方法后,可以这样调用父类方法:BaseClassName.methodname(self, arguments)
其实这个写法正是以类的名义去调用类的方法,此时第一个参数必须是实例。
isinstance() 和 issubclass()
isinstance(obj, int)判断obj这个实例是不是int或int派生的类 的实例。返回true or false
issubclass(float, int) 判断float这个类是不是int或int派生的类。返回true or false
多重继承
多重继承,方法在子类找不到时,会去父类找,查找顺序简单来说是深度优先,再从左到右。
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
特殊变量名
子类会覆盖父类方法,通过至少两个”_”前缀和至多一个”_”后缀来命名的特殊变量,不会被覆盖。
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # __update是update的一个copy,子类复写update并不影响__update
class MappingSubclass(Mapping):
def update(self, iterable): #复写了基类的update方法,而没复写__update方法,
#所以子类的构建方法和主类相同,新建的子类实例并不会写死lzd
self.items_list = []
self.items_list = ['lzd'] #复写的update是hardcode lzd
如果子类的update改名为__update还不是又覆盖了主类方法?所以Python有一个机制会自动将
至少两个”_”前缀和至多一个”_”后缀来命名的变量重新命名为_classname 加上原变量名。因此父类方法__update()变为_MappingSubclass__update()。
类的使用技巧
class Employee: 类的定义,空类
pass
john = Employee() # 新建一个空类的实例
# 给实例添加属性。类似一条有结构的record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
Generators
与普通函数类似,只是把return改成yeild关键字,如下reverse(data)就是一个完整的生成器(Generator),而不是普通函数。生成器可用于迭代(yeild放在循环体中不断返回值于用迭代?)。
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g
也可以像推导式那样,只是把中括号(或大括号)换成小括号生成 简易的生成器,简易的生成器一般生成后立即使用,它比推导式更省内存,但功能不及完整的生成器。
>>> (i*i for i in range(10)) #通过小括号建立Generator,是一个object
<generator object <genexpr> at 0x00000029D090D990>
>>> [i*i for i in range(10)] #中括号推导式建立的是一个list
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> {i*i for i in range(10)} #大括号推导式建立了一个集合set
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
>>> {i : i*i for i in range(10)} #通过修改表达式,大括号生推导式建立一个字典dict
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
>>> i*i for i in range(10) #没有括号是语法错误
SyntaxError: invalid syntax
#注意,小括号建立的是生成器,而不是元组。
>>> sum((i*i for i in range(10))) # 建立的生成器,一般马上使用。如这里用于sum函数
285
>>> sum(i*i for i in range(10)) # 留意这里的小括号也可以省略一对。
285
>>> list(i*i for i in range(10)) # 类似的,可以省略一对小括号
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> list((i*i for i in range(10)))
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]