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

Posted _tsubasa_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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不好理解,目前还没有看懂,暂时留两个链接,后面继续深入理解:
上面的英文原文:

以上是关于Python学习笔记——面向对象编程的主要内容,如果未能解决你的问题,请参考以下文章

Python学习笔记之 面向对象

python学习笔记:面向对象编程类

python学习笔记:面向对象编程类

python学习笔记:面向对象编程类

python学习笔记8--面向对象编程

python3学习笔记面向对象;过程;类