解读The Python Tutorial(九)——类

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]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值