|
| 1 | +原文:[5 reasons you need to learn to write Python decorators](https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators) |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +Decorators can massively magnify the positive impact of the code you write. |
| 6 | + |
| 7 | +By [Aaron |
| 8 | +Maxwell](https://www.oreilly.com/people/27097813-9a87-49f9-9fe3-84144509ace3) |
| 9 | + |
| 10 | +May 5, 2016 |
| 11 | + |
| 12 | + Dragons (source: |
| 14 | +[Pixabay](https://pixabay.com/en/dragons-china-thailand-ornament-1124668/)). |
| 15 | + |
| 16 | +If you're interested in learning how to use Python decorators and want to |
| 17 | +write more robust, reliable, and maintainable Python code, you'll want to |
| 18 | +check out Aaron Maxwell's online course, [Python–Beyond the |
| 19 | +Basics](https://www.oreilly.com/online-courses/python-beyond- |
| 20 | +basics.html?intcmp=il-prog-olreg-article- |
| 21 | +oltrain_5_reasons_you_need_to_learn_to_write_python_decorators_inline). |
| 22 | + |
| 23 | +Python decorators are so easy to use. Anyone who knows how to write a Python |
| 24 | +function can learn to use a decorator: |
| 25 | + |
| 26 | +[code] |
| 27 | + |
| 28 | + @somedecorator |
| 29 | + def some_function(): |
| 30 | + print("Check it out, I'm using decorators!") |
| 31 | + |
| 32 | +[/code] |
| 33 | + |
| 34 | +But _writing_ decorators is a whole different skill set. And it’s not trivial; |
| 35 | +you have to understand: |
| 36 | + |
| 37 | + * closures |
| 38 | + * how to work with functions as first-class arguments |
| 39 | + * variable arguments |
| 40 | + * argument unpacking, even |
| 41 | + * some details of how Python loads its source code. |
| 42 | + |
| 43 | +This all takes significant time to understand and master. And you already have |
| 44 | +a backlog of things to learn. Is this worth your time? |
| 45 | + |
| 46 | +For me, the answer has been "a thousand times, YES!" And odds are it will be |
| 47 | +for you, too. What are the key benefits of writing decorators...what they let |
| 48 | +you do easily and powerfully, in your day-to-day development? |
| 49 | + |
| 50 | +## Analytics, logging, and instrumentation |
| 51 | + |
| 52 | +Especially with large applications, we often need to specifically measure |
| 53 | +what’s going on, and record metrics that quantify different activities. By |
| 54 | +encapsulating such noteworthy events in their own function or method, a |
| 55 | +decorator can handle this requirement very readably and easily. |
| 56 | + |
| 57 | +[code] |
| 58 | + |
| 59 | + from myapp.log import logger |
| 60 | + |
| 61 | + def log_order_event(func): |
| 62 | + def wrapper(*args, **kwargs): |
| 63 | + logger.info("Ordering: %s", func.__name__) |
| 64 | + order = func(*args, **kwargs) |
| 65 | + logger.debug("Order result: %s", order.result) |
| 66 | + return order |
| 67 | + return wrapper |
| 68 | + |
| 69 | + @log_order_event |
| 70 | + def order_pizza(*toppings): |
| 71 | + # let's get some pizza! |
| 72 | + |
| 73 | +[/code] |
| 74 | + |
| 75 | +The same approach can be used to record counts or other metrics. |
| 76 | + |
| 77 | +## Validation and runtime checks |
| 78 | + |
| 79 | +Python’s type system is strongly typed, but very dynamic. For all its |
| 80 | +benefits, this means some bugs can try to creep in, which more statically |
| 81 | +typed languages (like Java) would catch at compile time. Looking beyond even |
| 82 | +that, you may want to enforce more sophisticated, custom checks on data going |
| 83 | +in or out. Decorators can let you easily handle all of this, and apply it to |
| 84 | +many functions at once. |
| 85 | + |
| 86 | +Imagine this: you have a set of functions, each returning a dictionary, which |
| 87 | +(among other fields) includes a field called "**summary**." The value of this |
| 88 | +summary must not be more than 80 characters long; if violated, that’s an |
| 89 | +error. Here is a decorator that raises a **ValueError** if that happens: |
| 90 | + |
| 91 | +[code] |
| 92 | + |
| 93 | + def validate_summary(func): |
| 94 | + def wrapper(*args, **kwargs): |
| 95 | + data = func(*args, **kwargs) |
| 96 | + if len(data["summary"]) > 80: |
| 97 | + raise ValueError("Summary too long") |
| 98 | + return data |
| 99 | + return wrapper |
| 100 | + |
| 101 | + @validate_summary |
| 102 | + def fetch_customer_data(): |
| 103 | + # ... |
| 104 | + |
| 105 | + @validate_summary |
| 106 | + def query_orders(criteria): |
| 107 | + # ... |
| 108 | + |
| 109 | + @validate_summary |
| 110 | + def create_invoice(params): |
| 111 | + # ... |
| 112 | + |
| 113 | +[/code] |
| 114 | + |
| 115 | +## Creating frameworks |
| 116 | + |
| 117 | +Once you master writing decorators, you'll be able to benefit from the simple |
| 118 | +syntax of using them, which lets you add semantics to the language that are |
| 119 | +easy to use. It's the next best thing to being able to extend the syntax of |
| 120 | +Python itself. |
| 121 | + |
| 122 | +In fact, many popular open source frameworks use this. The webapp framework |
| 123 | +Flask uses it to route URLs to functions that handle the HTTP request: |
| 124 | + |
| 125 | +[code] |
| 126 | + |
| 127 | + # For a RESTful todo-list API. |
| 128 | + @app.route("/tasks/", methods=["GET"]) |
| 129 | + def get_all_tasks(): |
| 130 | + tasks = app.store.get_all_tasks() |
| 131 | + return make_response(json.dumps(tasks), 200) |
| 132 | + |
| 133 | + @app.route("/tasks/", methods=["POST"]) |
| 134 | + def create_task(): |
| 135 | + payload = request.get_json(force=True) |
| 136 | + task_id = app.store.create_task( |
| 137 | + summary = payload["summary"], |
| 138 | + description = payload["description"], |
| 139 | + ) |
| 140 | + task_info = {"id": task_id} |
| 141 | + return make_response(json.dumps(task_info), 201) |
| 142 | + |
| 143 | + @app.route("/tasks/<int:task_id>/") |
| 144 | + def task_details(task_id): |
| 145 | + task_info = app.store.task_details(task_id) |
| 146 | + if task_info is None: |
| 147 | + return make_response("", 404) |
| 148 | + return json.dumps(task_info) |
| 149 | + |
| 150 | +[/code] |
| 151 | + |
| 152 | +Here you have a global object called **app**, with a method called **route**, |
| 153 | +taking certain arguments. That **route** method returns a decorator that is |
| 154 | +applied to the handler function. What’s going on beneath the hood is pretty |
| 155 | +intricate and complicated, but from the perspective of the person using Flask, |
| 156 | +all that complexity is completely hidden. |
| 157 | + |
| 158 | +Using decorators in this way also shows up in stock Python. For example, fully |
| 159 | +using the object system relies on the **classmethod** and **property** |
| 160 | +decorators: |
| 161 | + |
| 162 | +[code] |
| 163 | + |
| 164 | + class WeatherSimulation: |
| 165 | + def __init__(self, **params): |
| 166 | + self.params = params |
| 167 | + |
| 168 | + @classmethod |
| 169 | + def for_winter(cls, **other_params): |
| 170 | + params = {'month': 'Jan', 'temp': '0'} |
| 171 | + params.update(other_params) |
| 172 | + return cls(**params) |
| 173 | + |
| 174 | + @property |
| 175 | + def progress(self): |
| 176 | + return self.completed_iterations() / self.total_iterations() |
| 177 | + |
| 178 | +[/code] |
| 179 | + |
| 180 | +This class has three different def statements. But their semantics are all |
| 181 | +different: |
| 182 | + |
| 183 | + * the constructor is a normal method |
| 184 | + * for_winter is a classmethod providing a kind of factory, and |
| 185 | + * progress is read-only, dynamic attribute |
| 186 | + |
| 187 | +The simplicity of the **@classmethod** and **@property** decorators makes it |
| 188 | +easy to extend Python’s object semantics in everyday use. |
| 189 | + |
| 190 | +## Reusing impossible-to-reuse code |
| 191 | + |
| 192 | +Python gives you some very powerful tools for encapsulating code into an |
| 193 | +easily reusable form, with an expressive function syntax, functional |
| 194 | +programming support, and a full-featured object system. However, there are |
| 195 | +some patterns of code reuse which can’t be captured by these alone. |
| 196 | + |
| 197 | +Consider working with a flakey API. You make requests to something that speaks |
| 198 | +JSON over HTTP, and it works correctly 99.9% of the time. But… a small |
| 199 | +fraction of all requests will cause the server to return an internal error, |
| 200 | +and you need to retry the request. In that case, you’d implement some retry |
| 201 | +logic, like so: |
| 202 | + |
| 203 | +[code] |
| 204 | + |
| 205 | + resp = None |
| 206 | + while True: |
| 207 | + resp = make_api_call() |
| 208 | + if resp.status_code == 500 and tries < MAX_TRIES: |
| 209 | + tries += 1 |
| 210 | + continue |
| 211 | + break |
| 212 | + process_response(resp) |
| 213 | + |
| 214 | +[/code] |
| 215 | + |
| 216 | +Now imagine you have dozens of functions like **make_api_call()**, and they |
| 217 | +are called all over the codebase. Are you going to implement that while loop |
| 218 | +everywhere? Are you going to do it again every time you add a new API-calling |
| 219 | +function? This kind of pattern makes it hard to not have boilerplate code. |
| 220 | +Unless you use decorators. Then it’s quite simple: |
| 221 | + |
| 222 | +[code] |
| 223 | + |
| 224 | + # The decorated function returns a Response object, |
| 225 | + # which has a status_code attribute. 200 means |
| 226 | + # success; 500 indicates a server-side error. |
| 227 | + |
| 228 | + def retry(func): |
| 229 | + def retried_func(*args, **kwargs): |
| 230 | + MAX_TRIES = 3 |
| 231 | + tries = 0 |
| 232 | + while True: |
| 233 | + resp = func(*args, **kwargs) |
| 234 | + if resp.status_code == 500 and tries < MAX_TRIES: |
| 235 | + tries += 1 |
| 236 | + continue |
| 237 | + break |
| 238 | + return resp |
| 239 | + return retried_func |
| 240 | + |
| 241 | + This gives you an easy-to-use @retry decorator: |
| 242 | + |
| 243 | + @retry |
| 244 | + def make_api_call(): |
| 245 | + # .... |
| 246 | + |
| 247 | +[/code] |
| 248 | + |
| 249 | +## Boosting your career |
| 250 | + |
| 251 | +Writing decorators isn’t easy at first. It’s not rocket science, but takes |
| 252 | +enough effort to learn, and to grok the nuances involved, that many developers |
| 253 | +will never go to the trouble to master it. And that works to your advantage. |
| 254 | +When you become the person on your team who learns to write decorators well, |
| 255 | +and write decorators that solve real problems, other developers will use them. |
| 256 | +Because once the hard work of writing them is done, decorators are so easy to |
| 257 | +use. This can **massively** magnify the positive impact of the code you write. |
| 258 | +And it just might make you a hero, too. |
| 259 | + |
| 260 | +As I’ve traveled far and wide, training hundreds of working software engineers |
| 261 | +to use Python more effectively, teams have consistently reported writing |
| 262 | +decorators to be one of the most valuable and important tools they’ve learned |
| 263 | +in my advanced Python programming workshops. And that’s why it’s a key part of |
| 264 | +the upcoming [Python: Beyond the Basics](http://www.oreilly.com/online-courses |
| 265 | +/python-beyond-basics.html?intcmp=il-prog-olreg-article- |
| 266 | +oltrain_5_reasons_you_need_to_learn_to_write_python_decorators_inline) online |
| 267 | +course on May 25th and 26th, 2016. |
| 268 | + |
| 269 | +No matter how you learn to write decorators, you can be excited about what |
| 270 | +you’ll be able to do with them, and how it will—no joke—change the way you |
| 271 | +write Python code forever! |
| 272 | + |
| 273 | +Article image: Dragons (source: [Pixabay](https://pixabay.com/en/dragons- |
| 274 | +china-thailand-ornament-1124668/)). |
| 275 | + |
| 276 | +[](https://www.oreilly.com/people/ |
| 278 | +27097813-9a87-49f9-9fe3-84144509ace3) |
| 279 | + |
| 280 | +## [Aaron |
| 281 | +Maxwell](https://www.oreilly.com/people/27097813-9a87-49f9-9fe3-84144509ace3) |
| 282 | + |
| 283 | +Aaron Maxwell is the author of Advanced Python: A Not-For-Beginners Guide, and |
| 284 | +editor of the Advanced Python Newsletter. After a decade building the |
| 285 | +infrastructure and code for different Silicon Valley startups using a variety |
| 286 | +of languages—but mainly Python—he now travels widely to bring the most |
| 287 | +powerful secrets and best practices to Python developers around the world. He |
| 288 | +has started offering select online trainings, enabling students to benefit and |
| 289 | +learn at a fraction of the normal cost. |
| 290 | + |
| 291 | +* * * |
| 292 | + |
| 293 | +Video play |
| 294 | + |
| 295 | +[](https://www.oreilly.com/ideas/looking- |
| 298 | +back-looking-ahead-ada-lovelace-day-founder-suw-charman-anderson) |
| 299 | + |
| 300 | +[Software Engineering](https://www.oreilly.com/topics/software-engineering) |
| 301 | + |
| 302 | +## [Looking back and looking ahead with Ada Lovelace Day's |
| 303 | +founder](https://www.oreilly.com/ideas/looking-back-looking-ahead-ada- |
| 304 | +lovelace-day-founder-suw-charman-anderson) |
| 305 | + |
| 306 | +By [Mac Slocum](https://www.oreilly.com/people/0d2c1-mac-slocum) |
| 307 | + |
| 308 | +Suw Charman-Anderson, founder of Ada Lovelace Day, explains why she started |
| 309 | +the day and why it's caught on. |
| 310 | + |
| 311 | +[](https://www.oreilly.com/ideas/ada- |
| 314 | +lovelace-an-indirect-and-reciprocal-influence) |
| 315 | + |
| 316 | +[Software Engineering](https://www.oreilly.com/topics/software-engineering) |
| 317 | + |
| 318 | +## [Ada Lovelace, an indirect and reciprocal |
| 319 | +influence](https://www.oreilly.com/ideas/ada-lovelace-an-indirect-and- |
| 320 | +reciprocal-influence) |
| 321 | + |
| 322 | +By [Amy Jollymore](https://www.oreilly.com/people/amy-jollymore) |
| 323 | + |
| 324 | +Celebrating women in technology and the curious mind of Ada Lovelace |
| 325 | + |
| 326 | +[](https://www.oreilly.com/ideas |
| 330 | +/celebrating-ada-lovelace-day) |
| 331 | + |
| 332 | +[Software Engineering](https://www.oreilly.com/topics/software-engineering) |
| 333 | + |
| 334 | +## [Celebrating Ada Lovelace Day](https://www.oreilly.com/ideas/celebrating- |
| 335 | +ada-lovelace-day) |
| 336 | + |
| 337 | +By [Suzanne Axtell](https://www.oreilly.com/people/6e5fe-suzanne-axtell) |
| 338 | + |
| 339 | +The O'Reilly community shares stories of inspiring women in tech. Who inspired |
| 340 | +you? |
| 341 | + |
| 342 | +[](https://www.oreilly.com/ideas/the-five- |
| 345 | +shouts-of-good-programmers) |
| 346 | + |
| 347 | +[Software Engineering](https://www.oreilly.com/topics/software-engineering) |
| 348 | + |
| 349 | +## [The five shouts of good programmers](https://www.oreilly.com/ideas/the- |
| 350 | +five-shouts-of-good-programmers) |
| 351 | + |
| 352 | +By [Abraham Marin-Perez](https://www.oreilly.com/people/99e91f20-69c9-4c42 |
| 353 | +-bacf-198ffd26915c) |
| 354 | + |
| 355 | +A set of reactions to the most common programming scenarios that tend to turn |
| 356 | +software projects sour. |
| 357 | + |
| 358 | +### About Us |
| 359 | + |
| 360 | + * [Our Company](http://oreilly.com/about/) |
| 361 | + * [Work with Us](http://oreilly.com/work-with-us.html) |
| 362 | + * [Customer Service](http://shop.oreilly.com/category/customer-service.do) |
| 363 | + * [Contact Us](http://shop.oreilly.com/category/customer-service.do) |
| 364 | + |
| 365 | +### Site Map |
| 366 | + |
| 367 | + * [Ideas](https://www.oreilly.com/ideas) |
| 368 | + * [Learning](https://www.oreilly.com/learning) |
| 369 | + * [Topics](https://www.oreilly.com/topics) |
| 370 | + * [All](https://www.oreilly.com/all) |
| 371 | + |
| 372 | + * [ facebook ](http://fb.co/OReilly) |
| 373 | + * [ twitter ](http://twitter.com/oreillymedia) |
| 374 | + * [ youtube-large ](https://www.youtube.com/user/OreillyMedia) |
| 375 | + * [ google ](https://plus.google.com/+oreillymedia) |
| 376 | + * [ linkedin ](https://www.linkedin.com/company/o%27reilly-media) |
| 377 | + |
| 378 | +[ ](https://www.oreilly.com/) |
| 379 | + |
| 380 | +(C) 2016 O'Reilly Media, Inc. All trademarks and registered trademarks |
| 381 | +appearing on oreilly.com are the property of their respective owners. |
| 382 | + |
| 383 | + |
| 384 | +[Terms of Service](http://oreilly.com/terms/) • [Privacy |
| 385 | +Policy](http://oreilly.com/privacy.html) • [Editorial |
| 386 | +Independence](http://www.oreilly.com/about/editorial_independence.html) |
| 387 | + |
| 388 | + |
| 389 | + |
0 commit comments