Python Essential Reference 4th – 第7章 – 读书笔记

1、类(class)和实例(instances)是1对N的关系。

2、class由若干函数、变量(类成员)、属性(实例成员)组成。
一个示例的类如下:

class Account(object):

    num_account = 0    #类成员,所有示例共享!

    def __init__(self,name,balance):  #构造函数
        self.name = name              #实例成员
        self.balance = balance        #实例成员
        Account.num_account += 1      #更新类成员

    def withdraw(self,amt):           #注意一定要有self
        self.balance -= amt           #更新实例成员

3、类的实例化

#实例化,会调用__init__函数
a = Account("He",1000)
b = Account("Rui",2000)

#操作 ( attribute binding ),会先检查有无该属性或者函数,注意无self参数,显然的。。
a.withdraw(100)

4、Python无类作用域,即对实例(无论是函数还是属性)的操作都必须加self完成。

class Foo(object):

    def bar(self):
        print("bar!")

    def spam(self):
        bar(self)   #错误!
        self.bar()  #正确!

5、python中的继承:基类( base class )、派生类 ( derived class ),派生类继承基类的所有属性和方法,并可以选择重写或移除。object是所有Python对象的基类(和Java的Object一样)。一个继承类的写法很简单,就是在def时class B(A):,A为基类,B为派生类。

class EvilAccount(Account):

    def badwithdraw(self,amt):
        self.balance -= 2 * amt

而调用是,依然是
c = EvilAccount("Me",1500) #继承了基类的__init__方法
c.badwithdraw(1000) #直接定位到派生类
c.withdraw(1000) #派生类没有,定位到基类

6、派生类调用基类构造函数:多数发生在派生类的构造函数与基类参数不同时。

class EvilAccount(Account):

    def __init__(name,balance,factor):     #派生类与基类的构造函数不同
        Account.__init__(name,balance)    #派生类调用基类的构造函数
        self.f = factor                             #然后初始化自己的特有成员变量

7、派生类如何调用父类的函数:
(1)self.xxx(abcd)
(2)super(cls, instance).xxx(abcd)
例如:

class MoreEvilAccount(EvilAccount):
    def deposit(self, amount):
        self.withdraw(5.00)
        super(MoreEvilAccount,self).desposit(amount)   #调用父类的desposit方法

8、Python支持多继承,但属性间的冲突会导致很多问题,所以不建议使用。

9、多态/Duck Typing:Python一直都是运行时决定类型,因此有人称之为Duck Typing,即“鸭式类型”。好处是,可以定义一些内部方法、属性很类似但又无继承关系的类。例如标准库中的file-like文件。

10、静态方法和类方法。
可以说,类方法是静态方法的一个拓展吧(不再局限于类了)

class Foo(object):

    factor = 1    

    @staticmethod
    def add(x,y):
        return x+y

    @classmethod
    def mul(cls,s):
        return cls.factor*x

标准的调用方法都一样:
Foo.add(3,4)
Foo.mul(4,5)
除此之外,python并没有限定类方法和静态方法不能用于实例上,因此如下也是合法的:
f = Foo()
f.add(3,4)
f.mul(5,6)

11、属性标记@property,标记后,可以向访问实例属性一样自动get,如下:

#给函数加@property属性
class Cicle(object):
    def __init__(self,radius):
        self.radius = radius
    @property
    def area(self):
        return math.pi * self.radius *2

#调用时可视作属性一样get
>>> c  = Circle(4.0)
>>> c.radius
>>> 4.0

如果不加上述@property,则返回值c.radius会被视为是函数area的实例。
m = c.radius #m是函数实例
真正的m()时,才会调用c.radius()

12、一般来说,最好用getter和setter对实例内的属性进行保护,上面的@property只是getter方法,如何实现让函数类似的属性可以被赋值呢?需要@xxx.setter和@xxx.deleter。如下:

class Cicle(object):

    def __init__(self,area):
        self.__area__ = area
    @property
    def area(self):
        return self.__area__

    @area.setter
    def area(self,value):
        self.__area__ = value

    @area.deleter
    def area(self):
        raise TypeError("can't delete name.")

标签@area.deleter的.前面的area必须完全匹配@property标记的属性!
这样后,就可以如下用啦!
c = Cicle()
a = c.area
c.area = 123.2
del c.area

13、也可以用用户自定义的get和set方法来做隔离。它们是:__get__()、__set__()、__delete__()函数。

14、关于私有函数/变量。根据Python约定,以下划线_开头的,可视为私有变量,但无明确语法限制。

15、对象的内存管理:创建对象时,会调用class的__new__()和__init__()。
__new__()的用法一般很罕见,主要用途为:
(1)从一些不可变类继承时,在new中更改一些数值,入string,tuple等。

class UpperStr(str):
    def __new__(cls,value=""):
        return str.__new__(cls,value.upper())

u = UpperStr("hello")   #value is "HELLO"

(2)__new__()用于metaclass,后面会讲到

16、对象的管理也是基于引用计数:reference counting。当rc降到0的时候,会删除对象,此时调用自定义的__del__()如果有的话。__del__()一般也无需定义,除非你要显示的关闭文件、关闭网络socket、释放链接等操作。一般这些都不应该在__del__()中完成,和Java的finalize()道理一样。

17、引用计数rc并非完美,又是会产生‘“环引用”,导致内存泄漏,典型的就是“观察者模型”,此时可用弱引用解决问题。

import weakref
class AccountObserver(object):
    def __init__(self, theaccount):
        self.accountref = weakref.ref(theaccount )   #create weakref

18、默认情况下,python的属性名集合是用dirtionary(类似map)的结构实现的,记录在cls.__dict__中。
当实例的任何属性self.xxx变化时,都会反应到cls.__dict__中。访问属性时,最终会调用内置的__getattr__()、__setattr__()和__delattr__(),如果需要,可以在这三个地方做拦截器(记录日志之类的)。

19、可以替换属性集合的内置数据结构,采用__slot__,它将限定属性可使用的名称,换来更小的内存和更快的运行时间。

import weakref
class AccountObserver(object):
    __slot__ = ('name', 'balance')
    .... #属性只能命名为name和balance

由于不使用__dict__,对属性的访问也不会在进入__getattr__()等函数了。
此外,在继承的时候,子类必须也定义__slot__,否则内存消耗会更大!因此继承时候要谨慎使用。

20、python支持重载操作符,例如对加号+的重载如下:

class Complex:

    def __init__(self,real,imag=0):
        self.real = float(real)
        self.imag = float(imag)

    def __repr__(self):
        return "Complex(%s,%s)" % (self.real,self.imag)

    def __str__(self):
        return "(%g+%gj)" % (self.real,self.imag)

    def __add__(self,other):
        return Complex(self.real + other.real, self.imag + self.imag)

    def __sub__(self,other):
        return Complex(self.real - other.real, self.imag - self.imag)

21、检查一个实例是否属于某一类:isinstance(obj,name)
issubclass(A,B):如果类A属于类B,注意A和B都是cls,不是实例。
isinstance和issubclass都是可以重写的,这点上python比较灵活。

22、抽象类:Python支持抽象类,需要引用abc模块。

from abc import ABCMeta, abstractmethod, abstractproperty

class Foo:
    __meta__ = ABCMeta   #must 1

    @abstractmethod      #must 2
    def spam(self,a,b):
        pass

    @abstractproperty    #must 2
    def name(self):
        pass

抽象类肯定是不能直接被实例化的,实现抽象类得方法并不是继承!而是注册,如下:

class Grok:
    def spam(self,a,b):
        print("Grok.spam")

Foo.register(Grok)

23、Metaclass,我没看懂 - - 感觉是类似于Java的反射代理机制,用于框架时候比较给力。

24、类包装器Class Decorators: take a class as input and returns a class as output
其实也没太明白用途。。

#类包装器
registry = { }
def register(cls):
    registry[cls.__clsid__] = cls
return cls</pre>
@register
class Foo(object):
__clsid__ = "123-456"
def bar(self):
pass

#用法
register(Foo)

Leave a Reply

Your email address will not be published. Required fields are marked *