├── .gitignore ├── CONTRIBUTORS.markdown ├── Makefile ├── README.markdown ├── appendix.markdown ├── listings ├── access.py ├── call.py ├── closer.py ├── descriptor.py ├── fileobject.py ├── list.py ├── slate.py └── word.py ├── magicmarkdown.py ├── magicmethods.html ├── magicmethods.markdown ├── magicmethods.pdf ├── magicmethods.py ├── magicmethods.tex ├── style.css └── table.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.markdown: -------------------------------------------------------------------------------- 1 | ## Thanks to the following people for helping out ## 2 | 3 | - stevelosh on Reddit for contributing the stylesheet 4 | - Andrew Dalke for pointing out that I was missing `__index__`, `__getstate__`, and `__setstate__` 5 | - ewiethoff on Reddit for suggesting a table mapping magic methods to any special syntax that invokes them 6 | - richleland (Richard Leland) for providing styles for Pygments syntax highlighting 7 | - Simon Sapin for pointing out a number of bugs 8 | - sarenji (David Peter) for pointing out some bugs in the examples 9 | - michaelcontento (Michael Contento) for fixing a missing argument in `__exit__` 10 | - nosklo for giving some constructive criticism on #python 11 | - rhettinger for pointing out a number of omissions in the guide 12 | - aasted for pointing out an error in the organization of the guide 13 | - seliopou for making the guide a little more visually appealing 14 | - redtoad for correcting some typos 15 | - bobuss for finding an error in the `AccessCounter` example 16 | - Andrew-Crosio for finding yet another typo 17 | - william-mi-leslie for finding weaknesses and errors in the guide's treatment of operator overloading 18 | - petrushev for expanding the guide's description of `__reversed__` -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | docs: magicmethods.html magicmethods.pdf clean 2 | 3 | html: magicmethods.html 4 | 5 | pdf: magicmethods.pdf 6 | 7 | magicmethods.html: table.markdown magicmethods.markdown appendix.markdown 8 | python magicmarkdown.py 9 | 10 | magicmethods.pdf: magicmethods.tex 11 | pdflatex magicmethods.tex 12 | 13 | clean: 14 | rm -f markedup.html magicmethods.log magicmethods.dvi magicmethods.aux 15 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ## Python 的神奇方法指南 2 | 3 | 4 | 原文:[A Guide to Python's Magic Methods](http://www.rafekettler.com/magicmethods.html) 5 | 6 | 作者:Rafe Kettler 7 | 8 | 译文:[Python 的神奇方法指南](./magicmethods.markdown) 9 | 10 | 本文在 Creative Commons CC--NC-BY-SA (see http://creativecommons.org/licenses/by-nc-sa/3.0/) 协议下发布。 11 | 12 | ## 开始阅读 13 | 14 | [目录](./table.markdown) 15 | -------------------------------------------------------------------------------- /appendix.markdown: -------------------------------------------------------------------------------- 1 | ##Appendix 1: How to Call Magic Methods## 2 | 3 | Some of the magic methods in Python directly map to built-in functions; in this case, how to invoke them is fairly obvious. However, in other 4 | cases, the invocation is far less obvious. This appendix is devoted to exposing non-obvious syntax that leads to magic methods getting called. 5 | 6 | Magic Method | When it gets invoked (example) | Explanation 7 | ---------------------- | ---------------------------------- | -------------------------------------------- 8 | `__new__(cls [,...])` | `instance = MyClass(arg1, arg2)` | `__new__` is called on instance creation 9 | `__init__(self [,...])` | `instance = MyClass(arg1, arg2)` | `__init__` is called on instance creation 10 | `__cmp__(self, other)` | `self == other`, `self > other`, etc. | Called for any comparison 11 | `__pos__(self)` | `+self` | Unary plus sign 12 | `__neg__(self)` | `-self` | Unary minus sign 13 | `__invert__(self)` | `~self` | Bitwise inversion 14 | `__index__(self)` | `x[self]` | Conversion when object is used as index 15 | `__nonzero__(self)` | `bool(self)` | Boolean value of the object 16 | `__getattr__(self, name)` | `self.name # name doesn't exist` | Accessing nonexistent attribute 17 | `__setattr__(self, name, val)` | `self.name = val` | Assigning to an attribute 18 | `__delattr__(self, name)` | `del self.name` | Deleting an attribute 19 | `__getattribute__(self, name)` | `self.name` | Accessing any attribute 20 | `__getitem__(self, key)` | `self[key]` | Accessing an item using an index 21 | `__setitem__(self, key, val)` | `self[key] = val` | Assigning to an item using an index 22 | `__delitem__(self, key)` | `del self[key]` | Deleting an item using an index 23 | `__iter__(self)` | `for x in self` | Iteration 24 | `__contains__(self, value)` | `value in self`, `value not in self` | Membership tests using `in` 25 | `__call__(self [,...])` | `self(args)` | "Calling" an instance 26 | `__enter__(self)` | `with self as x:` | `with` statement context managers 27 | `__exit__(self, exc, val, trace)` | `with self as x:` | `with` statement context managers 28 | `__getstate__(self)` | `pickle.dump(pkl_file, self)` | Pickling 29 | `__setstate__(self)` | `data = pickle.load(pkl_file)` | Pickling 30 | 31 | Hopefully, this table should have cleared up any questions you might have had about what syntax invokes which magic method. 32 | 33 | ##Appendix 2: Changes in Python 3## 34 | 35 | Here, we document a few major places where Python 3 differs from 2.x in terms of its object model: 36 | 37 | - Since the distinction between string and unicode has been done away with in Python 3, `__unicode__` is gone and `__bytes__` (which behaves similarly to `__str__` and `__unicode__` in 2.7) exists for a new built-in for constructing byte arrays. 38 | - Since division defaults to true division in Python 3, `__div__` is gone in Python 3 39 | - `__coerce__` is gone due to redundancy with other magic methods and confusing behavior 40 | - `__cmp__` is gone due to redundancy with other magic methods 41 | - `__nonzero__` has been renamed to `__bool__` 42 | -------------------------------------------------------------------------------- /listings/access.py: -------------------------------------------------------------------------------- 1 | class AccessCounter(object): 2 | '''A class that contains a value and implements an 3 | access counter. The counter increments each time the 4 | value is changed.''' 5 | 6 | def __init__(self, val): 7 | super(AccessCounter, self).__setattr__('counter', 0) 8 | super(AccessCounter, self).__setattr__('value', val) 9 | 10 | def __setattr__(self, name, value): 11 | if name == 'value': 12 | super(AccessCounter, self).__setattr__('counter', 13 | self.counter + 1) 14 | # Make this unconditional. 15 | # If you want to prevent other attributes to be set, 16 | # raise AttributeError(name) 17 | super(AccessCounter, self).__setattr__(name, value) 18 | 19 | def __delattr__(self, name): 20 | if name == 'value': 21 | super(AccessCounter, self).__setattr__('counter', 22 | self.counter + 1) 23 | super(AccessCounter, self).__delattr__(name)] 24 | -------------------------------------------------------------------------------- /listings/call.py: -------------------------------------------------------------------------------- 1 | class Entity: 2 | '''Class to represent an entity. Callable to update 3 | the entity's position.''' 4 | 5 | def __init__(self, size, x, y): 6 | self.x, self.y = x, y 7 | self.size = size 8 | 9 | def __call__(self, x, y): 10 | '''Change the position of the entity.''' 11 | self.x, self.y = x, y 12 | 13 | # snip... 14 | -------------------------------------------------------------------------------- /listings/closer.py: -------------------------------------------------------------------------------- 1 | class Closer: 2 | '''A context manager to automatically close an object with a 3 | close() method in a with statement.''' 4 | 5 | def __init__(self, obj): 6 | self.obj = obj 7 | 8 | def __enter__(self): 9 | return self.obj # bound to target 10 | 11 | def __exit__(self, exception_type, exception_val, trace): 12 | try: 13 | self.obj.close() 14 | except AttributeError: # obj isn't closable 15 | print 'Not closable.' 16 | return True # exception handled successfully 17 | -------------------------------------------------------------------------------- /listings/descriptor.py: -------------------------------------------------------------------------------- 1 | class Meter(object): 2 | '''Descriptor for a meter.''' 3 | 4 | def __init__(self, value=0.0): 5 | self.value = float(value) 6 | def __get__(self, instance, owner): 7 | return self.value 8 | def __set__(self, instance, value): 9 | self.value = float(value) 10 | 11 | class Foot(object): 12 | '''Descriptor for a foot.''' 13 | 14 | def __get__(self, instance, owner): 15 | return instance.meter * 3.2808 16 | def __set__(self, instance, value): 17 | instance.meter = float(value) / 3.2808 18 | 19 | class Distance(object): 20 | '''Class to represent distance holding two descriptors for feet and 21 | meters.''' 22 | meter = Meter() 23 | foot = Foot() 24 | 25 | -------------------------------------------------------------------------------- /listings/fileobject.py: -------------------------------------------------------------------------------- 1 | from os.path import join 2 | 3 | class FileObject: 4 | '''Wrapper for file objects to make sure the file gets closed 5 | on deletion.''' 6 | 7 | def __init__(self, filepath='~', filename='sample.txt'): 8 | # open a file filename in filepath in read and write mode 9 | self.file = open(join(filepath, filename), 'r+') 10 | 11 | def __del__(self): 12 | self.file.close() 13 | del self.file 14 | -------------------------------------------------------------------------------- /listings/list.py: -------------------------------------------------------------------------------- 1 | class FunctionalList: 2 | '''A class wrapping a list with some extra functional 3 | magic, like head, tail, init, last, drop, and take.''' 4 | 5 | def __init__(self, values=None): 6 | if values is None: 7 | self.values = [] 8 | else: 9 | self.values = values 10 | 11 | def __len__(self): 12 | return len(self.values) 13 | 14 | def __getitem__(self, key): 15 | # if key is of invalid type or value, the list values 16 | # will raise the error 17 | return self.values[key] 18 | 19 | def __setitem__(self, key, value): 20 | self.values[key] = value 21 | 22 | def __delitem__(self, key): 23 | del self.values[key] 24 | 25 | def __iter__(self): 26 | return iter(self.values) 27 | 28 | def __reversed__(self): 29 | return reversed(self.values) 30 | 31 | def append(self, value): 32 | self.values.append(value) 33 | def head(self): 34 | # get the first element 35 | return self.values[0] 36 | def tail(self): 37 | # get all elements after the first 38 | return self.values[1:] 39 | def init(self): 40 | # get elements up to the last 41 | return self.values[:-1] 42 | def last(self): 43 | # get last element 44 | return self.values[-1] 45 | def drop(self, n): 46 | # get all elements except first n 47 | return self.values[n:] 48 | def take(self, n): 49 | # get first n elements 50 | return self.values[:n] 51 | -------------------------------------------------------------------------------- /listings/slate.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | class Slate: 4 | '''Class to store a string and a changelog, and forget its value when 5 | pickled.''' 6 | 7 | def __init__(self, value): 8 | self.value = value 9 | self.last_change = time.asctime() 10 | self.history = {} 11 | 12 | def change(self, new_value): 13 | # Change the value. Commit last value to history 14 | self.history[self.last_change] = self.value 15 | self.value = new_value 16 | self.last_change = time.asctime() 17 | 18 | def print_changes(self): 19 | print 'Changelog for Slate object:' 20 | for k, v in self.history.items(): 21 | print '%s\t %s' % (k, v) 22 | 23 | def __getstate__(self): 24 | # Deliberately do not return self.value or self.last_change. 25 | # We want to have a "blank slate" when we unpickle. 26 | return self.history 27 | 28 | def __setstate__(self, state): 29 | # Make self.history = state and last_change and value undefined 30 | self.history = state 31 | self.value, self.last_change = None, None 32 | -------------------------------------------------------------------------------- /listings/word.py: -------------------------------------------------------------------------------- 1 | class Word(str): 2 | '''Class for words, defining comparison based on word length.''' 3 | 4 | def __new__(cls, word): 5 | # Note that we have to use __new__. This is because str is an 6 | # immutable type, so we have to initialize it early (at creation) 7 | if ' ' in word: 8 | print "Value contains spaces. Truncating to first space." 9 | word = word[:word.index(' ')] 10 | # Word is now all chars before first space 11 | return str.__new__(cls, word) 12 | 13 | def __gt__(self, other): 14 | return len(self) > len(other) 15 | def __lt__(self, other): 16 | return len(self) < len(other) 17 | def __ge__(self, other): 18 | return len(self) >= len(other) 19 | def __le__(self, other): 20 | return len(self) <= len(other) 21 | -------------------------------------------------------------------------------- /magicmarkdown.py: -------------------------------------------------------------------------------- 1 | """ 2 | magicmarkdown.py 3 | utility script for changing markdown from magic methods guide into HTML 4 | """ 5 | 6 | import markdown 7 | 8 | HEADER = """ 9 |
10 | 11 |