廖雪峰Python学习笔记三-面向对象(Java基础)

使用面向对象的由来和哲学原理不用说了,各语言中面向对象的基本的机制和实现差不多,主要是Python和Java中的差异
###1、创建###
比如:

class Studentpass
Student xm
xm.name = 'xiaoming'

Python中的对象属性不用预先声明,可以再运行过程中自由的创建并赋值。

###2、私有成员###
成员包括变量和方法,Python中没有表明私有的关键字,使用前面加两条下滑__name来创建,且必须在类的内部创建。
表示为私有变量之后,该成员只能在内部可以访问,名字不变。
该成员不能在外部直接访问,(Python解释器会将成员名变成_Student_name,所有仍然可以通过这个在外部进行访问,但是强烈不建议这样做,这可能会带来很大的隐藏的破坏。)

注意一种情况, 如果使用xm.__name = 'xiaohua'会创建一个新的非私有的成员变量,但不会给内部的私有变量__name 赋值(成员方法一样的)。(一会儿是创建,一会儿是赋值)

(另外的,在Python类方法里面访问成员必须加self,不太方便呀)

###3、继承和多态###
和Java不同,Python采用多继承。
鸭子类型
多态是对传入方法的参数等调用某个方法,当传入不同的对象时给改参数时产生的行为不一样。
由于Python是动态语言,和静态语言不同的是,传入的对象并不需要是某个类的子类(属于某一类族),只需要它具有那些需要调用的方法即可。这就是Python的鸭子类型,“一个对象只要看起来像鸭子,走起路来像鸭子,就可以被看做是鸭子”。

###4、获取类信息###
类信息,类信息就是变量或者对象或者函数具有的类型的信息,比如是哪种类型,有哪些成员等。
4.1 类型信息
使用type(xxx)方法获取xxx变量的类型信息,它会返回相应的类对象。
判断相同:

import types
def fn():
...     pass
...
type(fn)==types.FunctionType
type(lambda x: x)==types.LambdaType
type((x for x in range(10)))==types.GeneratorType
type('abc')==type('123')

判断类型还有sInstance方法,并且支持传入多种类型进行判断。
isinstance((1, 2, 3), (list, tuple))
4.2 方法和成员信息
dir(instance)
会用字符串的list的形式列出所有的成员方法和成员变量。
获取、设置成员变量的值、判断成员是否存在
getattr()、setattr()以及hasattr()
getattr(obj, ‘z’, 404) # 获取属性’z’,如果不存在,返回默认值404
setattr(obj, ‘y’, 19) # 设置一个属性’y’

###动态绑定变量和方法以及限定:###
由于Python动态语言的特性,可以给实例和类动态的绑定变量和方法。对一个实例绑定的对其它实例不起作用,对类绑定的会对所有的实例起作用。

如果想要类和实例不被动态绑定其它的属性(貌似方法也不行),就相当于静态语言的预定义属性,可以使用 __slots__变量,如:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称,如果绑定其它就不行,这就相当于静态语言的预定义属性了

如果此时绑定其它的属性就不行了。
动态绑定的限制对继承的子类不起作用。

###多重继承###
和Java不同,Python允许多重继承。再多重继承的条件下设计继承关系时,一般让主线单一继承下来,然后需要混入额外的功能时,通过多重继承实现,额外继承的类通常取名为XxxMixIn。
例如,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
编写一个多进程模式的TCP服务,定义如下:

class MyTCPServer(TCPServer, ForkingMixIn):
    pass

编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer, ThreadingMixIn):
    pass

###类的操作符重载###
操作符重载在一些圈子中名声不好,他可能被滥用,导致缺陷或者意料之外的性能瓶颈。但是,如果使用得当,它能很大程度的的提升代码简洁性和可读性。Python对它做了一定的限制,做好了灵活性,可用性和安全性的平衡。Python中运算符重载的基本规则:
(1) 不能重载内置类型的运算符;
(2) 不能自定义运算符,只能重载现有的。
(3)某些运算符不能重载,is and or 以及not,位运算符& | ~是可以重载的。
Python运算符重载列表
Method Overloads Call for
init 构造函数 X=Class()
del 析构函数 对象销毁
repr 打印转换 print X,repr(X)
str 打印转换 print X,str(X)
call 调用函数 X()
_getattr 限制 X.undefine
setattr 取值 X.any=value
getitem 索引 X[key],For If
setitem 索引 X[key]=value
len 长度 len(X)
iter 迭代 In
add + X+Y,X+=Y
sub - X-Y,X-=Y
mul * X*Y
radd 右加+ +X
iadd += X+=Y
or | X|Y,X|=Y
cmp 比较 == X==Y,X<Y
lt 小于< X<Y
eq 等于= X=Y

1、重载根据下标获取Item
这个是比较复杂一点的,因为按下表获取值,传入的参数有多种,需要分别处理:

class Fib(object):

    def __getitem__(self, n):
        if isinstance(n, int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            l = []
            for x in range(stop):
                if x > start:
                    l.append(a)
                a, b = b, a + b
            return 
# 还有l[a:b:c] 指定间隔的,有负数的,set或者dict用key获取值的

2、可以重载.运算符
即获取对象的属性或者方法时,如果找不到相应的属性或者方法,就会触发这个函数,返回相应的结果。
一种应用,拼接网址URL时,使用:
利用完全动态的__getattr__,我们可以写出一个链式调用:

class 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'

以上代码,在__getattr__时返回的是拼接了参数的Chain对象,继续调用未知的属性时,继续拼接,以此一直循环往复,将整个URL拼出来。不过特殊字符如何拼出来?
更神奇的

class 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

    __call__ = __getattr__

    __repr__ = __str__

用print(Chain().users(‘Zhangsan’).repos)调用
输出:/users/Zhangsan/repos

本来Chain() 不能当函数用,通过__call__ = __getattr__把它变成可以当函数调用的,然后在__getattr__中返回它就可以传递参数了,即chain().users(‘Zhanshan’)这个写法有效了,user和参数都传进去了。这样的每个调用都可以传参print(Chain().users(‘Zhangsan’).repos.transferParmas(“transfer a parameter”))
这里可以看到函数式编程对函数使用的灵活性,将函数对象各种赋值传递,以及各种参数,各种使用方式。

3、特别一点的,还可以重载把对象当做一个方法进行调用, XXX(xxx…)
通过重写__call__方法实现,实际上在Python里面方法和对象没有本质区别。
比如

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)

调用方式如下:

>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

判断对象是否可以当做方法调用,使用callable()方法即可,比如
callable(Student())
True
callable(str)
False

使用枚举类

Python 3支持枚举, 枚举也就是若干个常量放到一个类中,这些常量有两个属性,name和int类型的value,可以获取name, value,判断相等,迭代功能。
目前知道的两种创建枚举的方法,简洁版的创建

from enum import Enum #需要导入模块

Month = Enum('Month',('Feb', 'Jen', 'Mar', 'Apr', 'Aay', 'Jun', 'Jul', 'august', 'Sep', 'Oct', 'Nov', 'Dec')

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

name指名称,member值Month.xxx全名;

完整版的创建,可以更详细的控制

from enum import Enume, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6


print(Weekday.Fri)
print(Weekday(1))
print(Weekday(2))
print(Weekday(1) == Weekday.Mon)
print(Weekday(1) == Weekday.Sun)
print(Weekday.Sat.name + ':' + str(Weekday.Sat.value))

可以看到,定义方法是继承Enum类,然后可以在里面设置枚举变量及其值。

使用时可以判断相等,可以迭代,可以直接访问,可以通过值访问,可以获取名称及值。

使用元类

Python是一种动态语言,它的类和函数等的编译解析是在运行期间做的。
Java是静态语言,不过他也可以进行动态加载,通过ClassLoader。那么Python解析加载的是通过什么实现的呢。就是type函数,Python执行过程中,当载如某个模块时,就会依次该模块的所有语句。(注意是执行一次,如果有print语句,会输出的。导入模块时只有发生了加载才会执行,而加载是看内存中有没有,以及是否是最新修改的文件,没有或不是最新的就会重新加载执行一次,否则不执行。可以主动使用Reload方法。

用type创建
具体的 原来知道的type的用法type(类Class或者实例Instance)获取到类或实例所属的类型,它的用法不止如此。它主要是用来加载类的,比如有一个Hello类,通常创建类的方法是:

class Hello(object):
	def hello(self, name='world'):
        print('Hello, %s.' % name)

这是正常情况下直接在代码中定义创建,还可以直接用type创建

def fn(self, name='world'): # 先定义函数
     print('Hello, %s.' % name)
Hello  = type('Hello', (object,), dict(hello = fn))

type()函数依次传入3个参数:
1.class的名称;
2.继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
3.class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

这就创建好了一个Hello类,和前面正常情况下创建是一样的。

控制类的创建行为
除了使用前面的type创建外,还可以用mateclass控制类的创建行为。mateclass直译即为元类。metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。正常情况下,你基本上不会碰到需要使用metaclass的情况。

使用@property
设置实例属性的时候,常用setter,进行参数检查等,每次调用setter方法会比较麻烦,所以使用
@birth.setter
def birth(self, value):
self._birth = value
这个修饰符之后,可以直接把该方法等价于一个属性,然后直接赋值就行了,此时相当于调用了该函数,可以进行参数检查等操作。
同样的,可以设置getter函数,
只设置其中一个,那么可以实现某种程度上的私有

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章