美文网首页
python 学习笔记9(类和实例) 2018-4-20

python 学习笔记9(类和实例) 2018-4-20

作者: 我是帅气的石头 | 来源:发表于2018-04-20 23:08 被阅读0次

一,类(class)和实例

1,类的定义:

class Student(object):
    pass

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

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

>>> class Student(object):
...     pass
...
>>> bart = Student()
>>> bart
<__main__.Student object at 0x0000000004C421D0>

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

可以自由的给bart绑定属性:

>>> bart.name = 'szm'
>>> bart.name
'szm'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name,score等属性绑上去:

class Student(object):

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

注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
(1)类里面由很多函数组成 函数的第一个参数默认都是self
(2)如果需要全局变量,就在类的内部直接定义:
(3)类的内部在调用函数或者变量的时候,必须使用 self.

image.png
有了init方法,在创建实例的时候,就不能传入空的参数,必须与init方法匹配的参数,但self不需要传。
class Student(object):
    def __init__(self,name,score):
        self.name=name
        self.score=score

    def hello(self):
        print('hello,{0}'.format(self.name))

bart = Student('szm','99')
print(bart.name)
print(bart.score)

输出结果:
szm
99

定义一个方法,除了第一个参数是self外,其他和普通还是一样:

bart.hello()

这样一来,我们在外部看Student类,就只需要知道,创建实例需要给出namescore,而内部就将这些数据和逻辑处理封装起来。
这种方式也称为数据封装
例如给Student添加新的方法

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >=80:
            return 'B'
        else :
            return 'C'

外部实例只需要调用:

bart.get_grade()

二,访问限制

1,以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的。(但是按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问)

>>> class Test():
...     def __init__(self):
...             self.foo = 11
...             self._bar=23
...
>>> t=Test()
>>> t.foo
11
>>> t._bar
23

从上面的例子我们可以看到_bar中的单个下划线并没有阻止我们‘进入’类并访问该变量。
但是,前导下划线的确会影响从模块中导入名称的方式。假设你在一个名为my_module的模块中有以下代码:

def external_func():
   return 23
def _internal_func():
   return 42

现在,如果使用通配符从模块中导入所有名称,则python不会导入带有前导下划线的名称(除非,模块定义了覆盖此行为的all列表):

>>> from my_module import *
>>> external_func()
23
>>> _internal_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_internal_func' is not defined
>>>

通常应该避免通配符导入,因为他们使名称空间存在哪些名称不清楚。为了清楚起见,坚持常规导入更好。与通配符导入不同,常规导入不受前导单个下划线命名的约定影响。

2,如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__ ,在python中,实例的变量名如果以__开头,就变成一个私有变量(private),只有内部可以访问,外部不能访问。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是,不能直接访问__name是因为python解析器对外把__name变量改成了_Student__name,所以,依然可以通过_Student__name来访问__name变量,这也叫做名称修饰-解析器更改变量的名称,以便在类拓展的时候不容易产生冲突:

bart._Student__name   
#打印 __name的值,但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

这似乎有些抽象,我们再看一个列子:

image.png
通过dir()函数查看这个对象的属性列表,并寻找我们的原始变量名称foo, _bar__baz
(1)self.foo 变量在属性列表中显示为未修改的foo
(2)self._bar,它以_bar的形式显示在类上,就像之前说过,在这种情况下,前导下划线仅仅是一个约定。
(3)对于self.__baz,你会发现列表中并不存在,但是会有一个_Test__baz的属性,这个就是python解释器所做的名称修饰。
我们试着打印所有的变量,以便我们更好的理解。
>>> t.foo
11
>>> t._bar
23
>>> t.__baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__baz'
>>> t._Test__baz
33

让我们创建另外一个拓展Test类,并尝试重写构造函数中添加的现有属性:

>>> class Extended2(Test):
...     def __init__(self):
...             super().__init__()
...             self.foo =' over'
...             self._bar='over'
...             self.__baz='over'

然后我们来运行一下实例:

>>> t2=Extended2()
>>> t2.foo
' over'
>>> t2._bar
'over'
>>> t2.__baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Extended2' object has no attribute '__baz'
>>> dir(t2)
['_Extended2__baz', '_Test__baz', '__class__', '__delattr__', '__dict__', '__d
ir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt_
_', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new
__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '
__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
>>>

我们看到这个对象中没有__baz属性,变成了_Extended2__baz

>>> t2._Extended2__baz
'over'

但原来的_Test__baz依然存在:

>>> t2._Test__baz
43

[备注]名称修饰同时也适用于方法名称。

>>> class MyMethod:
...     def __method(self):
...             return 12
...
>>> m=MyMethod()
>>> dir(m)
['_MyMethod__method', '__class__', '__delattr__', '__dict__', '__dir__', '__do
c__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash_
_', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__redu
ce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '_
_subclasshook__', '__weakref__']

_mehod方法被修改成了_MyMethod__method

3,如果以双下划线开头,并且以双下划线结尾的,是特殊变量,有特殊用途,类似构造函数__init__,所以__XXX__的,不是private变量,所以不能用__name__这样的变量名。

小结:

image.png

三,继承和多态

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(subclass),而被继承的class称为基类,父类或超类
比如,我们定义一个名为Animal的class,有一个run()方法:

class Animal(object):
    def run(self):
        print("Animal is running....")

当我们需要增加Dog()Cat()类时,可以直接从Animal类继承:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

DogCat是子类,Animal是父类;继承的最大好处是子类获得父类的全部功能;

dog = Dog()
dog.run()

cat = Cat()
cat.run()

运行结果:

Animal is running....
Animal is running....

当然,我们可以对子类增加一些方法或者重新定义父类方法(覆盖,不影响父类调用)

class Dog(Animal):
    def run(self):
        print('Dog is running...')

    def eat(self):
        print("Eating meat")

dog = Dog()
dog.run()
dog.eat()

运行的结果:

Dog is running...
Eating meat

当子类和父类都存在相同的run()方法时,子类的run()覆盖父类的run(),在代码运行的时候,总会调用子类的run()。这样,我们就获得了继承的另外一个好处:多态

相关文章

网友评论

      本文标题:python 学习笔记9(类和实例) 2018-4-20

      本文链接:https://www.haomeiwen.com/subject/cdazkftx.html