Python学习笔记——面向对象编程

接下来学习面向对象编程,基础的就不记录了,只记录一些Python特有的或者高级的特性。

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318645694388f1f10473d7f416e9291616be8367ab5000

1. 类的定义

定义类使用class关键字,后面紧跟类名(首字母大写),接着是从哪个类继承下来的(所有类最终会继承object)。

通过类名加参数就可以创建实例。可以自由的给实例绑定属性。可以把一些必须绑定的属性写在类中,通过__init__方法在初始化时将属性值进行绑定。

类中的函数与普通函数的区别是第一个参数是self,表示创建的实例本身(在调用时不需要传,Python解释器自己会把实例变量传进去)。

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def say_hello(self):
        print("Hello, my name is %s and I'm %s years old" % (self.name, self.age))

tom = Person('tom', 20)
tom.say_hello()
# Hello, my name is tom and I'm 20 years old

2. 访问限制

如果要让类的内部属性不被外部访问,可以在前面加上两个_,就变成了私有变量。

如果外部要访问/修改变量,可以加上get_xxx、set_xxx这样的方法。

前后都有两个_的是特殊变量,可以直接访问。

开头有一个_的变量是“约定俗成”的私有变量,外部可以访问,但是不要这么做。

开头有两个_的变量其实也是可以访问的,假设类名是Person,变量名是__name,在目前版本的Python中,外部可以通过_Person__name来访问__name变量。但是不建议这么做,除了规范外,不同版本的Python解释器会把__name改成不同的变量名。

3. 继承和多态

object是根(基类),任何类都可以追溯到object。

多态的含义(假设有一个类Animal,里面有一个run方法):

对于一个变量,我们只需要知道它是Animal类型,无需确切知道它的子类型,就可以调用run()方法,而具体调用的run()方法是作用在Animal、Dog还是Cat对象上,由运行时刻该对象的确切类型决定。即调用方只管调用,不管细节,当新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是开闭原则:

对扩展开放:允许新增Animal的子类;

对修改封闭:不需要修改依赖Animal类型的函数。

对于静态语言(如Java),如果需要传入Animal类型,则传入的对象必须是Animal类型或它的子类;而对于Python这种动态语言,不一定需要传入Animal类型,只需要保证传入的对象有一个run()方法就可以了。这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python支持多重继承,常使用MixIn的设计模式。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。

4. 获取对象信息

type()函数可以获取对象的类型。

对于有继承关系的class来说,可以使用isinstance()函数。该函数判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

ininstance()还支持判断一个变量是否是某些类型中的一种:

isinstance([1, 2, 3], (list, tuple))

dir()用于获取一个对象的所有属性和方法,它返回一个包含字符串的list。

方法也是属性,如果想知道哪些是方法,可以再使用callable()进行判断。

getattr()、setattr()和hasattr()用于操作对象的属性和函数,如:

# obj对象是否有属性x
hasattr(obj, 'x')

# 设置一个属性y
setattr(obj, 'y', 20)

# 获取属性y
getattr(obj, 'y')

# 获取属性z(如果试图获取不存在的属性,会有错误,因此可以给一个默认值)
getattr(obj, 'z', 404)

5. 实例属性和类属性

给实例绑定属性的方法是通过实例变量,或者通过self变量。

而如果想给类本身绑定一个属性,可以直接在class中定义属性。

类属性实例也可以使用,不过前提是实例没有这个属性。

个人认为比较好的方式是实例属性和类属性不要使用相同的名字,另外在获取类属性的时候,通过类名来获取,而不是通过实例名来获取。

6. 使用@propery

@property装饰器可以将一个方法变为属性调用。

把一个getter方法变成属性赋值,只需要加上一个@property就可以了。这时@property本身又创建了另一个装饰器@xxx.setter,其中xxx是属性名,负责把一个setter方法变成属性赋值。

还可以只定义getter方法,这样这个属性就是一个只读属性。

class Student(object):
    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2016 - self._birth

s = Student()
s.birth = 1990
s.birth
s.age

7. 定制类

Python的class中有许多形如__xxx__这样有特殊用途的函数,可以帮助我们定制类。

__len__

定义了__len__()方法的class,可以直接作用于len()函数,即len()函数内部调用了对象的__len__()方法。

__slots__

我们可以给实例绑定一个属性,也可以给实例绑定一个方法:

def set_age(self, age):
    self.age = age

from types import MethodType
s.set_age = MethodType(set_age, s)
# 其中s为某类的实例

这样绑定的方法只能是当前实例有效,如果想让所有实例都有效,可以给类绑定方法:

Person.set_age = set_age

但是,如果我们想限制实例的属性怎么办?可以在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age')

__slots__定义的属性仅对当前类实例起作用,对类不起作用,对继承的子类的实例也不起作用。除非在子类中也定义__slots__,这样子类允许定义的属性就是自身的__slots__再加上父类的__slots__。

__str__和__repr__

__str__()方法用于指定在print()对象时打印出的内容(给用户看的),而__repr__()方法用于指定直接打印的内容(给开发者看的)。当两者的内容一样时,可以在定义时直接赋值偷个懒:

class Person(object):
    def __str__(self):
        return 'str'
    def __repr__(self):
        return 'repr'
    # __repr__ = __str__

p = Person()
print(p)
# str
p
# repr

__iter__和__next__

如果一个类想被用于for...in循环,就必须定义一个__iter__()方法,该方法返回一个迭代对象,在遍历时通过迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

以斐波那契数列为例:

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 10:
            raise StopIteration()
        return self.a

for n in Fib():
    print(n)

# 1
# 1
# 2
# 3
# 5
# 8

__getitem__、__setitem__和__delitem__

要能根据下标来操作对象,就需要对应定义__getitem__()、__setitem__()和__delitem()__方法。

下标的行为可能是一个int,也可能是一个切片(slice类型,还可能有step),也可能是一个可作为key的object,如str。

所以要实现这几个方法并不简单,需要考虑多种情况(list、tuple、dict)和多种用法(index、slice、key),这里就不举例了。

__getattr__

正常情况下,在调用类的某个属性或方法时,如果不存在,会报错(AttributeError),__getattr__()方法可以处理这个问题。

当定义了__getattr__()方法后,如果某个方法或属性不存在,就会调用__getattr__(self, attr)方法,第二个参数就是方法名,可以自己来处理。

利用完全动态的__getattr__,可以写出一种链式调用:

def Chain(object):
    def __init__(self, path = ''):
        self._path = path

    def __getattr__(self, path):
        return Chain("%s/%s" % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__

Chain().status.user.timeline.list
# /status/user/timeline/list

__call__

如果想直接在实例本身上调用,可以定义__call__()方法。__call__()方法还可以定义参数。这样对实例调用好比对一个函数进行调用一样。

那么如果判断一个变量是对象还是函数呢?更多的时候,我们判断的是一个对象能否被调用,可以通过callable()函数来判断。

更多可定制方法请参见官方文档

8. 使用枚举类

Enum可以将相关常量定义在一个class中,且class不可变,成员可直接比较。

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar'))

for name, member in Month.__members__.items():
    print(name, '->', member, ',', member.value)
# Jan -> Month.Jan , 1
# Feb -> Month.Feb , 2
# Mar -> Month.Mar , 3

枚举值默认是从1开始的。如果要自定义值,可以从Enum派生出自定义类:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2

day1 = Weekday.Mon
print(day1)
# Weekday.Mon
print(Weekday.Mon)
# Weekday.Mon
print(Weekday['Mon']
# Weekday.Mon
print(Weekday(1))
# Weekday.Mon
print(day1 == Weekday.Mon)
# True

@unique装饰器可以帮助检查没有重复值。

访问枚举类型有多重方法,既可以用成员名称引用枚举常量,也可以直接根据value的值获取枚举常量。

9. 使用元类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

type()函数可以查看一个类或变量的类型,class的类型是type,实例的类型是class。

type()函数既可以返回一个对象的类型,又可以创建出新的类型。

def fn(self, name = 'world'):
    pass

Hello = type('Hello', (object,), dict(hello = fn))
h = Hello()
h.hello()
print(type(Hello))
# <class 'type'>
print(type(h))
# <class '__main__.Hello'>

要创建一个class对象,type()依次传入三个参数:

class的名称;

继承的父类集合;

class的方法名称与函数绑定。

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。

metaclass不好理解,目前还没有看懂,暂时留两个链接,后面继续深入理解:

http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000

http://blog.jobbole.com/21351/

上面的英文原文:

http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

时间: 2024-10-11 07:10:13

Python学习笔记——面向对象编程的相关文章

Python学习笔记-面向对象

一.什么是面向对象的程序设计 1.面向过程的程序设计 面向过程:核心是过程二字,过程即解决问题的步骤,就是先干什么,再干什么.基于该思想写程序就好比在设计一条流水线,是一种机械式的思维方式. 优点:复杂的过程流程化,进而简单化 缺点:扩展性差 2.面向对象的程序设计 面向对象:核心是对象二字,对象是特征与技能的结合体.基于该思想编写程序就好比在创造一个世界,世界是由一个个对象组成,是一种"上帝式"的思维方式 优点:可扩展性强 缺点:编程复杂度高,容易出现过度设计问题 二.类与对象 对象

从0开始的Python学习014面向对象编程

 简介 到目前为止,我们的编程都是根据数据的函数和语句块来设计的,面向过程的编程.还有一种我们将数据和功能结合起来使用对象的形式,使用它里面的数据和方法这种方法叫做面向对象的编程. 类和对象是面向对象编程的两个重要方面.对于类和对象的关系,举个例子就像学生和小明同学的关系一样.学生(类)是一个拥有共同属性的群体,小明同学(对象)是其中一个有自己特性的个体. 对于一个对象或类的变量被称为域,函数被称为类或对象的方法. 域有两种类型--属于每个对象或属于类本身,分别成为实例变量和类变量. 类使用cl

Python学习之==&gt;面向对象编程

一.面向对象与面向过程 面向对象与面向过程是两种不同的编程范式,范式指的是按照什么方式去编程.去实现一个功能.不同的编程范式本质上代表对各种不同类型的任务采取不同的解决问题的思路. 1.面向过程编程 角色是执行者,把一个项目按照一定的顺序,从头到尾一步步执行下去.这种思想好理解,但只要前面一个步骤变了,后面的步骤也要跟着变,维护起来比较麻烦. 2.面向对象编程 角色是指挥者,把一个项目分成一个个小的部分,每个部分负责一方面的功能,整个项目由这些部分组合而成一个整体.类似一个机关,分为各个职能部门

python学习笔记--面向对象的编程和类

一.面向对象的编程 面向对象程序设计--Object Oriented Programming,简称oop,是一种程序设计思想.二.面向对象的特性类:class类,对比现实世界来说就是一个种类,一个模型.一个类即是对一类拥有相同属性的对象的抽象.蓝图.原型.在类中定义了这些对象的都具备的属性(variables(data)).共同的方法. 对象:object对象,也就是指模型造出来的具体的东西.一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每

Python 学习笔记 - 面向对象(基础)

之前学习的编程方式都是通过面向过程来实现的,对于一些重用的代码,进一步的使用了函数,增强了代码的可读性和重用性.Python同时还支持面向对象的编程. 面向对象有三大特性: 封装 继承 多态 首先来看看封装.封装包括两点,把内容封装到某个地方:调用封装的内容 例1: class c1:     def __init__(self,name,obj):         self.name = name         self.obj = obj class c2:     def __init_

Python 学习笔记 - 面向对象(类成员)

上一篇学习了Python面向对象的3大特性,封装,继承和多态,这一篇继续学习类成员,包括字段,方法,属性以及他们的修饰符. 1.字段 字段分为静态字段和普通字段.静态字段属于类,而普通字段属于对象,因此静态字段在内存中只保存一份,而普通字段在每个对象中都保存了一份.定义的时候静态字段定义在类的范围里面,而普通字段定义在方法里面. 例如: >>> class Foo:     # 字段(静态字段)     CC = 123     def __init__(self):         #

Python学习笔记-面向对象进阶(二)

一.反射 1.什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省). 2.Python面向对象中的反射 通过字符串的形式操作对象相关的属性.python中的一切事物都是对象(都可以使用反射) 3.四个可以实现自省的函数 (1)hasattr(object,name),判断object中有没有一个name字符串对应的方法或属性,检测是否含有某属性. class BlackMedium: feture='Ugly' def _

Python学习笔记(三)编程_Style

语句和语法 *注释 #:可以从一行的任何地方开始 *续行 \: ''':闭合操作符,单一语句跨多行(实现多行注释) *代码组 缩进相同的一组语句构成的一个代码块 首行以关键字开始,如if.while等,以冒号结束 Python使用缩进来分隔代码组,同一代码组的代码行必须严格左对齐,否则会造成语法错误 *同一行放置多个语句 ;:以分号作为分隔符 *模块 每一个Python脚本文件都可以被当成一个模块 模块里的代码可以是一段直接执行的脚本,也可以是一些类似库函数的模块从而在由别的模块执行导入(imp

python学习日记-面向对象编程(一)

python的类(class)和实例(instance) 假设一种鞋子(shoe)有尺码(size)和颜色(color)两种属性,以此为例. 类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去.通过定义一个特殊的__init__方法,在创建实例的时候,就把size,color属性绑到shoe上去,例如: 1 class Shoe(object): 2 3 def __int__(self, size, color): 4 self.size = size