类通常包含:
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
x=Account('Girlbert',30)
name
使用x.name()
时被认为是实例的属性name
,抛出无法调用的错误。x.count
:首先查找实例属性,如果找不到实例的属性,再查找类变量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
典型的继承类举例:
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()
能够具体指定使用哪个基类的方法。
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
的顺序:实例本身>实例的类>基类
动态绑定
:不考虑实例类型的情况下使用实例。
例如obj.name()
:对于所有拥有name()
的实例obj
均适用.这种行为也被称为鸭子类型(duck typing)
(很像鸭子的动物,可以认为就是鸭子)。
动态绑定的典型应用是标准库定义的各种方法,如len(listx)
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()
@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
通过实现一个或多个__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__
TypedProperty
初始化时self.name="_name"
,需要下划线的原因是self.name
存储的是描述符对象的属性名称,设置值时,会将该值存储在实例中。如果属性名称恰好也是name
,那么f.name
可能会无限递归,因为__get__返回的是f.name
,加了前缀,返回的是f._name
,不会造成问题。默认情况下,类的所有属性和方法都是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 *
导出名称。除此之外,无其他特性。
创建实例的过程:
__new__
创建新实例__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
.
内部实现:实例是使用字典实现的。可以使用x.__dict__
访问这个字典。
修改实例就会修改__dict__
,修改__dict__
也等同于修改实例。
实例的类:x.__class__
,类也是对词典的浅层包装,也有一个__dict__
,注意它不是实例的字典。
类的基类:X.__bases__
,它是一个基类元组。
obj.name
查找顺序
obj.__getattribute__("name")
:搜索并返回属性值obj.__getattr__()
:如果已定义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__
添加方法/特性名称,因为这些名称存储在类中,而不是实例中。运算符重载即通过实现特定的方法,使实例能够支持某些运算符运算。
例如:
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
isinstance(obj,classname)
# obj是否是classname的实例或其派生类的实例
issubclass(classnameA,classnameB)
# A是否是B的子类
通过实现特定的方法,能够改变上述方法的返回结果。例如可以代理类ProxyA的类型检查,设置为检查A的类型。
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
Foo.register(SomeClass)
,将某个类注册为抽象基类的派生类(需要实现特定的方法),(但我在python3中并未找到)元类是指创建和管理类的对象。元类的类型是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
元类的主要作用:检查和收集关于类定义的信息。元类不会改变实际创建类的任何内容,只是添加一些额外的检验,试图规范类。当然元类也可以实现更多的功能,但这是不合适的。
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
装饰器是一种函数,它接收类作为输入并返回类所为输出,在创建类时调用。