zoukankan      html  css  js  c++  java
  • Python类的使用总结

    Python是一个面向对象的解释型语言,所以当然也有类的概念。
    在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
    之前接触类的概念是在学习C++时,现在学习了python后,觉得两者还是有很大的区别的。面向对象的思想是一样的,但是python做为更高级的语言,在类的定义与使用更加简便。

    类的定义
    Python中,定义类是通过class关键字,例如我们定义一个存储学生信息的类:

    class Student(object):
        pass

    class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的。通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

    定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

    1 >>> bart = Student()
    2 >>> bart
    3 <__main__.Student object at 0x10a67a590>
    4 >>> Student
    5 <class '__main__.Student'>

    可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。

    可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:

    1 >>> bart.name = 'Bart Simpson'
    2 >>> bart.name
    3 'Bart Simpson'

    这点与静态语言,比如C++是不一样的。我们可以随时给一个对象添加属性。
    在python中,类的属性就等同于c++类的成员变量,类的方法等同于c++类的成员函数。
    由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name,score等属性绑上去:

    1 class Student(object):
    2 
    3 def __init__(self, name, score):
    4 self.name = name
    5 self.score = score

    对比c++,__init__函数就等同于c++类得构造函数,注意:特殊方法“init”前后有两个下划线。
    注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

    有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

    1 >>> bart = Student('Bart Simpson', 59)
    2 >>> bart.name
    3 'Bart Simpson'
    4 >>> bart.score
    5 59

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

    我们可以给我们定义的Student类增加新的方法,比如get_grade:

     1 class Student(object):
     2 ...
     3 
     4 def get_grade(self):
     5 if self.score >= 90:
     6 return 'A'
     7 elif self.score >= 60:
     8 return 'B'
     9 else:
    10 return 'C'

    访问限制
    在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

    但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

    1 >>> bart = Student('Bart Simpson', 98)
    2 >>> bart.score
    3 98
    4 >>> bart.score = 59
    5 >>> bart.score
    6 59

    如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

    1 class Student(object):
    2 
    3 def __init__(self, name, score):
    4 self.__name = name
    5 self.__score = score
    6 
    7 def print_score(self):
    8 print('%s: %s' % (self.__name, self.__score))

    改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量__name和实例变量__score了:

    1 >>> bart = Student('Bart Simpson', 98)
    2 >>> bart.__name
    3 Traceback (most recent call last):
    4 File "<stdin>", line 1, in <module>
    5 AttributeError: 'Student' object has no attribute '__name'

    但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法:

    1 class Student(object):
    2 ...
    3 
    4 def get_name(self):
    5 return self.__name
    6 
    7 def get_score(self):
    8 return self.__score

    如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法:

    1 class Student(object):
    2 ...
    3 
    4 def set_score(self, score):
    5 self.__score = score

    需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量。
    有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

    类的私有成员一定不可以在外部访问吗?其实也不是。
    不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

    1 >>> bart._Student__name
    2 'Bart Simpson'

    但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。
    总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
    最后注意下面的这种错误写法:

    1 >>> bart = Student('Bart Simpson', 98)
    2 >>> bart.get_name()
    3 'Bart Simpson'
    4 >>> bart.__name = 'New Name' # 设置__name变量!
    5 >>> bart.__name
    6 'New Name'

    表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量。不信试试:

    1 >>> bart.get_name() # get_name()内部返回self.__name
    2 'Bart Simpson'

    多继承


    在Python中,类也是支持多继承的。只需要在定义类时的括号里把继承的所有类名写入就可以。


    例如:

    1 class Dog(Mammal, Runnable):
    2 pass

    上面的例子定义了一个名为Dog的类,同时继承了Mammal和Runnable类。

    我们再看一个例子,比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

    1 class Animal(object):
    2 def run(self):
    3 print('Animal is running...')

    当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:

    1 class Dog(Animal):
    2 pass
    3 
    4 class Cat(Animal):
    5 pass

    我们再编写一个函数,这个函数接受一个Animal类型的变量:

    1 def run_twice(animal):
    2 animal.run()
    3 animal.run()

    我们传入Animal的实例时,run_twice()就打印出:

    >>> run_twice(Animal())
    Animal is running...
    Animal is running...

    当我们传入Dog的实例时,同样也会打印出:

    >>> run_twice(Dog())
    Animal is running...
    Animal is running...

    因为Dog类继承了Animal类,是Animal的子类,在执行run函数时,由于Dog类实例没有定义自己的run函数,执行的是Animal类的run函数。
    但是如果我们传入一个跟Animal类没有任何关系的一个类实例时,会出现什么情况呢?
    我们定义一个Timer的类,该类也有一个名为run的方法。

    class Timer(object):
    def run(self):
    print('Start...')

    传入run_twice函数:

    run_twice(Timer())
    Start...
    Start...

    我们会发现该函数仍可以正常运行。

    对于静态语言(例如C++)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
    对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。
    这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

    获取对象类型
    当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

    使用type
    首先,我们来判断对象类型,使用type()函数:
    基本类型都可以用type()判断:

    >>> type(123)
    <class 'int'>
    >>> type('str')
    <class 'str'>
    >>> type(None)
    <type(None) 'NoneType'>

    如果一个变量指向函数或者类,也可以用type()判断:

    >>> type(abs)
    <class 'builtin_function_or_method'>
    >>> type(a)
    <class '__main__.Animal'>

    但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:

    >>> 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()
    对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
    如果继承关系是:object -> Animal -> Dog -> Husky
    那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

    >>> a = Animal()
    >>> d = Dog()
    >>> h = Husky()

    然后,判断:

    >>> isinstance(h, Husky)
    True

    没有问题,因为h变量指向的就是Husky对象。
    再判断:

    >>> isinstance(h, Dog)
    True

    h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

    使用dir()
    如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

    >>> dir('ABC')
    ['__add__', '__class__', '__contains__', '__delattr__', '__dir__',
    '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
    '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__',
    '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__',
    '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
    '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__',
    '__subclasshook__', 'capitalize', 'casefold', 'center', 'count',
    'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map',
    'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower',
    'isnumeric', 'isprintable', 'isspace',
    'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip',
    'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines',
    'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper',
    'zfill']

    类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

    >>> len('ABC')
    3
    >>> 'ABC'.__len__()

    我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法:

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

    如果试图获取不存在的属性,会抛出AttributeError的错误:

    >>> getattr(obj, 'z') # 获取属性'z'
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'MyObject' object has no attribute 'z'


    使用__slots__
    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

    class Student(object):
    pass

    然后,尝试给实例绑定一个属性:

    >>> s = Student()
    >>> s.name = 'Michael' # 动态给实例绑定一个属性
    >>> print(s.name)
    Michael

    但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。
    为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

    class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

    然后,我们试试:

    >>> s = Student() # 创建新的实例
    >>> s.name = 'Michael' # 绑定属性'name'
    >>> s.age = 25 # 绑定属性'age'
    >>> s.score = 99 # 绑定属性'score'
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'Student' object has no attribute 'score'

    由于’score’没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
    使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。
    除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。

    使用@property
    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

    s = Student()
    s.score = 9999

    这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

    class Student(object):

    def get_score(self):
    return self._score

    def set_score(self, value):
    if not isinstance(value, int):
    raise ValueError('score must be an integer!')
    if value < 0 or value > 100:
    raise ValueError('score must between 0 ~ 100!')
    self._score = value


    现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

    >>> s = Student()
    >>> s.set_score(60) # ok!
    >>> s.get_score()
    60
    >>> s.set_score(9999)
    Traceback (most recent call last):
    ...
    ValueError: score must between 0 ~ 100!

    但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。
    有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!
    还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

    class Student(object):

    @property
    def score(self):
    return self._score

    @score.setter
    def score(self, value):
    if not isinstance(value, int):
    raise ValueError('score must be an integer!')
    if value < 0 or value > 100:
    raise ValueError('score must between 0 ~ 100!')
    self._score = value

    把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

    >>> s = Student()
    >>> s.score = 60 # OK,实际转化为s.set_score(60)
    >>> s.score # OK,实际转化为s.get_score()
    60
    >>> s.score = 9999
    Traceback (most recent call last):
    ...
    ValueError: score must between 0 ~ 100!

    注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。

    还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

    class Student(object):

    @property
    def birth(self):
    return self._birth

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

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

    上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

    @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。


    ————————————————
    版权声明:本文为CSDN博主「Rotation.」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/fengxinlinux/article/details/77091914

  • 相关阅读:
    javascript笔记:深入理解javascript的function
    java笔记:SpringSecurity应用(二)
    javascript笔记:临摹jQuery(二)
    javascript笔记:临摹jQuery(一)
    java笔记:SpringSecurity应用(一)
    javascript笔记:javascript的前世,至于今生嘛地球人都知道了哈
    使用AJAX和J2EE创建功能强大的瘦客户端
    阻止事件冒泡
    Pro JavaScript Techniques第一章: 现代javscript编程
    JS数组方法汇总 array数组元素的添加和删除
  • 原文地址:https://www.cnblogs.com/wt869054461/p/11907796.html
Copyright © 2011-2022 走看看