《Python参考手册》7 类与面向对象编程

文章目录

    • 7.1 class语句
    • 7.2 类实例
    • 7.3 作用域规则
    • 7.4 继承
      • 7.4.1 多重继承
    • 7.5 多态动态绑定和鸭子类型
    • 7.6 静态方法和类方法
    • 7.7 特性
    • 7.8 描述符
    • 7.9 数据封装和私有属性
    • 7.10 对象内存管理
    • 7.11 对象表示和属性绑定
    • 7.12 \_\_slots\_\_
    • 7.13 运算符重载
    • 7.14 类型和类成员测试
    • 7.15 抽象基类
    • 7.16 元类
    • 7.17 类装饰器

7.1 class语句

类通常包含:

  • 方法method
  • 类变量class variable
  • 属性property

注意类变量与属性的区别,类变量类似java的静态变量,它在所有实例中共享值。

class Account(object):
    num_accounts=0    //类变量
    def __init__(self,name):
        self.name=name    //属性
        Account.num_accounts+=1
    def name(self):         
        return self.name

7.2 类实例

x=Account('Girlbert',30)

  1. name使用x.name()时被认为是实例的属性name,抛出无法调用的错误。
  2. x.count:首先查找实例属性,如果找不到实例的属性,再查找类变量

7.3 作用域规则

Python类特有的一个机制是:实例都包含一个dict,使用x.anykey=value可以为实例增加一个属性。
Python不会为方法中用到的名称创建作用域,它的后果是:如果要引用方法以外的属性/方法,必须是完全限定的。

class Foo(object):
    def bar(self):
        print("bar")
        k=10   //k的作用域范围仅仅在方法内部
    def spam(self):
        bar(self)  //ERROR
        self.bar() //correct
        Foo.bar(self)//correct

7.4 继承

典型的继承类举例:

import t_class.account as account
class Student(account.Account):
    def __init__(self,name,age,level):
        account.Account.__init__(self,name,age) 
        self.level=level

继承类的初始化方法中Account.__init__()语法上不是必须的,这个方法是初始化self而不是创建父类实例。
调用基类的同名方法:Account.method(self,*args,**kwargs),还可以使用super

class Account(object):
    def foo(self):
        print("account")
class Student(Account):
    def foo(self):
        print("Student")
class JuniorStudent(Student):
     def foo(self):
        print("JuniorStudent")
        super().foo()  //输出Student

此处如果Studnet没有实现foo()super().foo()那么输出结果将是Account.这两种调用基类方法的方法各有优点:super只需要有一个基类实现了方法即可,而Account.method()能够具体指定使用哪个基类的方法。

7.4.1 多重继承

python支持多重继承。多重继承有一个关键问题:属性解析变得复杂。Python解析属性的优先级大约是:将所有基类从"最特殊"的类到"最不特殊"的类。或者将继承图列成一个树:

     0            1       2  
   /   \
  0.1.1   0.1.2
 /
0.2               
  

它的优先级即:2 -> 1.2 -> 1.2 -> 0 -> 1 -> 2
对于同层级的类,优先级是按照定义时参数的顺序来的。

基类的准确顺序由C3线性化算法确定。使用tudent.__mro__可以查看属性解析顺序。

class X(object):pass
class Y(X):pass
class (X,Y):pass #TypeError:不能确定方法解析顺序

它不能解析的原因是:Y比X更特殊,却出现在后面。

虽然python支持多重继承,但最好不要使用多重继承,特别是在解析顺序不直观的情况下。多重继承可以用于定义混合类,被混合的类最好没有关联。

obj.attr的顺序:实例本身>实例的类>基类

7.5 多态动态绑定和鸭子类型

动态绑定:不考虑实例类型的情况下使用实例。
例如obj.name():对于所有拥有name()的实例obj均适用.这种行为也被称为鸭子类型(duck typing)(很像鸭子的动物,可以认为就是鸭子)。
动态绑定的典型应用是标准库定义的各种方法,如len(listx)

7.6 静态方法和类方法

import time
class Date(object):
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day
    @staticmethod           //静态方法
    def now():
        pass
    def __str__(self):
        return ("%s-%s-%s")%(self.year,self.month,self.day)

class LunarDate(Date):
    @classmethod            //类方法
    def now(cls):
        pass

静态方法位于类定义的命名空间。可以直接用类名调用。
类方法是将类本身作为对象操作的方法。参数cls可以认为就是类本身,它可以调用类的静态方法:cls.now()
为什么需要类方法?
例如Date.now()方法,如果是一个静态方法,那么LunarDate必须覆盖这个方法(重新实现);如果Date.now(cls)是类方法,那么通过cls可以调用到当前日期种类的特殊性,Date的子类不需要再覆盖now()方法。

调用一个类方法/静态方法还可以使用x.now(),它等同于Date.now()

7.7 特性

@property用法举例:

import math
class Circle(object):
    def __init__(self,radius):
        self.radius=radius

    @property
    def area(self):
        return math.pi*(self.radius**2)


print(Circle(1).area) #3.14

@property的好处是什么?
可以保持编程接口的统一。
另外@property对属性的常见操作做了一定程度的封装。

举例:


class Foo(object):
    def getname(self):
        return self.__name
    def setname(self,value):
        if not isinstance(value,str):
            raise TypeError("Must be a string!")
        self.__name=value
    def delname(self):
        raise TypeError("Can't delete name")
    name=property(getname,setname,delname)
#这里property方法与实例方法相关联,因此name只有使用实例调用才能正常运行
f=Foo()
f.name='Matlab'  # set
print(f.name) # get
f.name=4        #set 报错。

使用特性简化:


class Foo(object):

    @property
    def name(self):
        return self._name
    @name.setter
    def name(self,value):
        if not isinstance(value,str):
            raise TypeError("must be a string")
        self._name=value
    @name.deleter
    def name(self):
        raise TypeError("cant delete name")


f=Foo()
f.name='Matlab'  # set
print(f.name) # get
f.name=4        #set TypeError
del f.name

@name.setter,@name.deleteer方法名称必须是name

7.8 描述符

通过实现一个或多个__get__(),__set__(),__delete__()方法,可以将描述符与属性访问关联起来。
使用描述符对象能够更进一步强化对属性的访问。

请注意__getattr__()是属性访问底层方法,它与描述符对象有区别:描述符是为了访问对象,__getattr__是访问对象的属性。

class TypedProperty(object):
    def __init__(self,name,type,default=None):
        self.name="_"+name //如果不加前缀,会有问题
        self.type=type
        self.default=default if default else type()

    def __getattr__(self, item):
        print("getattr")
        return "not exist"

    def __get__(self, instance, owner):
        print("get")
        return getattr(instance,self.name,self.default)

    def __set__(self, instance, value):
        if not isinstance(value,self.type):
            raise TypeError("Must be a %s"% self.type)
        setattr(instance,self.name,value)

    def __delete__(self, instance):
        raise AttributeError("can't delete attribute")

class Foo(object):
    name=TypedProperty("name",str)

f=Foo()
a=Foo.name #调用Foo.name.__get__
f.name="lls" #调用Foo.name.__set__
del f.name # 调用Foo.name.__delete__

tp=TypedProperty("lls",str)
print(tp.num) #由于属性不存在,会调用到t.__getattr__
  1. TypedProperty初始化时self.name="_name",需要下划线的原因是self.name存储的是描述符对象的属性名称,设置值时,会将该值存储在实例中。如果属性名称恰好也是name,那么f.name可能会无限递归,因为__get__返回的是f.name,加了前缀,返回的是f._name,不会造成问题。
  2. 描述只能在类级别上进行实例化。持有描述符的类属性比实例上存储的属性具有更高的优先级。(不懂)

7.9 数据封装和私有属性

默认情况下,类的所有属性和方法都是public

  • 数据完全暴露,缺少保护
  • 继承体系中命名空间冲突

解决方式:按照特殊方式命名的名称,python会自动变形。
规则是:双下划线开头的名称自动添加前缀


__Foo -> _Classname__Foo

class Foo(object):
    def __init__(self,name):
       self.__name=name
    @staticmethod
    def __add(x,y):   
        return x+y

print(Foo._Foo__add(1,2))  #3
print(Foo.__add(1,2)) # Error


f=Foo("Invoker")
# print(f.name) #Error
print(f._Foo__name) #Invoker

  • 由于classname的不同,因此命名空间不容易冲突。
  • 这种方案只是隐藏名称,并不是严格的私有化机制。
  • 注意如果使用__xxxx__这种名称不会自动变形。
  • 变形过程只在编译时发生一次,它不会在运行期间发生。

单下划线
_name的作用:阻止通过from module import *导出名称。除此之外,无其他特性。

7.10 对象内存管理

创建实例的过程:

  1. __new__创建新实例
  2. __init__初始化(语法上并不要求必须初始化)
class Foo(object):
    def __init__(self,value):
        self.value=value

f=Foo.__new__(Foo)
f.value=4
# Foo.__init__(f,4)
print(f.value)

通常不会自定义__new__,它有两种应用场景:

  • 类继承自不可变类:例如str,只能在__new__中改变某些值
  • 定义元类

创建实例后,实例将有引用计数来管理,如果引用计数为0,实例立即被销毁。
实例销毁时首先查找并试图调用__del__()方法。del x语句删除对象的引用,但它不会调用__del__().

同其他具有垃圾回收功能的语言一样,最好不要自定义__del__(),因为自定义的过程中极易新增一个对象引用,产生循环引用,导致引用计数无法归0.解决的方法是使用weakref弱引用,弱引用不增加对象的引用计数。

import weakref

accountref=weakref.ref(instance)

__del__中使用accountref.

7.11 对象表示和属性绑定

内部实现:实例是使用字典实现的。可以使用x.__dict__访问这个字典。
修改实例就会修改__dict__,修改__dict__也等同于修改实例。

实例的类:x.__class__,类也是对词典的浅层包装,也有一个__dict__,注意它不是实例的字典。

类的基类:X.__bases__,它是一个基类元组。

obj.name查找顺序

  1. obj.__getattribute__("name"):搜索并返回属性值
  2. obj.__getattr__():如果已定义

7.12 __slots__

class Foo(object):
    __slots__ = ("name","balance","value")
    def __init__(self,value):
        self.value=value
        pass
    def add(self,y):
        self.value+=y
        return self.value
f=Foo(4)
f.__dict__  # Error:no attribute '__dict__'

使用__slots__以后,实例将不会使用字典,而是使用一个数组保存属性。数组的值对应__slots__元组的顺序。如果name没有列出在__slots__中,不能使用f.name='xxxx'.

__slots__不是一种安全机制,而是对内存和执行速度的一种性能优化。在创建大量对象的程序中,显然使用数组性能更好。
需要注意:

  • 如果基类使用了__slots__,那么继承类也要使用__slots__,即使继承类不添加任何属性。否则,派生类的运行速度将更慢,比不使用__slots__更糟
  • __slots__会破坏期望实例具有__dict__的代码。
  • __slots__不会对诸如__getattribute__(),__getattr__()的调用产生影响,这些方法的默认实现已经考虑到__slots__
  • 没有必要向__slots__添加方法/特性名称,因为这些名称存储在类中,而不是实例中。

7.13 运算符重载

运算符重载即通过实现特定的方法,使实例能够支持某些运算符运算。
例如:

class Foo(object):
    def __init__(self,value):
        self.value=value
        pass
    def __add__(self,y):
        return self.value+y.value

f0=Foo(4)
f1=Foo(5)
print(f0+f1)  # 9

7.14 类型和类成员测试

isinstance(obj,classname) # obj是否是classname的实例或其派生类的实例
issubclass(classnameA,classnameB) # A是否是B的子类

通过实现特定的方法,能够改变上述方法的返回结果。例如可以代理类ProxyA的类型检查,设置为检查A的类型。

7.15 抽象基类

import abc

class Foo(metaclass=abc.ABCMeta):

    @abc.abstractclassmethod
    def sum(self):
        return 10

    @property
    def name(self):
        pass

class SubFoo(Foo):
    def sum(self,value):
        self.value=value

    @property
    def name(self):
        pass

  • 抽象基类不能实例化
  • 继承自抽象基类的类必须实现所有的抽象方法或属性。
  • 虽然强制要求继承类实现方法或属性,但不检查这些方法或属性的参数和返回值。也不检查子类是否支持抽象基类的操作集。
  • python2中允许这样的操作:Foo.register(SomeClass),将某个类注册为抽象基类的派生类(需要实现特定的方法),(但我在python3中并未找到)

7.16 元类

元类是指创建和管理类的对象。元类的类型是type

>>> type(Foo)
<type 'type'>
class_name='Foo'
class_parents=(object,)
class_body="""
def __init__(self,x):
    self.x=x
def hello(self):
    print('Hello World')
"""
class_dict={}
#将class_body执行结果和变量存入class_dict
exec(class_body,globals(),class_dict)
#创建类对象Foo
Foo=type(class_name,class_parents,class_dict)
f=Foo(1)
f.hello()  #Hello World

每个类都有默认的元类就是type()。因此我们可以自定义元类,实现特定的功能.


class SomeMetaClass(type):
    def __init__(self,name,bases,dict):
       # ...
       #...
       type.__init__(self,name,bases,dict) 
class Foo(metaclass=SomeMetaClass):
    pass

元类的主要作用:检查和收集关于类定义的信息。元类不会改变实际创建类的任何内容,只是添加一些额外的检验,试图规范类。当然元类也可以实现更多的功能,但这是不合适的。

7.17 类装饰器

registry={}

def register(cls):
    print("register:"+cls.__name__)
    registry[cls._clsid_]=cls
    return cls

@register   #装饰器
class Foo(object):
    _clsid_='12345'
    def __init__(self):
        print("Foo init")
    def sum(self):
        pass


Foo()
#输出:
# register:Foo
# Foo init

装饰器是一种函数,它接收类作为输入并返回类所为输出,在创建类时调用。

你可能感兴趣的:(python)