Navigation

Disadvantages of the Python language over C++

In the C++ snippet the transaction is properly finalised regardless of anything. Moreover, you can’t forget it, the language performs that for you. In Python you have to remember to handle commit
the disadvantages of the Python language over C++


Besides performance (read here[1]), the following is what I miss in Python the most as a C++ coder:
1. Scoped resources. Compare C++
  1. void handle_request(Request r) {
  2. Transaction t(db_);
  3. // Construct queries, specify data sinks, etc.
  4. }
with Python
  1. def handle_request(r):
  2. t = Transaction(self._db)
  3. try:
  4. # Construct queries, specify data sinks, etc.
  5. t.commit()
  6. finally:
  7. t.rollback()
In the C++ snippet the transaction is properly finalised regardless of anything. Moreover, you can’t forget it, the language performs that for you. In Python you have to remember to handle commit/rollback situation manually. There is the “with” construct which saves some typing, but is still optional.
  1. def handle_request(r):
  2. t = Transaction(self._db)
  3. with t:
  4. # Construct queries, specify data sinks, etc.
  5. t.commit()
2. Scope resolution.
This is purely a readability problem, but in Python I have to prepend many names with “self.”, even class-level constants:
  1. def indicate_error(self):
  2. self.leds.set_color(self.LEFT, self.YELLOW, 0.5)
  3. self.leds.set_color(self.RIGHT, self.YELLOW, 0.5)
The C++ version would be:
  1. void indicate_error() {
  2. leds.set_color(LEFT, YELLOW, 0.5);
  3. leds.set_color(RIGHT, YELLOW, 0.5);
  4. }
I guess this is a part of “explicit is better than implicit” mantra, but if any Python experts know an elegant solution (not involving global variables), I’d appreciate the advice.
3. Annotation of immutability.
C++ features “const-correctness” that neatly expresses the command-query separation[2]. Note that this usage of “const” is only distantly related to constants[3].
  1. class Change {
  2. public:
  3. Cost estimate() const;
  4. void prepare();
  5. };
  6.  
  7. void transfigure(const Change& c);
For a C++ programmer it is obvious that transfigure() may call estimate(), but not prepare(). Furthermore, any functions invoked from within transfigure() must obey this rule. This is very unobvious with Python:
  1. class Change:
  2. def estimate(self):
  3. # some code
  4. def prepare(self):
  5. # some code
  6.  
  7. def transfigure(c):
  8. # some more code
4. Value types.
This is what gives idiomatic C++ programs so much of their relative simplicity!
  1. struct Money {
  2. std::string currency;
  3. int cents;
  4. };
  5.  
  6. Money a = estimate_price();
  7. register_price("ball", a);
  8. a.cents += 2; // This does not change the price of the ball.
  9. register_price("doll", a);
In the snippet above it is the current value of the variable a that is passed to the register_price() functions, not a reference to the variable itself. Therefore changes to the variable that happen after the call have no effect on the already registered prices.
Compare with Python:
  1. class Money:
  2. def __init__(self):
  3. self.currency = "EUR"
  4. self.cents = 0
  5.  
  6. a = estimate_price()
  7. register_price("ball", a)
  8. a.cents = a.cents + 2 # The price of the ball might change!
  9. register_price("doll", a)
Obviously, it is possible to write this kind of programs in Python, but that requires some thinking and IMHO the problem does not worth the effort.
5. Static typing.
If the program at hand is sufficiently big I find myself hunting class definitions. Code for context:
  1. def compute(m):
  2. metric_data = m.fetch()
  3. use(how_am_i_supposed_to_use_metric_data_here)
  4.  
  5. # The proper incantation looks like this:
  6. use(metric_data[“Cost”].data_points[0][“value”])
I normally spend significant time to figure that it is legal to access “data_points” and the number of indexing operators to use.
Compare with C++:
  1. Stats compute(Monitor *m) {
  2. MetricData data = m->fetch();
  3. use(/* How to use data? */);
  4. }
Go to the definition of Monitor (in many cases that’s a click), then TimeSeries. I see the indexer on MetricData returns a reference to TimeSeries which is an array of maps, indexed by strings. Bingo!
  1. class MetricData {
  2. public:
  3. const TimeSeries& operator[](const std::string& name) const;
  4. };
  5.  
  6. struct TimeSeries {
  7. std::vector<std::map<std::string, double>> data_points;
  8. };
Finally, I must admit that while I have substantial experience with C++ I am only a casual user of Python, so any corrections are welcome!

مشاركة

أضف تعليق:

1 comments: