# 對象
[TOC]
## 訪問控制(私有屬性和私有方法)
```python
# 私有屬性(變量)
class Student(object):
def __init__(self, name, score):
self.__name = name # 屬性的名稱前加上兩個下劃線__,外部就不能訪問
self.__score = score
def _show_info(self):
print(self.__name)
print(self.__score)
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
bart = Student('Bart Simpson', 59)
bart.__name # AttributeError: 'Student' object has no attribute '__name'
bart.print_score() # Bart Simpson: 59
bart.__name = 'New Name' # 設置__name變量!
bart.__name # 'New Name'
# 注意: 看上去好像設置私有變量成功,但實際上這個__name變量和class內部的__name變量不是一個變量!內部的__name變量已經被Python解釋器自動改成了_Student__name,而外部代碼給bart新增了一個__name變量
bart.__show_info() # 私有方法不能訪問:AttributeError: 'Student' object has no attribute '__show_info'
```
1. 變量名類似__xxx__的,也就是以雙下劃線開頭,并且以雙下劃線結尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量
2. 只以一個下劃線開頭的實例變量名,比如_name,這樣的實例變量外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
## 類的方法
```python
class Foo:
name = 'who'
count = 0
# __init__也屬于實例方法,實例方法必須依托于實例
def __init__(self, name):
self.name = name
# 每實例一次,調用一次, 這里可以統計該類調用的次數
self.__class__.count += 1
print(self.__class__.count)
# 類實例方法:第一個參數強制為類實例對象,一般用self代替
# 類實力為對象后可以直接訪問的方法
# 可以通過這個類實例對象(self)訪問對象屬性
# 可以通過類實例對象(self)的__class__屬性訪問類屬性
def hi(self , str):
print(self.__class__.name) # who
print(str , self.name) # hello zxg
# 類方法:cls即為類自身
@classmethod
def class_method(cls):
print(cls.count)
# 靜態方法
# 不能傳遞和類或實例相關的參數,如cls或self,但可以傳遞其他參數
@staticmethod
def static_method(nickname):
print(nickname)
if __name__ == '__main__':
foo01 = Foo('zxg')
# foo01.hi('hello')
# Foo.hi('hello') # 報錯:實例方法不能通過類直接用,必須依托于實例,當然可以把foo01的對象作為第一參數傳進去
foo01.class_method() # 可以通過實例調用
Foo.class_method() # 也可以直接通過類來調用
# foo01.static_method('zxg')
# Foo.static_method('zxg')
# print(foo01)
# print(Foo)
```
1. 邏輯上,類方法應當只被類調用,實例方法實例調用,靜態方法兩者都能調用
2. 主要區別在于參數傳遞上的區別,實例方法悄悄傳遞的是self引用作為參數,而類方法悄悄傳遞的是cls引用作為參數。而靜態方法不會任何隱式傳遞
## 使用__slots__限制實例的屬性
```python
class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
# 使用__slots__要注意,__slots__定義的屬性僅對當前類實例起作用,對繼承的子類是不起作用的:
class GraduateStudent(Student):
pass
bart = Student() # 對象實例不需要用new
```
## 類和類方法的裝飾器
### 類方法中的裝飾器
```python
# 原來的class
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
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
s = Student()
s.score = 'haha' # OK,實際轉化為s.set_score(60)
s.score # OK,實際轉化為s.get_score()
# 只定義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 2018 - self._birth
zxg = Student()
zxg.birth = 1988
zxg.age # 30
Student.sex = 1
zxg.sex = 1 # python一切皆對象,類也屬于對象,可以動態為類添加屬性,該屬性會體現在實例后的對象里面
```
### 類裝飾器
#### 函數裝飾類
##### 裝飾器無參數
```python
#給每個類打印一句話
def Decorator(obj):
print("定義了一個裝飾器函數")
return obj
# 相當于執行 Decorator(School)
@Decorator
class School():
pass
def godme(fun):
def __godme(self,message):
print('before')
fun(self,message)
print('after')
return __godme
class Person:
def show(self,message):
print(message)
@godme
def say(self,message):
print(message)
person = Person()
# 相當于 godme(person.say('happy'))
# 調用順序 godme() -> __godme()-> person.say()
# 輸出順序 before -> happy -> after
person.say('happy')
```
##### 裝飾器有參數
```python
#給每個類添加一個可變的數據屬性
def Decorator(**kwargs):
def add(obj):
# <class '__main__.School'>
print(obj)
"添加數據屬性"
print('調用外部函數的**kwargs',kwargs)
for key,val in kwargs.items():
# 添加數據屬性
setattr(obj,key,val)
return obj
print("外部傳入的參數為:",kwargs)
return add
# 執行順序:
# 1.運行Decorator函數,先打印外部的傳入的參數,返回add函數名;
# 2.再執行School = add(School)
# Decorator(addr = "浙江省杭州市",name ="浙江大學")(School)
@Decorator(addr = "浙江省杭州市",name ="浙江大學")
class School():
def __init__(self,price):
self.price =price
```
#### 類裝飾函數或方法
##### 無參數
```python
class tracer:
def __init__(self,func):
print('__init__ is called' )
self.calls = 0
self.func = func
def __call__(self,*args):
self.calls += 1
print(args)
print('call %s to %s' %(self.calls, self.func.__name__))
self.func(*args)
@tracer
def spam(a, b, c):
print(a + b + c)
'''
1. 相當于 tracer(spam(1,2,3))
2. 調用順序:__init__(spam) -> __call__() -> spam(a, b, c)
'''
spam(1,2,3)
```
##### 有參數
```python
class tracer:
def __init__(self, *args):
self.calls = 0
self.args = args
print(args)
def __call__(self, func):
self.func = func
def realfunc(*args):
self.calls += 1
print('call %s to %s' %(self.calls, self.func.__name__))
self.func(*args)
return realfunc
# 相當于tracer("xxxx")(spam)
# 調用順序 __init__("xxxx") -> __call__(spam) -> realfunc(a, b, c) -> spam(a, b, c)
@tracer("xxxx")
def spam(a, b, c):
print(a + b + c)
spam(1,2,3)
```
## 繼承
```python
class Parent:
def myMethod(self):
print ('調用父類方法')
class Child(Parent):
def myMethod(self):
# super().myMethod(); # Python3,如果super在類里面的話,可以省略里面的參數
print ('調用子類方法')
c = Child() # 子類實例
c.myMethod() # 子類調用重寫方法
super(Child,c).myMethod() #用子類對象調用父類已被覆蓋的方法
```
## 多重繼承
在設計類的繼承關系時,通常,主線都是單一繼承下來的,如果需要“混入”額外的功能,通過多重繼承就可以實現。
```python
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
```
## 定制類(魔術方法)
類似PHP中的魔術方法
```python
# __str__ # 打印實例時調用
# __repr__ # console直接敲命令時調用
# object python的頂級父類
class Student(object):
def __init__(self, name):
self.name = name
# print打印時觸發
def __str__(self):
return 'Student object (name: %s)' % self.name
# 控制臺輸出時觸發
__repr__ = __str__ # 在Python console直接敲命令時調用__repr__方法,又因為__repr__方法跟__str__的內容一樣
zxg = Student('zxg')
print(zxg) # Student object (name: zxg)
# __iter__ 實現迭代
# __getitem__ list化之后取第幾個元素及切片
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個計數器a,b
def __iter__(self):
return self # 實例本身就是迭代對象,故返回自己
# 循環時觸發
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100: # 退出循環的條件
raise StopIteration()
return self.a # 返回下一個值
# list化之后切片觸發
def __getitem__(self, n):
print(n)
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
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
f = Fib()
f[5] # 5
f[:5] # slice(1, 5, None)
for n in f:
print(n)
# __setitem__() # 設置元素時觸發
# __delitem__() # 刪除元素時觸發
# 通過以上的幾個魔術方法,可以把自己定義的類表現出list,dict,tuple的特性
# __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.list # /status/user/list 實現鏈式操作
# __call__ # 對實例直接調用
class Student(object):
def __init__(self, name):
self.name = name
# 對實例直接調用的時候,調用的就是這個方法
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() # 直接可以調用
# __new__ 當你繼承一些不可變的class時(比如int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑。還有就是實現自定義的metaclass。
```
## 待處理
__del__
__enter__
__exit__
## 使用枚舉類
```python
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month) # <enum 'Month'>
for name, member in Month.__members__.items():
# Sep => Month.Sep , 9
print(name, '=>', member, ',', member.value)
```
## __new__ 和 __init__
> **實際上在我們實例化對象時,python默認執行了__new__操作,先創建類實例,然后返回給__init__初始化實例**
```python
# python新類允許用戶重載__new__和__init__方法。__new__創建類實例,__init__初始化一個已被創建的實例;
class newStyleClass(object):
# __new__在頂級父類object是一個靜態方法
# cls代表當前類
def __new__(cls):
print("__new__ is called")
return super(newStyleClass, cls).__new__(cls)
# self
def __init__(self):
print("__init__ is called")
print("self is: ", self)
newStyleClass()
# 我們發現__new__函數先被調用,接著__init__函數在__new__函數返回一個實例的時候被調用,并且這個實例作為self參數被傳入了__init__函數
# 若__new__()沒有正確返回當前類cls的實例,那__init__()將不會被調用,即使是父類的實例也不行。
obj = 12
# obj can be an object from any class, even object.__new__(object)
class returnExistedObj(object):
def __new__(cls):
print("__new__ is called")
return obj # __init__不被調用
# return super(returnExistedObj,cls).__new__(cls)
def __init__(self):
print("__init__ is called")
print("self is: ", self)
returnExistedObj()
```
## 使用元類
### 通過type函數臨時創建類
Python函數和類的定義,不是編譯時定義的,而是運行時動態創建的;所以可以在運行時臨時動態的創建類
`type`傳入三個參數:
1. class的名稱;
1. 繼承的父類集合,注意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
1. class的方法名稱與函數綁定,這里我們把函數fn綁定到方法名hello上。這里的dict的參數是**kws;
```python
def fn(self, name='world'): # 先定義函數
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 創建Hello class
o = Hello()
o.hello()
```
### metaclass
除了使用type()動態創建類以外,要控制類的創建行為,還可以使用metaclass。先定義metaclass,就可以創建類,最后創建實例。
metaclass是Python面向對象里最難理解,也是最難使用的魔術代碼。正常情況下,你不會碰到需要使用metaclass的情況
```python
# metaclass是類的模板(類的類),所以必須從`type`類型派生:
# metaclass的類名總是以Metaclass結尾
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 有了ListMetaclass,我們在定義類的時候還要指示使用ListMetaclass來定制類,傳入關鍵字參數metaclass
# 當我們傳入關鍵字參數metaclass時,魔術就生效了,它指示Python解釋器在創建MyList時,要通過ListMetaclass.__new__()來創建,在此,我們可以修改類的定義,比如,加上新的方法,然后,返回修改后的定義。
class MyList(list, metaclass=ListMetaclass):
pass
L = MyList()
L.add(1)
print(L)
```
**元類的作用**
1. 攔截類的創建;
2. 修改類;
3. 返回修改之后的類;
```python
# 看下面這個例子
# type其實也是個元類
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)
# 我們知道在python中一切皆對象
# 那么下面我們的s其實就是一個類對象,它的__name__其實和Foo的__name__是一樣的
# 看下面的方法是不是和type創建類有點像?
s = UpperAttrMetaclass('Foo',(object,),{'bar':'bip'})
print(hasattr(s,'bar')) # False
print(hasattr(s,'BAR')) # True
print(s.BAR) # bip
class Foo(object,metaclass=UpperAttrMetaclass):
bar='bip'
print(hasattr(Foo,'bar'))
print(hasattr(Foo,'BAR'))
print(Foo.BAR)
# 最后它們輸出的結果其實是一模一樣的,這就說明了類其實和我們普通的實例對象差不多,只不過普通實例是通過類創建,而類是通過元類創建
# 而__new__就是用來創建實例的,不論是普通的實例,還是類實例,總之就是個實例
```
**意義**
實現面向對象高級編程,體現其“開閉原則”:把擴展打開,把修改關閉。
比如: 編寫一個ORM框架,所有的類都只能動態定義,因為只有使用者才能根據表的結構定義出對應的類來。
```python
class Field(object):
def __init__(self, name, column_type):
self.name = name # 字段名
self.column_type = column_type # 字段類型
def __str__(self):
return '<%s: %s>' % (self.__class__.__name__, self.name)
class StringField(Field):
def __init__(self, name):
# super().__init__(name, 'varchar(100)') # Python3里面的super可以省略StringField, self
super(StringField, self).__init__(name, 'varchar(100)') # 字段名和字段類型
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint') # 字段名和字段類型
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs): # cls: 正在被生成的對象, name: 生成該對象的類名, bases: 父類集合, attrs: 屬性方法集合
if name == 'Model': # 若類名為Model, 則是對Model的修改,這里是原樣返回
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name) # 輸出類的名字
mappings = dict() # 初始化user類的屬性方法字典
for key, value in attrs.items():
if isinstance(value, Field):
print('Found mappings: %s ==> %s' % (key, value)) # 輸出屬性類型和屬性名
mappings[key] = value # 將屬性類型(key)和屬性名(value)加入到字典
for key in mappings.keys():
attrs.pop(key) # 類屬性中刪除該Field屬性, 否則容易造成運行時錯誤(實例的屬性會遮蓋類的同名屬性)
attrs['__mappings__'] = mappings # 將屬性方法字典加入到屬性方法集合中
attrs['__table__'] = name # 表名(此處假設類名即為表名)
return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass): # 使用ModelMetaclass來創建類
def __init__(self, **kw): # **kw命名關鍵詞參數
print(kw)
super(Model, self).__init__(**kw)
def __getattr__(self, key): # 獲得values
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value): # 設置屬性/方法名
self[key] = value
def save(self): # 將屬性保存到數據庫(此處僅為生成相應的SQL語句)
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name) # 將字段名加入到fields中
params.append('?') # 向params中添加xxx個問號
args.append(getattr(self, k, None)) # 將values加入args中
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params)) # 生成SQL語句, join方法是將list中的內容以前面的符號作為分隔連在一起
print('SQL: %s' % sql)
print('ARGS: %s' % args)
class User(Model):
# 定義類的屬性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 創建一個實例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到數據庫:
u.save()
# 過程是
'''
從上到下執行:
Model => ModelMetaclass => User(以及里面的屬性IntegerField,StringField等) => ModelMetaclass => Model.save
'''
```
### `__metaclass__`
python2繼承元類的語法
```python
# python3
class Model(dict, metaclass=ModelMetaclass):
pass
# python2
class Model(dict):
__metaclass__ = ModelMetaclass
pass
```