Python_类
前面的内容我都提交到github上面,我感觉这一块是我比较薄弱的一块。所以用我博客的形式记录一下的。
术语有关
对象具有特性,并且多个名称可以绑定在同一个对象上。者在其他语言中被称为别名。在对python的第一印象中者通常会被忽悠,并且当处理不可变基础类型(数字,字符串,元组)时可以被放心的忽略。但是,在调用列表、字典这类可变对象,或者大多数程序外部类型(文件,窗体等)描述实体时,别名对python代码的语义便具有影响。者通常有助于程序的优化,因为在某些方面别名表现的就像是指针。
Python作用域和命令空间
命名空间
命名空间是从命名到对象的映射。当前命名空间主要是通过python字典实现的,不过通常不关心具体的实现方式,以后也有坑呢会改变其实心方式。关于命名空间需要了解的意见很重要的事就是不同命名空间中的命名没有任何联系。
顺便提一句,我成python中任何一个“.”之后的为属性。因此,模块的属性和模块中的全局名有直接的映射关系:它们共享同一命名空间!
属性可以是只读或写的。后一种情况下,可以对属性赋值。可写的属性也可以用del语句删除。
通常,模块命名空间也会一直保持到解释器退出。由解释器在最高层用执行的语句,不管它是从脚本文件中读入还是来自交互式输入,都是main模式的一部分,所以它们也拥有自己的命名空间。
尽管作用域是静态定义,在使用他们都是动态的。每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:
- 包含局部命名的使用域在最里面,首先被搜索;其实搜索的是中层的作用域,这里包含了统计的函数,最后搜索最外面的作用,它包含内置命名
- 首先搜索最内层的作用,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索七点,包含非语句,但是也非全局的骂你干嘛
- 接下来的作用域包含当前模块的全局命名
- 最外层的作用域是包含内置命名的命名空间
重要的作用域决定于源程序的意义:一个定义在某模块中的函数的全局作用域是该模块的命名空间,而不是该函数的别名被定义或调用的位置,了解这一点非常重要。另一方面,命名的实际所说过程是动态的,在运行时确定的。
python的一个特别之处在于–如果没有使用global语法–其赋值操作总是在最里层的作用。赋值不会复制数据–只是将命名绑定到对象。删除也是如此:del x只是从局部作用域的命名空间中删除命名x。事实上,所有引入新命名的操作都作用域局部作用域。特别是import语句和函数定将模块名或函数绑定于局部作用域。
初始类
类定义语法
class ClassName
<statement-1>
...
<statement-1>
进入类定义部分后,会创建出一个新的命名空间,作为局部作用域。–因此,所有的赋值成员为这个新命名空间的局部变量。特别是函数定义在此绑定新的命名。
类对象
类对象支持两种操作:属性引用和实例化
属性引用使用和python中所有属性引用一样的标准语法:object.name
。类对象创建后,类命名空间中所有的命名都是有效属性性名。
class MyClass:
“””A simple example class”””
i=1234
def f(self):
return ‘hello world’
那么MyClass.i
和MyClass.f
是有效的属性引用,分别返回一个整数和一个方法对象。也可以对类属性赋值,你可以通过MyClass.i赋值来修改它。
类的实例化使用函数符号。只是将类对象看作一个返回新的类实例的无参函数即可。
x=MyClass()
以上创建了一个新的类实例并该对象赋给局部变量k
当然,出于弹性的需要,init方法可以有参数,参数通过init方法传递到类的实例化操作中。
class Complex:
def __init__(self,realpart,imagpart):
self.r=realpart
self.i=imagpart
x=Complex(3.0,-4.5)
print x.r,x.i
实例对象
实例对象唯一可用的操作就是属性引用。有两种有效的属性名。
- 数据属性相当于数据成员。和局部变量一样。
- 另外一种为实例对象所接收的引用属性是方法。
实例对象的有效名称依赖于它的类。按照定义,类中所有的函数对象对应它的实例中的方法。x.f
是一个有效的方法引用,因为MyClass.f
是一个函数。但是x.i
不是,因为MyClass.i
不是函数。不过x.f
和MyClass.f
不同,它是一个方法对象,不是一个函数对象。
方法对象
通常,方法通过右绑定方式调用
x.f()
在MyClass
示例中,这会返回字符串hello world
。然而,也不是一定要直接调用方法。x.f
是一个方法对象,它可以存储以后调用。
xf=x.f
while True:
print xf()
你可能注意到调用x.f()
时没有引用前面标出的变量,尽管在f()
的函数含义中指明了一个参数。这个参数怎么了?
方法的特别之处在于实例对象作为函数的第一个参数传给了函数。在我们的例子中,调用x.f()
相当于MyClass.f(x)
。通常,以n个参数的列表去调用一个方法相当于将方法的对象插入到参数列表的最前面后,以这个列表去调用相应的函数。
类和实例变量
一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于累的所有实例共享的属性和方法
class Dog:
kind=‘canine’
def __init__(self,name):
self.name=name
正如在术语相关的讨论中,可变对象,例如字典和列表的共享数据可能带来意外的效果。
一个类正确设计应该使用一个实例变量
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)
一些说明
约定:大写方法名称的首字母,使用一个唯一的小字符串作为数据属性名称的前缀,或者方法使用动词而数据属性使用名词。
def f1(self,x,y):
return min(x,x+y)
class C:
f=f1
def g(self):
return ‘hello world’
h=g
现在f,g和h都是类c的属性,引用的都是函数对象,因此它们都是c实例的方法,h严格等于g。
继承
当然,如果一种语言不吃继承,类就没有什么意义。派生类的定义如下
class DeriviedClassName(BaseClassName):
<state-1>
...
<state-2>
基类定义在另一个模块中时者一点非常有用
class DerivedClassName(modname.BaseClassName):
构造派生类对象是,就记住了基类。这在解析属性引用的时候尤其有用:如果在类中找不到请求调用的属性,就搜索基类。如果基类是由别的类派生而来,这个规则会递归的应用而上去。
派生类中的覆盖方法可能是想要扩从而不是简单的替代基类中的重名方法。有一个简单的方法可以直接调用基类方法,只要调用BaseClassName.methodname(self,arguments)
。有时这对于客户也很有用。
python有两个用于继承的函数
- 函数isinstance()用于检查视力类型,
isinstatnce(obj,int)
只有在obj.__class__
是init或者其它int继承的类型 - 函数issubclass()用于检查类继承
issubclass(bool,int)
是True
,因为bool是init的子类
多继承
python同样支持多继承形式。
class DerivedClassName(Base,Base1,Base2):
<state-1>
...
<state-2>
唯一的规则是深度优先,从左到右。
私有变量和类本地引用
以一个下划线开头的命名会被处理为API的非公开部分。它会被视为一个实现细节,无需公开。
因为有一个正当的类私有成员用途,python提供了对这种结构的有限支持,称为name mangling。任何形如__spam
的标识,被替代为_classname_spam
,去掉前导下划线的classname
即当前的类名。此语法不关注标识的位置,只要求在类定义内。
名称重整是有助于子类重写方法,而不会打破组内的方法调用。
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 #private copy of original update() method
class MappingSubclass(Mapping):
def update(self,keys,values):
#provides new signature for update()
#but does not break __init___()
for item in zip(keys,vlaues):
self.items_list.append(item)
补充
class Employee:
pass
john=Employee()
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
实例方法对象也有属性:m.im_self 是一个实例方法所属的对象,而 m.im_func 是这个方法对应的函数对象。
异常也是类
打印一个异常类的错误信息时,先打印类名,然后是一个空格、一个冒号,然后是用内置函数 str() 将类转换得到的完整字符串。
迭代器
我觉得这儿是重点
现在你可能注意到大多数容器对象都可以用for遍历
for element in [1,2,3]:
print element
for element in (1,2,3):
print element
for key in {‘one’:123,’two’:321}
print key
for char in ‘123’:
print char
for line in open(‘myfile.txt’):
print line
这种形式的访问清晰、简洁、方便。迭代器的用法在python中普通而且统一。在后台,for语句在容器对象中调用iter()。该函数返回一个定义了next()方法的迭代器对象。
s=’abc‘
it=iter(s)
print next(it)
print next(it)
print next(it)
了解了迭代器协议的后台机制,就可以很容易的给自己的类添加迭代器行为。定义一个_iter_()
方法,使其返回一个带有next()
方法的对象。如果这个类已经定义了next()
,那么_iter_()
只需要返回self
Class Reverse:
“””Iterator for looping over a sequence backwards.”””
def __init__(self,data):
self.data=data
self.index=len(data)
def __iter__(self):
return self
def __next__(self):
if self.index==0:
raise StopIteration
self.index=self.index-1
return self.data[self.index]
rev=Reverse(‘spam’)
iter(rev)
for char in rev:
print(char)
生成器
Generator是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,需要返回数据的时候使用yield
语句。每次next()
被调用时,生产器回复它脱离的位置(它记忆语句最后一次执行的文职和所有的数据值)。
def reverse(data):
for index in range(len(data-1,-1,-1)):
yield data[index]
for char in reverse(‘golf’):
print char
另一个关键的功能在于两次执行之间,局部变量和执行状态都自动保存下来。这使得函数很容易写,而且比使用self.index和self.data之类的方式更清晰。
补充的内容
fib就是一个普通的python函数,它特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fib()返回的就是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。
凡看到类似
def something():
return =[]
for ... in ...:
result.append(x)
return result
都可以优化
def sometion():
for ... in ...:
yield x
生成器表达式
有时简单的生成器可以用简洁的方式调用,就像不带中括号的链表推导式。这些表达式是为函数调用生成器而设计的。生成器表达式比完整的生成器定义更简洁,但是没有那么多变,而且通常比等价的链表推导式更容易记。
sum(i*i for i in range(10))
set()
max()
list()