Python11.0

面向对象

类和实例

oop
类class
实例instance
类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

class Student(object):#一般类名首字母大写,括号内为继承类
    pass

通过定义一个特殊的__init__方法,为模板类设计相应属性,在产生实例的时候直接赋予

class Student(object):

    def __init__(self, name, score):#self表示创建实例本身
        self.name = name
        self.score = score
#调用如下
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。
除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

数据封装
一个实例的操作不靠外部的函数实现,而靠自己内部的函数实现,这样实例的数据就只会在内部函数中被引用,这样就实现了数据的封装。
这样写在内部的函数,我们称为类的方法。定义一个方法,除了第一个参数是self外,其他和普通函数一样。
要调用一个方法,只需要在实例变量上直接调用(实例.方法),除了self不用传递,其他参数正常传入:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))
        
>>> bart.print_score()
Bart Simpson: 59

访问限制

类不喜欢外部直接修改实例的属性,如果一定要这样做的话,应该通过方法,即类内部定义的函数。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

class Student(object):

    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

这样绕弯子通过方法来获取属性有一个好处,就是可以对参数做检查,避免传入无效的参数。

class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量
有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,这样的变量意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量并非一定不能从外部访问。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名

继承和多态

子类继承父类
第一个好处,子类获得父类所有属性和方法
第二个好处,子类所写的方法可以覆盖父类,即多态
子类是父类的精细化,独特化,具体化,因而父类也叫基类

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
>>> isinstance(c, Animal)#子类也归属在父类,但反过来不行
True

多态另一好处是,当一个函数声明接收Animal,传入的Dog、Cat、Tortoise也都可以接收,因为对animal的一切操作,其子类都会继承,所以按理应当允许,实际上也是这样。

对于这一方面,动态语言python相比静态语言还有一处方便。
因为子类具有父类的所有操作,子类方法范围大于等于父类,所以我们认为即使一个函数声明接收父类,我们给他一个子类也是够用的。但一个函数具体实现不是必然包括了父类的所有方法,存在这样的情况,一个非子类含有这个函数所用到的方法,即便其并非相应子类,也可以传入并被接收。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

多态遵循“开闭原则”,即对源代码闭,对扩展开。
即如果想在一个子类上扩展某样从父类继承下来的功能,直接在子类处修改即可,子类的修改可以覆盖从父类继承的方法。这保证了父类的封闭性。

获取对象信息

如何获取对象的类型
使用type() 返回相应的类型

>>> type(123)#观察返回值
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

>>> type(123)==type(456)#因为返回类型,所以可以进行判断
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

#判断基本数据类型可以直接写int,str等
#如果要判断一个对象是否是函数可以使用types模块中定义的常量
>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance() 判断继承关系

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True
>>> isinstance(h, Animal)
True
>>> isinstance(d, Husky)
False

能用type()判断的基本类型也可以用isinstance()判断

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple

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

使用dir()
通过返回一个包含字符串的list,获得一个对象的所有属性和方法

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的。
len()是方法。len()是函数。实际上,在len()函数内部,它自动去调用传入对象的__len__()方法,所以根本的操作是方法,下面的代码是等价的:

>>> len('ABC')#函数
3
>>> 'ABC'.__len__()#方法
3

正如之前所说,两者中更根本的是方法,所以当我们在类中写出方法时,即使没有声明写出函数,也可应用?

>>> class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)
100

getattr()、setattr()以及hasattr()
获得,安置,是否拥有

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

#可以传入一个default参数,如果属性不存在,就返回默认值
>>> getattr(obj, 'z', 404) # 获取属性'z',不存在则返回默认值404
404

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

实例属性和类属性

类也有“默认参数”

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值