基礎概念:[Python] 08 - Classes --> Objectshtml
進階概念:[Advanced Python] 11 - Implement a Classpython
參考資源:廖雪峯,面向對象編程 linux
參考資源:廖雪峯,面向對象高級編程c++
參考資源:錯誤、調試和測試數據庫
不妨考慮下__init__中採用 DataFrame結構。編程
一個類實例也能夠成爲相似函數這樣能直接調用的對象,只要定義的時候有__call__()方法就能夠。設計模式
__slots__ = (...),減小沒必要要的靈活性從而節省內存空間。安全
類內的 類方法和實例方法的設計與實現。數據結構
多繼承、元類、異常處理、單元測試。多線程
class datetime的設計和實現。
Python做爲動態語言,類的屬性能夠動態添加,是否是太靈活了呢?那就限制一下屬性的添加範圍。
因爲'score'
沒有被放到__slots__
中,因此不能綁定score
屬性,試圖綁定score
將獲得AttributeError
的錯誤。
注意:使用__slots__
要注意,__slots__
定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的:
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'
動態語言意味着「浪費空間」換取「靈活性」。顯然,減小沒必要要的靈活性能夠 reduce memory of RAM。
From: 10. __slots__ Magic
使用後帶來的內存環保效果以下:
It involves the usage of __slots__
to tell Python not to use a dict, and only allocate space for a fixed set of attributes. Here is an example with and without __slots__
:
Without __slots__
:
class MyClass(object): def __init__(self, name, identifier): self.name = name self.identifier = identifier self.set_up() # ...
With __slots__
:
class MyClass(object): __slots__ = ['name', 'identifier'] def __init__(self, name, identifier): self.name = name self.identifier = identifier self.set_up() # ...
The second piece of code will reduce the burden on your RAM. Some people have seen almost 40% to 50% reduction in RAM usage by using this technique.
原文連接:http://www.javashuo.com/article/p-wvqvpqvy-ks.html
Ref: python中@classmethod @staticmethod區別
class Person(object): grade=6 # 類變量 def __init__(self): self.name = "king" self.age=20
----------------------------------------------------------------------- def sayHi(self): #加self區別於普通函數 print ('Hello, your name is?',self.name) def sayAge(self): print( 'My age is %d years old.'%self.age) ----------------------------------------------------------------------- @staticmethod # 靜態方法不能訪問類變量和實例變量,也不能使用self def sayName(): print ("my name is king") @classmethod # 類方法能夠訪問類變量,但不能訪問實例變量 def classMethod(cls): #print('cls:',cls) print('class grade:',cls.grade) #print("class method") class Child(object): def __init__(self): self.name = "小明" self.age=20 def sayName(self, obj): print('child name is:', self.name) print(obj.sayName) print(obj.sayName()) # 這裏要特別注意帶括號和不帶括號的區別:一個是對象,一個是方法 p = Person() # 實例化對象 print('p.grade:',p.grade) # 實例對象調用類變量 p.grade=9 print(p.classMethod(), p.grade) # 實例改變類變量時,其grade變量只會在實例中改變 print(Person().grade) # 類對象調用類變量 p.sayHi() # 實例對象調用類成員函數 Person().sayAge() # 類對象調用類成員函數 p.sayName() # 實例對象調用類靜態方法 m=Person() m.sayName() # 多個實例共享此靜態方法 Person().sayName() # 類對象調用靜態方法 p.classMethod() # 實例對象調用類方法 Person.classMethod() # 類對象調用類方法 # 調用類 tt=Child() tt.sayName(Person())
什麼時候使用?
Ref: class method vs static method in Python
爲什麼這裏提到了」工廠方法「?由於工廠方法須要返回類,而類方法是'天生的」具有cls參數。
而靜態方法即便在類中,也沒法達到被繼承的效果。
Ref: Python classmethod()
from datetime import date # random Person class Person: def __init__(self, name, age): self.name = name self.age = age @classmethod def fromBirthYear(cls, name, birthYear): return cls(name, date.today().year - birthYear) def display(self): print(self.name + "'s age is: " + str(self.age))
# (1) 常規方式返回一個實例對象 person = Person('Adam', 19) person.display()
# (2) 類方法返回一個實例對象 person1 = Person.fromBirthYear('John', 1985) person1.display()
類函數 ----> classmethod
printAge本來是個普通函數,經過classmethod變爲類方法。
class Person: age = 25 def printAge(cls): print('The age is:', cls.age) # create printAge class method Person.printAge = classmethod(Person.printAge) Person.printAge()
須要注意下第一種方法,沒有self 以及 cls,實例對象不能調用的一種「類方法」。
class Person(object):
########################################
# 類方法的 叄種 定義方式
########################################
# [第壹種方法] 不加任何參數直接定義,也是類方法
def Work():
print(" I am working!")
# [第貳種方法] 加裝飾器方法 @classmethod def Think(cls, b): #類方法Think必需要帶至少1個參數,第一個參數默認爲類名,後面能夠引用。 cls.Eat(b) #在類方法Think中,調用類方法Eat類方法。 cls.Work() #在類方法Think中,調用Work類方法。 print(b,",I am Thinking!") # [第叄種方法] 先定義類方法,至少1個參數,第一個默認爲類名 def Eat(cls, b): print(b+",I am eating") Eat=classmethod(Eat) #第二種方法:經過內建函數classmethod()來建立類方法。
-----------------------------------------------------------------------------------
# 靜態方法,引用時直接用類名.Sleep()便可。 @staticmethod def Sleep(): print("I am sleeping") # 這種方法是:實例對象調用方法 def __scolia__(self): print("scola") return "scola"
-----------------------------------------------------------------------------------
# 實例對象能夠訪問的私有方法,在類方法中能夠相互調用和使用。類不能直接訪問或者外部訪問。 def __good(self): print("good") return "good" Person.Think("li") Person.Eat("jcy") Person.Work() # a.Work() 報錯,實例對象不能調用類方法 Person.Sleep() a=Person() a.__colia__() # 魔術方法,沒有私有化。 #a.__good() # 私有方法:報錯了!
Yes. You can define a function outside of a class and then use it in the class body as a method.
link: https://stackoverflow.com/questions/9455111/define-a-method-outside-of-class-definition
def func(self): print("func") class MyClass: myMethod = func
1. 倒三角狀況,只需「括號中排前面的父類的方法」優先,便可解決。
2. 鑽石繼承時,c++中採用虛函數解決"路徑歧義性」, goto: [c++] Class,那 python 怎麼辦?
class Father(object): def __init__(self, name): self.name = name print("Im father") class Son_1(Father): def __init__(self, age, name): self.age = age Father.__init__(self, name) print("Im Son_1") class Son_2(Father): def __init__(self, gender, name): self.gender = gender Father.__init__(self, name) print("我是Son_2") class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender): Son_1.__init__(self, age, name) # init兩次,看上去也很怪 Son_2.__init__(self, gender, name) pass
grand_son = GrandSon("張三", 18, "男") print(GrandSon.__mro__)
Father類被執行了兩次!有問題。
Im father Im Son_1 Im father 我是Son_2 (<class '__main__.GrandSon'>, <class '__main__.Son_1'>, <class '__main__.Son_2'>, <class '__main__.Father'>, <class 'object'>)
Ref:多繼承(鑽石繼承)的問題和解決
使用虛基類和虛繼承可讓一個指定的基類在繼承體系中將其成員數據實例共享給從該基類直接或間接派生出的其它類,即便從不一樣路徑繼承來的同名數據成員在內存中只有一個拷貝,同一個函數名也只有一個映射。
用super()確實可以解決父類__init__重複調用的問題,可是這個傳參是個麻煩,這樣一來,Son1和Son2的參數須要根據grandSon的參數來變化,並且不能寫死,這樣很是不合理。
參數的順序是要保持一致的,這個限制條件看上去不是很「和諧」,須要繼續改進。
class Father(object): def __init__(self, name): self.name = name print("Im father") class Son_1(Father): def __init__(self, name, age, gender): self.age = age super(Son_1, self).__init__(name, gender) print("Im Son_1") class Son_2(Father): def __init__(self, name, gender): self.gender = gender super(Son_2, self).__init__(name) print("我是Son_2") class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender): super(GrandSon, self).__init__(name, age, gender) print("我是GrandSon")
grand_son = GrandSon("張三", 19, "男",) print(GrandSon.__mro__)
class Father(object): def __init__(self, name, *args, **kwargs): self.name = name print("我是父類__init__") class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = age class Son_2(Father): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") self.gender = gender super(Son_2, self).__init__(name, *args, **kwargs) class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender): super(GrandSon, self).__init__(name, age, gender)
def say_hello(self): print(self.name, self.age, self.gender)
grand_son = GrandSon("老王", 24, "男")
Output:
Python 3.7.4 (default, Jul 9 2019, 00:06:43) [GCC 6.3.0 20170516] on linux 我是Son_1的__init__ 我是Son_2的__init__ 我是父類__init__
Ref: 使用元類
不只能夠動態建立一個「對象」,也能夠建立一個「類」。
type()
函數既能夠返回一個對象的類型,又能夠建立出新的類型,好比,咱們能夠經過type()
函數建立出Hello
類,而無需經過class Hello(object)...
的定義:
>>> def fn(self, name='world'): # 先定義函數 ... print('Hello, %s.' % name) ...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class >>> h = Hello() >>> h.hello() Hello, world.
>>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>
控制類的建立行爲,還可使用metaclass。
先定義metaclass,就能夠建立類,最後建立實例。
More details: https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
動態修改有什麼意義?直接在MyList
定義中寫上add()
方法不是更簡單嗎?正常狀況下,確實應該直接寫,經過metaclass修改純屬變態。
可是,總會遇到須要經過metaclass修改類定義的。ORM就是一個典型的例子。
ORM全稱「Object Relational Mapping」,即對象-關係映射,就是把關係數據庫的一行映射爲一個對象,也就是一個類對應一個表,這樣,寫代碼更簡單,不用直接操做SQL語句。
要編寫一個ORM框架,全部的類都只能動態定義,由於只有使用者才能根據表的結構定義出對應的類來。
with open(self.file_path) as file: while 1: lines = file.readlines(1000) if not lines: break for line in lines: items = line.strip().split('|')
print(itmes)
try: weekday = self.fnWhichWeekday(str_weekday) y = int(str_y) yhat = int(str_yhat) print("weekday: {}, y: {}, yhat: {}".format(weekday, y, yhat)) except Exception as e: print("Invaid line.") continue;
try: F1 = 2.0/((1.0/self.PRECISION)+(1.0/self.RECALL)) except ZeroDivisionError as e: print("ZeroDivisionError", e) print("Debug03: PRECISION: {}, RECALL: {}".format(self.PRECISION, self.RECALL))
1 from datetime import datetime 2 import re 3 4 class CalculatorML: 5 6 TP=0 7 TN=0 8 FP=0 9 FN=0 10 PRECISION=0 11 RECALL=0 12 13 def __init__(self, file_path, start_line): 14 self.file_path = file_path 15 self.start_line = start_line 16 17 18 def fnWhichWeekday(self, text): 19 match = re.search(r'\d{4}-\d{2}-\d{2}', text) # 找到時間字符串 20 date = datetime.strptime(match.group(), '%Y-%m-%d').date() # 時間格式轉換 21 weekday = date.weekday() 22 return weekday 23 24 25 def fnScanLines(self): 26 with open(self.file_path) as file: 27 while 1: 28 lines = file.readlines(1000) 29 if not lines: 30 break 31 for line in lines: 32 items = line.strip().split('|') 33 34 # valid line. 35 if len(items) is not 3: 36 continue 37 38 str_weekday = items[0] 39 str_y = items[1] 40 str_yhat = items[2] 41 42 # valid item. 43 try: 44 weekday = self.fnWhichWeekday(str_weekday) 45 y = int(str_y) 46 yhat = int(str_yhat) 47 print("weekday: {}, y: {}, yhat: {}".format(weekday, y, yhat)) 48 except Exception as e: 49 print("Invaid line.") 50 continue; 51 52 # TP 53 if (y is yhat) and (y is 1): 54 self.TP += 1 55 # TN 56 if (y is not yhat) and (y is 1): 57 self.TN += 1 58 # FP 59 if (y is yhat) and (y is 0): 60 self.FP += 1 61 # FN 62 if (y is not yhat) and (y is 0): 63 self.FN += 1 64 65 print("Finally, TP: {}, TN: {}, FP: {}, FN: {}".format(self.TP, self.TN, self.FP, self.FN)) 66 67 68 def fnSetPrecision(self): 69 try: 70 self.PRECISION=(self.TP*1.0)/(self.TP+self.FP) 71 except ZeroDivisionError as e: 72 print("ZeroDivisionError", e) 73 print("Debug01: TP: {}, TP: {}, FP: {}".format(self.TP, self.TP, self.FP)) 74 75 def fnSetRecall(self): 76 try: 77 self.RECALL=(self.TP*1.0)/(self.TP+self.FN) 78 except ZeroDivisionError as e: 79 print("ZeroDivisionError", e) 80 print("Debug02: jTP: {}, TP: {}, FN: {}".format(self.TP, self.TP, self.FN)) 81 82 def fnGetF1(self): 83 84 self.fnScanLines() 85 self.fnSetPrecision() 86 self.fnSetRecall() 87 88 try: 89 F1 = 2.0/((1.0/self.PRECISION)+(1.0/self.RECALL)) 90 91 except ZeroDivisionError as e: 92 print("ZeroDivisionError", e) 93 print("Debug03: PRECISION: {}, RECALL: {}".format(self.PRECISION, self.RECALL)) 94 95 return F1; 96 97 98 filePath = "./test.psv" 99 start_line = 3 100 101 calculator = CalculatorML(filePath, start_line) 102 F1 = calculator.fnGetF1() 103 print(F1)
程序能一次寫完並正常運行的機率很小,基本不超過1%。總會有各類各樣的bug須要修正。
[Optimized Python] 17 - Performance bottle-neck
In [52]: import datetime In [53]: datetime.__file__ Out[53]: '/usr/local/anaconda3/lib/python3.7/datetime.py'
這裏,datetime繼承了date類,是一個不錯的分析案例。
class datetime(date): """datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) The year, month and day arguments are required. tzinfo may be None, or an instance of a tzinfo subclass. The remaining arguments may be ints. """ __slots__ = date.__slots__ + time.__slots__ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
if (isinstance(year, (bytes, str)) and len(year) == 10 and 1 <= ord(year[2:3])&0x7F <= 12): # Pickle support if isinstance(year, str): try: year = bytes(year, 'latin1') except UnicodeEncodeError: # More informative error message. raise ValueError( "Failed to encode latin1 string when unpickling " "a datetime object. " "pickle.load(data, encoding='latin1') is assumed.") self = object.__new__(cls) self.__setstate(year, month) self._hashcode = -1 return self
year, month, day = _check_date_fields(year, month, day) hour, minute, second, microsecond, fold = _check_time_fields( hour, minute, second, microsecond, fold) _check_tzinfo_arg(tzinfo) self = object.__new__(cls) self._year = year self._month = month self._day = day self._hour = hour self._minute = minute self._second = second self._microsecond = microsecond self._tzinfo = tzinfo self._hashcode = -1 self._fold = fold return self
date類大概分爲以下幾個部分:
類註釋 __slots__ def __new__ @classmethod __str__ @property 類運算符重載
Ref: python的__new__方法
最重要的「有返回值」,就是返回了self。
class Person(object): def __new__(cls, name, age): print '__new__ called.' return super(Person, cls).__new__(cls, name, age) def __init__(self, name, age): print '__init__ called.' self.name = name self.age = age def __str__(self): return '<Person: %s(%s)>' % (self.name, self.age) if __name__ == '__main__': name = Person('xxx', 24) print name
何時須要__new__方法。
new方法主要是當你繼承一些不可變的class時(好比int, str, tuple), 提供給你一個自定義這些類的實例化過程的途徑。還有就是實現自定義的metaclass。
具體咱們能夠用int來做爲一個例子:這是一個不可變的類,但咱們還想繼承它的優良特性,怎麼辦?
class PositiveInteger(int):
def __new__(cls, value): return super(PositiveInteger, cls).__new__(cls, abs(value)) i = PositiveInteger(-3) print i
隨着編程語言的演進,一些設計模式(如單例)也隨之過期,甚至成了反模式(請參考網頁[ t.cn/zRoxwyD]),另外一些則被內置在編程語言中(如迭代器模式)。
另外,也有一些新的模式誕生(好比Borg/Monostate,請參考網頁[ t.cn/zWOOOZC]和[ t.cn/RqrKbBe])
單例模式,須要在類的」建立部分「作一些手腳,也就天然和__new__扯上關係。
class Singleton(object): __instance = None __first_init = True def __new__(cls, age, name): if not cls.__instance: cls.__instance = object.__new__(cls) return cls.__instance def __init__(self, age, name): if self.__first_init: self.age = age self.name = name Singleton.__first_init = False a = Singleton(18, "xxx") b = Singleton(8, "xxx") # (1) 實際上是同一個對象 print(id(a)) # (2) 此處相等,也說明了是"同一個對象" print(id(b)) print(a.age) print(b.age) # (3) 由於是同一個對象,即便b的__init__沒作什麼,b依然能夠拿來去用,由於b實際上就是a的引用 a.age = 19 print(b.age)
輸出:
139953926130600 139953926130600 18 18 19
但,慎用單例模式!
類方法,這裏返回的都是類。有必要麼?爲何?
@classmethod def fromtimestamp(cls, t): "Construct a date from a POSIX timestamp (like time.time())." y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t) return cls(y, m, d) @classmethod def today(cls): "Construct a date from time.time()." t = _time.time() return cls.fromtimestamp(t) @classmethod def fromordinal(cls, n): """Construct a date from a proleptic Gregorian ordinal. January 1 of year 1 is day 1. Only the year, month and day are non-zero in the result. """ y, m, d = _ord2ymd(n) return cls(y, m, d) @classmethod def fromisoformat(cls, date_string): """Construct a date from the output of date.isoformat().""" if not isinstance(date_string, str): raise TypeError('fromisoformat: argument must be str') try: assert len(date_string) == 10 return cls(*_parse_isoformat_date(date_string)) except Exception: raise ValueError(f'Invalid isoformat string: {date_string!r}')
爲什麼返回一個類?
—— 由於暫時還不清楚user的結果細節,例如什麼格式的結果。
import datetime end_time = 1525104000000 d = datetime.datetime.fromtimestamp(end_time / 1000, None) # 時間戳轉換成字符串日期時間,返回的實際上是一個類!
str1 = d.strftime("%Y-%m-%d %H:%M:%S.%f")
print(d) # 2018-05-01 00:00:00 print(str1) # 2018-05-01 00:00:00.000000
對比c++,能夠經過」函數重載「 (function overloading)。但python對於構造函數而言,能夠經過 @classmethod 補充這個效果。
End.