一,类(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.

有了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
类,就只需要知道,创建实例需要给出name
和score
,而内部就将这些数据和逻辑处理封装起来。
这种方式也称为数据封装
例如给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改成不同的变量名。
这似乎有些抽象,我们再看一个列子:

通过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__
这样的变量名。
小结:

三,继承和多态
当我们定义一个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
Dog
和Cat
是子类,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()
。这样,我们就获得了继承的另外一个好处:多态
网友评论